Merge tag 'v3.2.1' into linked-identities
Version 3.2.1 Conflicts: OpenKeychain/build.gradle OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml OpenKeychain/src/main/res/values/strings.xml
This commit is contained in:
@@ -32,10 +32,10 @@ import java.util.regex.Pattern;
|
||||
|
||||
public class TwitterResource extends LinkedTokenResource {
|
||||
|
||||
public static final String[] CERT_PINS = new String[] {
|
||||
// antec Class 3 Secure Server CA - G4
|
||||
public static final String[] CERT_PINS = null; /*(new String[] {
|
||||
// Symantec Class 3 Secure Server CA - G4
|
||||
"513fb9743870b73440418d30930699ff"
|
||||
};
|
||||
};*/
|
||||
|
||||
final String mHandle;
|
||||
final String mTweetId;
|
||||
|
||||
@@ -376,6 +376,8 @@ public class ImportExportOperation extends BaseOperation {
|
||||
log.add(LogType.MSG_IMPORT_ERROR, 1);
|
||||
}
|
||||
|
||||
ContactSyncAdapterService.requestSync();
|
||||
|
||||
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
|
||||
importedMasterKeyIdsArray);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
@@ -32,7 +50,7 @@ public class PgpCertifyOperation {
|
||||
OperationLog log,
|
||||
int indent,
|
||||
CertifyAction action,
|
||||
Map<ByteBuffer,byte[]> signedHashes,
|
||||
Map<ByteBuffer, byte[]> signedHashes,
|
||||
Date creationTimestamp) {
|
||||
|
||||
if (!secretKey.isMasterKey()) {
|
||||
|
||||
@@ -178,13 +178,20 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
case PIN:
|
||||
case PATTERN:
|
||||
case PASSPHRASE: {
|
||||
if (cryptoInput.getPassphrase() == null) {
|
||||
Passphrase localPassphrase = cryptoInput.getPassphrase();
|
||||
if (localPassphrase == null) {
|
||||
try {
|
||||
localPassphrase = getCachedPassphrase(signingKeyRing.getMasterKeyId(), signingKey.getKeyId());
|
||||
} catch (PassphraseCacheInterface.NoSecretKeyException ignored) {
|
||||
}
|
||||
}
|
||||
if (localPassphrase == null) {
|
||||
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
|
||||
return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||
signingKeyRing.getMasterKeyId(), signingKey.getKeyId(),
|
||||
cryptoInput.getSignatureTime()));
|
||||
}
|
||||
if (!signingKey.unlock(cryptoInput.getPassphrase())) {
|
||||
if (!signingKey.unlock(localPassphrase)) {
|
||||
log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class KeychainContract {
|
||||
|
||||
interface ApiAppsColumns {
|
||||
String PACKAGE_NAME = "package_name";
|
||||
String PACKAGE_SIGNATURE = "package_signature";
|
||||
String PACKAGE_CERTIFICATE = "package_signature";
|
||||
}
|
||||
|
||||
interface ApiAppsAccountsColumns {
|
||||
|
||||
@@ -148,7 +148,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
"CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " ("
|
||||
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
|
||||
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB"
|
||||
+ ApiAppsColumns.PACKAGE_CERTIFICATE + " BLOB"
|
||||
+ ")";
|
||||
|
||||
private static final String CREATE_API_APPS_ACCOUNTS =
|
||||
|
||||
@@ -50,7 +50,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
@@ -1415,7 +1414,7 @@ public class ProviderHelper {
|
||||
private ContentValues contentValueForApiApps(AppSettings appSettings) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
|
||||
values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature());
|
||||
values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageSignature());
|
||||
return values;
|
||||
}
|
||||
|
||||
@@ -1462,7 +1461,7 @@ public class ProviderHelper {
|
||||
settings.setPackageName(cursor.getString(
|
||||
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
|
||||
settings.setPackageSignature(cursor.getBlob(
|
||||
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE)));
|
||||
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE)));
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
@@ -1554,31 +1553,10 @@ public class ProviderHelper {
|
||||
mContentResolver.insert(uri, values);
|
||||
}
|
||||
|
||||
public Set<String> getAllFingerprints(Uri uri) {
|
||||
Set<String> fingerprints = new HashSet<>();
|
||||
String[] projection = new String[]{KeyRings.FINGERPRINT};
|
||||
Cursor cursor = mContentResolver.query(uri, projection, null, null, null);
|
||||
try {
|
||||
if (cursor != null) {
|
||||
int fingerprintColumn = cursor.getColumnIndex(KeyRings.FINGERPRINT);
|
||||
while (cursor.moveToNext()) {
|
||||
fingerprints.add(
|
||||
KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(fingerprintColumn))
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
public byte[] getApiAppSignature(String packageName) {
|
||||
public byte[] getApiAppCertificate(String packageName) {
|
||||
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
|
||||
|
||||
String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE};
|
||||
String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE};
|
||||
|
||||
Cursor cursor = mContentResolver.query(queryUri, projection, null, null, null);
|
||||
try {
|
||||
|
||||
@@ -31,10 +31,12 @@ import android.provider.OpenableColumns;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.DatabaseUtil;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class TemporaryStorageProvider extends ContentProvider {
|
||||
|
||||
@@ -44,7 +46,9 @@ public class TemporaryStorageProvider extends ContentProvider {
|
||||
private static final String COLUMN_NAME = "name";
|
||||
private static final String COLUMN_TIME = "time";
|
||||
private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/");
|
||||
private static final int DB_VERSION = 1;
|
||||
private static final int DB_VERSION = 2;
|
||||
|
||||
private static File cacheDir;
|
||||
|
||||
public static Uri createFile(Context context, String targetName) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@@ -66,7 +70,7 @@ public class TemporaryStorageProvider extends ContentProvider {
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
|
||||
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
COLUMN_ID + " TEXT PRIMARY KEY, " +
|
||||
COLUMN_NAME + " TEXT, " +
|
||||
COLUMN_TIME + " INTEGER" +
|
||||
");");
|
||||
@@ -74,28 +78,39 @@ public class TemporaryStorageProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.d(Constants.TAG, "Upgrading files db from " + oldVersion + " to " + newVersion);
|
||||
|
||||
switch (oldVersion) {
|
||||
case 1:
|
||||
db.execSQL("DROP TABLE IF EXISTS files");
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
|
||||
COLUMN_ID + " TEXT PRIMARY KEY, " +
|
||||
COLUMN_NAME + " TEXT, " +
|
||||
COLUMN_TIME + " INTEGER" +
|
||||
");");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TemporaryStorageDatabase db;
|
||||
private static TemporaryStorageDatabase db;
|
||||
|
||||
private File getFile(Uri uri) throws FileNotFoundException {
|
||||
try {
|
||||
return getFile(Integer.parseInt(uri.getLastPathSegment()));
|
||||
return getFile(uri.getLastPathSegment());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
private File getFile(int id) {
|
||||
return new File(getContext().getCacheDir(), "temp/" + id);
|
||||
private File getFile(String id) {
|
||||
return new File(cacheDir, "temp/" + id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
db = new TemporaryStorageDatabase(getContext());
|
||||
return new File(getContext().getCacheDir(), "temp").mkdirs();
|
||||
cacheDir = getContext().getCacheDir();
|
||||
return new File(cacheDir, "temp").mkdirs();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,13 +148,15 @@ public class TemporaryStorageProvider extends ContentProvider {
|
||||
if (!values.containsKey(COLUMN_TIME)) {
|
||||
values.put(COLUMN_TIME, System.currentTimeMillis());
|
||||
}
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
values.put(COLUMN_ID, uuid);
|
||||
int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values);
|
||||
try {
|
||||
getFile(insert).createNewFile();
|
||||
getFile(uuid).createNewFile();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
return Uri.withAppendedPath(BASE_URI, Long.toString(insert));
|
||||
return Uri.withAppendedPath(BASE_URI, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,7 +169,7 @@ public class TemporaryStorageProvider extends ContentProvider {
|
||||
selectionArgs, null, null, null);
|
||||
if (files != null) {
|
||||
while (files.moveToNext()) {
|
||||
getFile(files.getInt(0)).delete();
|
||||
getFile(files.getString(0)).delete();
|
||||
}
|
||||
files.close();
|
||||
return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs);
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
@@ -47,6 +48,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||
import org.sufficientlysecure.keychain.remote.ui.SelectAllowedKeysActivity;
|
||||
import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
@@ -205,6 +207,18 @@ public class OpenPgpService extends RemoteService {
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
private PendingIntent getSelectAllowedKeysIntent(Intent data) {
|
||||
// If signature is unknown we return an _additional_ PendingIntent
|
||||
// to retrieve the missing key
|
||||
Intent intent = new Intent(getBaseContext(), SelectAllowedKeysActivity.class);
|
||||
intent.putExtra(SelectAllowedKeysActivity.EXTRA_SERVICE_INTENT, data);
|
||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(getCurrentCallingPackage()));
|
||||
|
||||
return PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
private PendingIntent getShowKeyPendingIntent(long masterKeyId) {
|
||||
Intent intent = new Intent(getBaseContext(), ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
@@ -403,6 +417,20 @@ public class OpenPgpService extends RemoteService {
|
||||
.setAdditionalEncryptId(signKeyId); // add sign key for encryption
|
||||
}
|
||||
|
||||
// OLD: Even if the message is not signed: Do self-encrypt to account key id
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
|
||||
String accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME);
|
||||
// if no account name is given use name "default"
|
||||
if (TextUtils.isEmpty(accName)) {
|
||||
accName = "default";
|
||||
}
|
||||
final AccountSettings accSettings = getAccSettings(accName);
|
||||
if (accSettings == null || (accSettings.getKeyId() == Constants.key.none)) {
|
||||
return getCreateAccountIntent(data, accName);
|
||||
}
|
||||
pseInput.setAdditionalEncryptId(accSettings.getKeyId());
|
||||
}
|
||||
|
||||
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
||||
if (inputParcel == null) {
|
||||
inputParcel = new CryptoInputParcel();
|
||||
@@ -476,13 +504,12 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Set<Long> allowedKeyIds;
|
||||
Set<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
|
||||
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
|
||||
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
|
||||
allowedKeyIds = mProviderHelper.getAllKeyIdsForApp(
|
||||
ApiAccounts.buildBaseUri(currentPkg));
|
||||
} else {
|
||||
allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
|
||||
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
|
||||
allowedKeyIds.addAll(mProviderHelper.getAllKeyIdsForApp(
|
||||
ApiAccounts.buildBaseUri(currentPkg)));
|
||||
}
|
||||
|
||||
long inputLength = is.available();
|
||||
@@ -575,6 +602,15 @@ public class OpenPgpService extends RemoteService {
|
||||
return result;
|
||||
} else {
|
||||
LogEntryParcel errorMsg = pgpResult.getLog().getLast();
|
||||
|
||||
if (errorMsg.mType == OperationResult.LogType.MSG_DC_ERROR_NO_KEY) {
|
||||
// allow user to select allowed keys
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, getSelectAllowedKeysIntent(data));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new Exception(getString(errorMsg.mType.getMsgId()));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -45,10 +47,10 @@ import java.util.Arrays;
|
||||
*/
|
||||
public abstract class RemoteService extends Service {
|
||||
|
||||
public static class WrongPackageSignatureException extends Exception {
|
||||
public static class WrongPackageCertificateException extends Exception {
|
||||
private static final long serialVersionUID = -8294642703122196028L;
|
||||
|
||||
public WrongPackageSignatureException(String message) {
|
||||
public WrongPackageCertificateException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -74,9 +76,9 @@ public abstract class RemoteService extends Service {
|
||||
String packageName = getCurrentCallingPackage();
|
||||
Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
|
||||
|
||||
byte[] packageSignature;
|
||||
byte[] packageCertificate;
|
||||
try {
|
||||
packageSignature = getPackageSignature(packageName);
|
||||
packageCertificate = getPackageCertificate(packageName);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Should not happen, returning!", e);
|
||||
// return error
|
||||
@@ -91,7 +93,7 @@ public abstract class RemoteService extends Service {
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_REGISTER);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageCertificate);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
@@ -105,7 +107,7 @@ public abstract class RemoteService extends Service {
|
||||
|
||||
return result;
|
||||
}
|
||||
} catch (WrongPackageSignatureException e) {
|
||||
} catch (WrongPackageCertificateException e) {
|
||||
Log.e(Constants.TAG, "wrong signature!", e);
|
||||
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
@@ -127,14 +129,24 @@ public abstract class RemoteService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPackageSignature(String packageName) throws NameNotFoundException {
|
||||
private byte[] getPackageCertificate(String packageName) throws NameNotFoundException {
|
||||
PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
|
||||
PackageManager.GET_SIGNATURES);
|
||||
Signature[] signatures = pkgInfo.signatures;
|
||||
// TODO: Only first signature?!
|
||||
byte[] packageSignature = signatures[0].toByteArray();
|
||||
// NOTE: Silly Android API naming: Signatures are actually certificates
|
||||
Signature[] certificates = pkgInfo.signatures;
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
for (Signature cert : certificates) {
|
||||
try {
|
||||
outputStream.write(cert.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Should not happen! Writing ByteArrayOutputStream to concat certificates failed");
|
||||
}
|
||||
}
|
||||
|
||||
return packageSignature;
|
||||
// Even if an apk has several certificates, these certificates should never change
|
||||
// Google Play does not allow the introduction of new certificates into an existing apk
|
||||
// Also see this attack: http://stackoverflow.com/a/10567852
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,9 +156,12 @@ public abstract class RemoteService extends Service {
|
||||
* @return package name
|
||||
*/
|
||||
protected String getCurrentCallingPackage() {
|
||||
// TODO:
|
||||
// callingPackages contains more than one entry when sharedUserId has been used...
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
|
||||
|
||||
// NOTE: No support for sharedUserIds
|
||||
// callingPackages contains more than one entry when sharedUserId has been used
|
||||
// No plans to support sharedUserIds due to many bugs connected to them:
|
||||
// http://java-hamster.blogspot.de/2010/05/androids-shareduserid.html
|
||||
String currentPkg = callingPackages[0];
|
||||
Log.d(Constants.TAG, "currentPkg: " + currentPkg);
|
||||
|
||||
@@ -155,12 +170,12 @@ public abstract class RemoteService extends Service {
|
||||
|
||||
/**
|
||||
* DEPRECATED API
|
||||
*
|
||||
* <p/>
|
||||
* Retrieves AccountSettings from database for the application calling this remote service
|
||||
*/
|
||||
protected AccountSettings getAccSettings(String accountName) {
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Log.d(Constants.TAG, "getAccSettings accountName: "+ accountName);
|
||||
Log.d(Constants.TAG, "getAccSettings accountName: " + accountName);
|
||||
|
||||
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
|
||||
|
||||
@@ -198,14 +213,14 @@ public abstract class RemoteService extends Service {
|
||||
*
|
||||
* @param allowOnlySelf allow only Keychain app itself
|
||||
* @return true if process is allowed to use this service
|
||||
* @throws WrongPackageSignatureException
|
||||
* @throws WrongPackageCertificateException
|
||||
*/
|
||||
private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageSignatureException {
|
||||
private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageCertificateException {
|
||||
return isUidAllowed(Binder.getCallingUid(), allowOnlySelf);
|
||||
}
|
||||
|
||||
private boolean isUidAllowed(int uid, boolean allowOnlySelf)
|
||||
throws WrongPackageSignatureException {
|
||||
throws WrongPackageCertificateException {
|
||||
if (android.os.Process.myUid() == uid) {
|
||||
return true;
|
||||
}
|
||||
@@ -229,11 +244,9 @@ public abstract class RemoteService extends Service {
|
||||
/**
|
||||
* Checks if packageName is a registered app for the API. Does not return true for own package!
|
||||
*
|
||||
* @param packageName
|
||||
* @return
|
||||
* @throws WrongPackageSignatureException
|
||||
* @throws WrongPackageCertificateException
|
||||
*/
|
||||
private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException {
|
||||
private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException {
|
||||
Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
|
||||
|
||||
ArrayList<String> allowedPkgs = mProviderHelper.getRegisteredApiApps();
|
||||
@@ -244,22 +257,22 @@ public abstract class RemoteService extends Service {
|
||||
Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
|
||||
|
||||
// check package signature
|
||||
byte[] currentSig;
|
||||
byte[] currentCert;
|
||||
try {
|
||||
currentSig = getPackageSignature(packageName);
|
||||
currentCert = getPackageCertificate(packageName);
|
||||
} catch (NameNotFoundException e) {
|
||||
throw new WrongPackageSignatureException(e.getMessage());
|
||||
throw new WrongPackageCertificateException(e.getMessage());
|
||||
}
|
||||
|
||||
byte[] storedSig = mProviderHelper.getApiAppSignature(packageName);
|
||||
if (Arrays.equals(currentSig, storedSig)) {
|
||||
byte[] storedCert = mProviderHelper.getApiAppCertificate(packageName);
|
||||
if (Arrays.equals(currentCert, storedCert)) {
|
||||
Log.d(Constants.TAG,
|
||||
"Package signature is correct! (equals signature from database)");
|
||||
"Package certificate is correct! (equals certificate from database)");
|
||||
return true;
|
||||
} else {
|
||||
throw new WrongPackageSignatureException(
|
||||
"PACKAGE NOT ALLOWED! Signature wrong! (Signature not " +
|
||||
"equals signature from database)");
|
||||
throw new WrongPackageCertificateException(
|
||||
"PACKAGE NOT ALLOWED! Certificate wrong! (Certificate not " +
|
||||
"equals certificate from database)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// TODO: make extensible BaseRemoteServiceActivity and extend these cases from it
|
||||
public class RemoteServiceActivity extends BaseActivity {
|
||||
|
||||
public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class SelectAllowedKeysActivity extends BaseActivity {
|
||||
|
||||
public static final String EXTRA_SERVICE_INTENT = "data";
|
||||
|
||||
private Uri mAppUri;
|
||||
|
||||
private AppSettingsAllowedKeysListFragment mAllowedKeysFragment;
|
||||
|
||||
Intent mServiceData;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done" custom action bar
|
||||
setFullScreenDialogDoneClose(R.string.api_settings_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
save();
|
||||
}
|
||||
},
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cancel();
|
||||
}
|
||||
});
|
||||
|
||||
Intent intent = getIntent();
|
||||
mServiceData = intent.getParcelableExtra(EXTRA_SERVICE_INTENT);
|
||||
mAppUri = intent.getData();
|
||||
if (mAppUri == null) {
|
||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
|
||||
finish();
|
||||
return;
|
||||
} else {
|
||||
Log.d(Constants.TAG, "uri: " + mAppUri);
|
||||
loadData(savedInstanceState, mAppUri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.api_remote_select_allowed_keys);
|
||||
}
|
||||
|
||||
private void save() {
|
||||
mAllowedKeysFragment.saveAllowedKeys();
|
||||
setResult(Activity.RESULT_OK, mServiceData);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void loadData(Bundle savedInstanceState, Uri appUri) {
|
||||
Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
|
||||
Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri);
|
||||
startListFragments(savedInstanceState, allowedKeysUri);
|
||||
}
|
||||
|
||||
private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) {
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
// we could end up with overlapping fragments.
|
||||
if (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance of the fragments
|
||||
mAllowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri);
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.api_allowed_keys_list_fragment, mAllowedKeysFragment)
|
||||
.commitAllowingStateLoss();
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,18 +50,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
*/
|
||||
public class CloudImportService extends Service implements Progressable {
|
||||
|
||||
//required as extras from intent
|
||||
// required as extras from intent
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
public static final String EXTRA_DATA = "data";
|
||||
|
||||
//required by data bundle
|
||||
// required by data bundle
|
||||
public static final String IMPORT_KEY_LIST = "import_key_list";
|
||||
public static final String IMPORT_KEY_SERVER = "import_key_server";
|
||||
|
||||
// indicates a request to cancel the import
|
||||
public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
|
||||
|
||||
//tells the spawned threads whether the user has requested a cancel
|
||||
// tells the spawned threads whether the user has requested a cancel
|
||||
private static AtomicBoolean mActionCancelled = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
@@ -86,7 +86,7 @@ public class CloudImportService extends Service implements Progressable {
|
||||
|
||||
public KeyImportAccumulator(int totalKeys) {
|
||||
mTotalKeys = totalKeys;
|
||||
//ignore updates from ImportExportOperation for now
|
||||
// ignore updates from ImportExportOperation for now
|
||||
mImportProgressable = new Progressable() {
|
||||
@Override
|
||||
public void setProgress(String message, int current, int total) {
|
||||
@@ -131,20 +131,17 @@ public class CloudImportService extends Service implements Progressable {
|
||||
mSecret += result.mSecret;
|
||||
|
||||
long[] masterKeyIds = result.getImportedMasterKeyIds();
|
||||
for (int i = 0; i < masterKeyIds.length; i++) {
|
||||
mImportedMasterKeyIds.add(masterKeyIds[i]);
|
||||
for (long masterKeyId : masterKeyIds) {
|
||||
mImportedMasterKeyIds.add(masterKeyId);
|
||||
}
|
||||
|
||||
// if any key import has been cancelled, set result type to cancelled
|
||||
// resultType is added to in getConsolidatedKayImport to account for remaining factors
|
||||
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* returns accumulated result of all imports so far
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ImportKeyResult getConsolidatedImportKeyResult() {
|
||||
|
||||
@@ -205,7 +202,7 @@ public class CloudImportService extends Service implements Progressable {
|
||||
Bundle data = extras.getBundle(EXTRA_DATA);
|
||||
|
||||
final String keyServer = data.getString(IMPORT_KEY_SERVER);
|
||||
//keyList being null (in case key list to be reaad from cache) is checked by importKeys
|
||||
// keyList being null (in case key list to be reaad from cache) is checked by importKeys
|
||||
final ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
||||
|
||||
// Adding keys to the ThreadPoolExecutor takes time, we don't want to block the main thread
|
||||
@@ -225,7 +222,7 @@ public class CloudImportService extends Service implements Progressable {
|
||||
new ParcelableFileCache<>(this, "key_import.pcl");
|
||||
int totKeys = 0;
|
||||
Iterator<ParcelableKeyRing> keyListIterator = null;
|
||||
//either keyList or cache must be null, no guarantees otherwise
|
||||
// either keyList or cache must be null, no guarantees otherwise
|
||||
if (keyList == null) {//export from cache, copied from ImportExportOperation.importKeyRings
|
||||
|
||||
try {
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@@ -84,7 +83,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
||||
String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
|
||||
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
|
||||
|
||||
Fragment frag2 = CreateKeyYubiImportFragment.createInstance(
|
||||
Fragment frag2 = CreateKeyYubiKeyImportFragment.createInstance(
|
||||
nfcFingerprints, nfcAid, nfcUserId);
|
||||
loadFragment(frag2, FragAction.START);
|
||||
|
||||
@@ -99,7 +98,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
||||
|
||||
if (mFirstTime) {
|
||||
setTitle(R.string.app_name);
|
||||
setActionBarIcon(R.drawable.ic_launcher);
|
||||
mToolbar.setNavigationIcon(null);
|
||||
mToolbar.setNavigationOnClickListener(null);
|
||||
} else {
|
||||
setTitle(R.string.title_manage_my_keys);
|
||||
@@ -131,7 +130,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
||||
finish();
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Fragment frag = CreateKeyYubiImportFragment.createInstance(
|
||||
Fragment frag = CreateKeyYubiKeyImportFragment.createInstance(
|
||||
scannedFingerprints, nfcAid, userId);
|
||||
loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
@@ -44,18 +43,17 @@ import org.sufficientlysecure.keychain.ui.widget.EmailEditText;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class CreateKeyEmailFragment extends Fragment {
|
||||
private CreateKeyActivity mCreateKeyActivity;
|
||||
private EmailEditText mEmailEdit;
|
||||
private ArrayList<EmailAdapter.ViewModel> mAdditionalEmailModels = new ArrayList<>();
|
||||
private EmailAdapter mEmailAdapter;
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
EmailEditText mEmailEdit;
|
||||
RecyclerView mEmailsRecyclerView;
|
||||
View mBackButton;
|
||||
View mNextButton;
|
||||
|
||||
ArrayList<EmailAdapter.ViewModel> mAdditionalEmailModels;
|
||||
|
||||
EmailAdapter mEmailAdapter;
|
||||
// NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS
|
||||
// EMAIL_ADDRESS fails for mails with umlauts for example
|
||||
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$");
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
@@ -73,14 +71,13 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
* Checks if text of given EditText is not empty. If it is empty an error is
|
||||
* set and the EditText gets the focus.
|
||||
*
|
||||
* @param context
|
||||
* @param editText
|
||||
* @return true if EditText is not empty
|
||||
*/
|
||||
private static boolean isEditTextNotEmpty(Context context, EditText editText) {
|
||||
private boolean isMainEmailValid(EditText editText) {
|
||||
boolean output = true;
|
||||
if (editText.getText().length() == 0) {
|
||||
editText.setError(context.getString(R.string.create_key_empty));
|
||||
if (!checkEmail(editText.getText().toString(), false)) {
|
||||
editText.setError(getString(R.string.create_key_empty));
|
||||
editText.requestFocus();
|
||||
output = false;
|
||||
} else {
|
||||
@@ -95,9 +92,9 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
View view = inflater.inflate(R.layout.create_key_email_fragment, container, false);
|
||||
|
||||
mEmailEdit = (EmailEditText) view.findViewById(R.id.create_key_email);
|
||||
mBackButton = view.findViewById(R.id.create_key_back_button);
|
||||
mNextButton = view.findViewById(R.id.create_key_next_button);
|
||||
mEmailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails);
|
||||
View backButton = view.findViewById(R.id.create_key_back_button);
|
||||
View nextButton = view.findViewById(R.id.create_key_next_button);
|
||||
RecyclerView emailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails);
|
||||
|
||||
// initial values
|
||||
mEmailEdit.setText(mCreateKeyActivity.mEmail);
|
||||
@@ -106,29 +103,21 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
if (mCreateKeyActivity.mEmail == null) {
|
||||
mEmailEdit.requestFocus();
|
||||
}
|
||||
mBackButton.setOnClickListener(new View.OnClickListener() {
|
||||
backButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
|
||||
}
|
||||
});
|
||||
mNextButton.setOnClickListener(new View.OnClickListener() {
|
||||
nextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
nextClicked();
|
||||
}
|
||||
});
|
||||
mEmailsRecyclerView.setHasFixedSize(true);
|
||||
mEmailsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
mEmailsRecyclerView.setItemAnimator(new DefaultItemAnimator());
|
||||
|
||||
// initial values
|
||||
if (mAdditionalEmailModels == null) {
|
||||
mAdditionalEmailModels = new ArrayList<>();
|
||||
if (mCreateKeyActivity.mAdditionalEmails != null) {
|
||||
mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails);
|
||||
}
|
||||
}
|
||||
emailsRecyclerView.setHasFixedSize(true);
|
||||
emailsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
emailsRecyclerView.setItemAnimator(new DefaultItemAnimator());
|
||||
|
||||
if (mEmailAdapter == null) {
|
||||
mEmailAdapter = new EmailAdapter(mAdditionalEmailModels, new View.OnClickListener() {
|
||||
@@ -137,13 +126,77 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
addEmail();
|
||||
}
|
||||
});
|
||||
|
||||
if (mCreateKeyActivity.mAdditionalEmails != null) {
|
||||
mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails);
|
||||
}
|
||||
}
|
||||
|
||||
mEmailsRecyclerView.setAdapter(mEmailAdapter);
|
||||
emailsRecyclerView.setAdapter(mEmailAdapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given email is valid
|
||||
*
|
||||
* @param email
|
||||
* @param additionalEmail
|
||||
* @return
|
||||
*/
|
||||
private boolean checkEmail(String email, boolean additionalEmail) {
|
||||
// check for email format or if the user did any input
|
||||
if (!isEmailFormatValid(email)) {
|
||||
Notify.create(getActivity(),
|
||||
getString(R.string.create_key_email_invalid_email),
|
||||
Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for duplicated emails
|
||||
if (!additionalEmail && isEmailDuplicatedInsideAdapter(email) || additionalEmail &&
|
||||
mEmailEdit.getText().length() > 0 && email.equals(mEmailEdit.getText().toString())) {
|
||||
Notify.create(getActivity(),
|
||||
getString(R.string.create_key_email_already_exists_text),
|
||||
Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the email format
|
||||
* Uses the default Android Email Pattern
|
||||
*
|
||||
* @param email
|
||||
* @return
|
||||
*/
|
||||
private boolean isEmailFormatValid(String email) {
|
||||
// check for email format or if the user did any input
|
||||
return !(email.length() == 0 || !EMAIL_PATTERN.matcher(email).matches());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicated emails inside the additional email adapter.
|
||||
*
|
||||
* @param email
|
||||
* @return
|
||||
*/
|
||||
private boolean isEmailDuplicatedInsideAdapter(String email) {
|
||||
//check for duplicated emails inside the adapter
|
||||
for (EmailAdapter.ViewModel model : mAdditionalEmailModels) {
|
||||
if (email.equals(model.email)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialog fragment for the user to input a valid email.
|
||||
*/
|
||||
private void addEmail() {
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
@@ -153,34 +206,17 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
|
||||
String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL);
|
||||
|
||||
if (email.length() > 0 && mEmailEdit.getText().length() > 0 &&
|
||||
email.equals(mEmailEdit.getText().toString())) {
|
||||
Notify.create(getActivity(),
|
||||
getString(R.string.create_key_email_already_exists_text),
|
||||
Notify.LENGTH_LONG, Notify.Style.ERROR).show();
|
||||
return;
|
||||
if (checkEmail(email, true)) {
|
||||
// add new user id
|
||||
mEmailAdapter.add(email);
|
||||
}
|
||||
//check for duplicated emails inside the adapter
|
||||
for (EmailAdapter.ViewModel model : mAdditionalEmailModels) {
|
||||
if (email.equals(model.email)) {
|
||||
Notify.create(getActivity(),
|
||||
getString(R.string.create_key_email_already_exists_text),
|
||||
Notify.LENGTH_LONG, Notify.Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add new user id
|
||||
mEmailAdapter.add(email);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
AddEmailDialogFragment addEmailDialog = AddEmailDialogFragment.newInstance(messenger);
|
||||
|
||||
addEmailDialog.show(getActivity().getSupportFragmentManager(), "addEmailDialog");
|
||||
}
|
||||
|
||||
@@ -191,7 +227,7 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void nextClicked() {
|
||||
if (isEditTextNotEmpty(getActivity(), mEmailEdit)) {
|
||||
if (isMainEmailValid(mEmailEdit)) {
|
||||
// save state
|
||||
mCreateKeyActivity.mEmail = mEmailEdit.getText().toString();
|
||||
mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails();
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.method.HideReturnsTransformationMethod;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -37,9 +36,6 @@ import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
import org.sufficientlysecure.keychain.ui.widget.PassphraseEditText;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CreateKeyPassphraseFragment extends Fragment {
|
||||
|
||||
// view
|
||||
@@ -111,8 +107,8 @@ public class CreateKeyPassphraseFragment extends Fragment {
|
||||
// initial values
|
||||
// TODO: using String here is unsafe...
|
||||
if (mCreateKeyActivity.mPassphrase != null) {
|
||||
mPassphraseEdit.setText(Arrays.toString(mCreateKeyActivity.mPassphrase.getCharArray()));
|
||||
mPassphraseEditAgain.setText(Arrays.toString(mCreateKeyActivity.mPassphrase.getCharArray()));
|
||||
mPassphraseEdit.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray()));
|
||||
mPassphraseEditAgain.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray()));
|
||||
}
|
||||
|
||||
mPassphraseEdit.requestFocus();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -18,37 +18,20 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.DefaultItemAnimator;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.AddEmailDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.widget.EmailEditText;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CreateKeyStartFragment extends Fragment {
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
@@ -56,8 +39,8 @@ public class CreateKeyStartFragment extends Fragment {
|
||||
View mCreateKey;
|
||||
View mImportKey;
|
||||
View mYubiKey;
|
||||
TextView mCancel;
|
||||
public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012;
|
||||
TextView mSkipOrCancel;
|
||||
public static final int REQUEST_CODE_IMPORT_KEY = 0x00007012;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
@@ -79,12 +62,12 @@ public class CreateKeyStartFragment extends Fragment {
|
||||
mCreateKey = view.findViewById(R.id.create_key_create_key_button);
|
||||
mImportKey = view.findViewById(R.id.create_key_import_button);
|
||||
mYubiKey = view.findViewById(R.id.create_key_yubikey_button);
|
||||
mCancel = (TextView) view.findViewById(R.id.create_key_cancel);
|
||||
mSkipOrCancel = (TextView) view.findViewById(R.id.create_key_cancel);
|
||||
|
||||
if (mCreateKeyActivity.mFirstTime) {
|
||||
mCancel.setText(R.string.first_time_skip);
|
||||
mSkipOrCancel.setText(R.string.first_time_skip);
|
||||
} else {
|
||||
mCancel.setText(R.string.btn_do_not_save);
|
||||
mSkipOrCancel.setText(R.string.btn_do_not_save);
|
||||
}
|
||||
|
||||
mCreateKey.setOnClickListener(new View.OnClickListener() {
|
||||
@@ -98,7 +81,7 @@ public class CreateKeyStartFragment extends Fragment {
|
||||
mYubiKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
CreateKeyYubiWaitFragment frag = new CreateKeyYubiWaitFragment();
|
||||
CreateKeyYubiKeyWaitFragment frag = new CreateKeyYubiKeyWaitFragment();
|
||||
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
});
|
||||
@@ -108,48 +91,48 @@ public class CreateKeyStartFragment extends Fragment {
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(mCreateKeyActivity, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
startActivityForResult(intent, REQUEST_CODE_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
|
||||
mCancel.setOnClickListener(new View.OnClickListener() {
|
||||
mSkipOrCancel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finishSetup(null);
|
||||
if (mCreateKeyActivity.mFirstTime) {
|
||||
Preferences prefs = Preferences.getPreferences(mCreateKeyActivity);
|
||||
prefs.setFirstTime(false);
|
||||
Intent intent = new Intent(mCreateKeyActivity, MainActivity.class);
|
||||
startActivity(intent);
|
||||
mCreateKeyActivity.finish();
|
||||
} else {
|
||||
// just finish activity and return data
|
||||
mCreateKeyActivity.setResult(Activity.RESULT_CANCELED);
|
||||
mCreateKeyActivity.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
private void finishSetup(Intent srcData) {
|
||||
if (mCreateKeyActivity.mFirstTime) {
|
||||
Preferences prefs = Preferences.getPreferences(mCreateKeyActivity);
|
||||
prefs.setFirstTime(false);
|
||||
}
|
||||
Intent intent = new Intent(mCreateKeyActivity, MainActivity.class);
|
||||
// give intent through to display notify
|
||||
if (srcData != null) {
|
||||
intent.putExtras(srcData);
|
||||
}
|
||||
startActivity(intent);
|
||||
mCreateKeyActivity.finish();
|
||||
}
|
||||
|
||||
// workaround for https://code.google.com/p/android/issues/detail?id=61394
|
||||
// @Override
|
||||
// public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
// return keyCode == KeyEvent.KEYCODE_MENU || super.onKeyDown(keyCode, event);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQUEST_CODE_CREATE_OR_IMPORT_KEY) {
|
||||
if (requestCode == REQUEST_CODE_IMPORT_KEY) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
finishSetup(data);
|
||||
if (mCreateKeyActivity.mFirstTime) {
|
||||
Preferences prefs = Preferences.getPreferences(mCreateKeyActivity);
|
||||
prefs.setFirstTime(false);
|
||||
Intent intent = new Intent(mCreateKeyActivity, MainActivity.class);
|
||||
intent.putExtras(data);
|
||||
startActivity(intent);
|
||||
mCreateKeyActivity.finish();
|
||||
} else {
|
||||
// just finish activity and return data
|
||||
mCreateKeyActivity.setResult(Activity.RESULT_OK, data);
|
||||
mCreateKeyActivity.finish();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "No valid request code!");
|
||||
|
||||
@@ -48,7 +48,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
|
||||
public class CreateKeyYubiImportFragment extends Fragment implements NfcListenerFragment {
|
||||
public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListenerFragment {
|
||||
|
||||
private static final String ARG_FINGERPRINT = "fingerprint";
|
||||
public static final String ARG_AID = "aid";
|
||||
@@ -67,7 +67,7 @@ public class CreateKeyYubiImportFragment extends Fragment implements NfcListener
|
||||
|
||||
public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
|
||||
|
||||
CreateKeyYubiImportFragment frag = new CreateKeyYubiImportFragment();
|
||||
CreateKeyYubiKeyImportFragment frag = new CreateKeyYubiKeyImportFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_FINGERPRINT, scannedFingerprints);
|
||||
@@ -28,7 +28,7 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
|
||||
|
||||
public class CreateKeyYubiWaitFragment extends Fragment {
|
||||
public class CreateKeyYubiKeyWaitFragment extends Fragment {
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
View mBackButton;
|
||||
@@ -25,7 +25,7 @@ import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
@@ -86,12 +86,12 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false);
|
||||
View view = inflater.inflate(R.layout.decrypt_files_fragment, container, false);
|
||||
|
||||
mFilename = (TextView) view.findViewById(R.id.decrypt_file_filename);
|
||||
mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption);
|
||||
mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt);
|
||||
view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() {
|
||||
mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename);
|
||||
mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_files_delete_after_decryption);
|
||||
mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt);
|
||||
view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
|
||||
@@ -232,7 +232,6 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (pgpResult.success()) {
|
||||
|
||||
switch (mCurrentCryptoOperation) {
|
||||
case KeychainIntentService.ACTION_DECRYPT_METADATA: {
|
||||
askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
|
||||
@@ -264,9 +263,8 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pgpResult.createNotify(getActivity()).show();
|
||||
}
|
||||
pgpResult.createNotify(getActivity()).show(DecryptFilesFragment.this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -309,7 +307,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onVerifyLoaded(boolean verified) {
|
||||
protected void onVerifyLoaded(boolean hideErrorOverlay) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -55,24 +56,24 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
|
||||
public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final int LOADER_ID_UNIFIED = 0;
|
||||
|
||||
protected LinearLayout mResultLayout;
|
||||
|
||||
protected ImageView mEncryptionIcon;
|
||||
protected TextView mEncryptionText;
|
||||
protected ImageView mSignatureIcon;
|
||||
protected TextView mSignatureText;
|
||||
|
||||
protected View mSignatureLayout;
|
||||
protected TextView mSignatureName;
|
||||
protected TextView mSignatureEmail;
|
||||
protected TextView mSignatureAction;
|
||||
|
||||
private LinearLayout mContentLayout;
|
||||
private LinearLayout mErrorOverlayLayout;
|
||||
|
||||
private OpenPgpSignatureResult mSignatureResult;
|
||||
|
||||
@Override
|
||||
@@ -82,7 +83,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
// NOTE: These views are inside the activity!
|
||||
mResultLayout = (LinearLayout) getActivity().findViewById(R.id.result_main_layout);
|
||||
mResultLayout.setVisibility(View.GONE);
|
||||
|
||||
mEncryptionIcon = (ImageView) getActivity().findViewById(R.id.result_encryption_icon);
|
||||
mEncryptionText = (TextView) getActivity().findViewById(R.id.result_encryption_text);
|
||||
mSignatureIcon = (ImageView) getActivity().findViewById(R.id.result_signature_icon);
|
||||
@@ -92,6 +92,17 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
mSignatureEmail = (TextView) getActivity().findViewById(R.id.result_signature_email);
|
||||
mSignatureAction = (TextView) getActivity().findViewById(R.id.result_signature_action);
|
||||
|
||||
// Overlay
|
||||
mContentLayout = (LinearLayout) view.findViewById(R.id.decrypt_content);
|
||||
mErrorOverlayLayout = (LinearLayout) view.findViewById(R.id.decrypt_error_overlay);
|
||||
Button vErrorOverlayButton = (Button) view.findViewById(R.id.decrypt_error_overlay_button);
|
||||
vErrorOverlayButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mErrorOverlayLayout.setVisibility(View.GONE);
|
||||
mContentLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void lookupUnknownKey(long unknownKeyId) {
|
||||
@@ -113,12 +124,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
final ImportKeyResult result =
|
||||
returnData.getParcelable(OperationResult.EXTRA_RESULT);
|
||||
|
||||
// if (!result.success()) {
|
||||
result.createNotify(getActivity()).show();
|
||||
// }
|
||||
result.createNotify(getActivity()).show();
|
||||
|
||||
getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -153,7 +161,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
getActivity().startService(intent);
|
||||
|
||||
}
|
||||
|
||||
private void showKey(long keyId) {
|
||||
@@ -191,6 +198,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
|
||||
getLoaderManager().destroyLoader(LOADER_ID_UNIFIED);
|
||||
|
||||
mErrorOverlayLayout.setVisibility(View.GONE);
|
||||
mContentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
onVerifyLoaded(true);
|
||||
|
||||
return;
|
||||
@@ -205,7 +215,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
}
|
||||
|
||||
getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this);
|
||||
|
||||
}
|
||||
|
||||
private void setSignatureLayoutVisibility(int visibility) {
|
||||
@@ -228,8 +237,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
KeychainContract.KeyRings._ID,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.USER_ID,
|
||||
KeychainContract.KeyRings.IS_REVOKED,
|
||||
KeychainContract.KeyRings.IS_EXPIRED,
|
||||
KeychainContract.KeyRings.VERIFIED,
|
||||
KeychainContract.KeyRings.HAS_ANY_SECRET,
|
||||
};
|
||||
@@ -237,10 +244,8 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_MASTER_KEY_ID = 1;
|
||||
static final int INDEX_USER_ID = 2;
|
||||
static final int INDEX_IS_REVOKED = 3;
|
||||
static final int INDEX_IS_EXPIRED = 4;
|
||||
static final int INDEX_VERIFIED = 5;
|
||||
static final int INDEX_HAS_ANY_SECRET = 6;
|
||||
static final int INDEX_VERIFIED = 3;
|
||||
static final int INDEX_HAS_ANY_SECRET = 4;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
@@ -282,8 +287,10 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
getActivity(), mSignatureResult.getKeyId()));
|
||||
}
|
||||
|
||||
boolean isRevoked = data.getInt(INDEX_IS_REVOKED) != 0;
|
||||
boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
|
||||
// NOTE: Don't use revoked and expired fields from database, they don't show
|
||||
// revoked/expired subkeys
|
||||
boolean isRevoked = mSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED;
|
||||
boolean isExpired = mSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED;
|
||||
boolean isVerified = data.getInt(INDEX_VERIFIED) > 0;
|
||||
boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
|
||||
@@ -294,6 +301,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
setSignatureLayoutVisibility(View.VISIBLE);
|
||||
setShowAction(signatureKeyId);
|
||||
|
||||
mErrorOverlayLayout.setVisibility(View.VISIBLE);
|
||||
mContentLayout.setVisibility(View.GONE);
|
||||
|
||||
onVerifyLoaded(false);
|
||||
|
||||
} else if (isExpired) {
|
||||
@@ -303,6 +313,22 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
setSignatureLayoutVisibility(View.VISIBLE);
|
||||
setShowAction(signatureKeyId);
|
||||
|
||||
mErrorOverlayLayout.setVisibility(View.GONE);
|
||||
mContentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
onVerifyLoaded(true);
|
||||
|
||||
} else if (isYours) {
|
||||
|
||||
mSignatureText.setText(R.string.decrypt_result_signature_secret);
|
||||
KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED);
|
||||
|
||||
setSignatureLayoutVisibility(View.VISIBLE);
|
||||
setShowAction(signatureKeyId);
|
||||
|
||||
mErrorOverlayLayout.setVisibility(View.GONE);
|
||||
mContentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
onVerifyLoaded(true);
|
||||
|
||||
} else if (isYours) {
|
||||
@@ -322,6 +348,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
setSignatureLayoutVisibility(View.VISIBLE);
|
||||
setShowAction(signatureKeyId);
|
||||
|
||||
mErrorOverlayLayout.setVisibility(View.GONE);
|
||||
mContentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
onVerifyLoaded(true);
|
||||
|
||||
} else {
|
||||
@@ -331,6 +360,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
setSignatureLayoutVisibility(View.VISIBLE);
|
||||
setShowAction(signatureKeyId);
|
||||
|
||||
mErrorOverlayLayout.setVisibility(View.GONE);
|
||||
mContentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
onVerifyLoaded(true);
|
||||
}
|
||||
|
||||
@@ -344,7 +376,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
}
|
||||
|
||||
setSignatureLayoutVisibility(View.GONE);
|
||||
|
||||
}
|
||||
|
||||
private void showUnknownKeyStatus() {
|
||||
@@ -388,6 +419,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
}
|
||||
});
|
||||
|
||||
mErrorOverlayLayout.setVisibility(View.GONE);
|
||||
mContentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
onVerifyLoaded(true);
|
||||
|
||||
break;
|
||||
@@ -399,6 +433,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
|
||||
setSignatureLayoutVisibility(View.GONE);
|
||||
|
||||
mErrorOverlayLayout.setVisibility(View.VISIBLE);
|
||||
mContentLayout.setVisibility(View.GONE);
|
||||
|
||||
onVerifyLoaded(false);
|
||||
break;
|
||||
}
|
||||
@@ -407,6 +444,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
||||
|
||||
}
|
||||
|
||||
protected abstract void onVerifyLoaded(boolean verified);
|
||||
protected abstract void onVerifyLoaded(boolean hideErrorOverlay);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -23,16 +23,16 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
@@ -138,8 +138,6 @@ public class DecryptTextActivity extends BaseActivity {
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActions(Bundle savedInstanceState, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
@@ -162,10 +160,14 @@ public class DecryptTextActivity extends BaseActivity {
|
||||
if (sharedText != null) {
|
||||
loadFragment(savedInstanceState, sharedText);
|
||||
} else {
|
||||
Notify.create(this, R.string.error_invalid_data, Notify.Style.ERROR).show();
|
||||
Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!");
|
||||
Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "ACTION_SEND received non-plaintext, this should not happen in this activity!");
|
||||
Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
} else if (ACTION_DECRYPT_TEXT.equals(action)) {
|
||||
Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT");
|
||||
@@ -176,7 +178,9 @@ public class DecryptTextActivity extends BaseActivity {
|
||||
if (extraText != null) {
|
||||
loadFragment(savedInstanceState, extraText);
|
||||
} else {
|
||||
Notify.create(this, R.string.error_invalid_data, Notify.Style.ERROR).show();
|
||||
Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!");
|
||||
Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
} else if (ACTION_DECRYPT_FROM_CLIPBOARD.equals(action)) {
|
||||
Log.d(Constants.TAG, "ACTION_DECRYPT_FROM_CLIPBOARD");
|
||||
@@ -191,6 +195,7 @@ public class DecryptTextActivity extends BaseActivity {
|
||||
}
|
||||
} else if (ACTION_DECRYPT_TEXT.equals(action)) {
|
||||
Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
|
||||
Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,6 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
public static final String ARG_CIPHERTEXT = "ciphertext";
|
||||
|
||||
// view
|
||||
private LinearLayout mValidLayout;
|
||||
private LinearLayout mInvalidLayout;
|
||||
private TextView mText;
|
||||
|
||||
// model
|
||||
@@ -78,19 +76,8 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false);
|
||||
mValidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_valid);
|
||||
mInvalidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_invalid);
|
||||
mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext);
|
||||
|
||||
Button vInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button);
|
||||
vInvalidButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mInvalidLayout.setVisibility(View.GONE);
|
||||
mValidLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -203,7 +190,6 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (pgpResult.success()) {
|
||||
|
||||
byte[] decryptedMessage = returnData
|
||||
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
|
||||
String displayMessage;
|
||||
@@ -219,15 +205,12 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
}
|
||||
mText.setText(displayMessage);
|
||||
|
||||
pgpResult.createNotify(getActivity()).show();
|
||||
|
||||
// display signature result in activity
|
||||
loadVerifyResult(pgpResult);
|
||||
|
||||
} else {
|
||||
pgpResult.createNotify(getActivity()).show();
|
||||
// TODO: show also invalid layout with different text?
|
||||
}
|
||||
pgpResult.createNotify(getActivity()).show(DecryptTextFragment.this);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -244,18 +227,8 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onVerifyLoaded(boolean verified) {
|
||||
|
||||
mShowMenuOptions = verified;
|
||||
protected void onVerifyLoaded(boolean hideErrorOverlay) {
|
||||
mShowMenuOptions = hideErrorOverlay;
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
|
||||
if (verified) {
|
||||
mInvalidLayout.setVisibility(View.GONE);
|
||||
mValidLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mInvalidLayout.setVisibility(View.VISIBLE);
|
||||
mValidLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
@@ -30,16 +30,16 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.service.CloudImportService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -346,60 +346,66 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
||||
mListFragment.loadNew(loaderState);
|
||||
}
|
||||
|
||||
private void handleMessage(Message message) {
|
||||
if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final ImportKeyResult result =
|
||||
returnData.getParcelable(OperationResult.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
Log.e(Constants.TAG, "result == null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())
|
||||
|| ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
|
||||
ImportKeysActivity.this.setResult(RESULT_OK, intent);
|
||||
ImportKeysActivity.this.finish();
|
||||
return;
|
||||
}
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) {
|
||||
ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData);
|
||||
ImportKeysActivity.this.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
result.createNotify(ImportKeysActivity.this)
|
||||
.show((ViewGroup) findViewById(R.id.import_snackbar));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import keys with mImportData
|
||||
*/
|
||||
public void importKeys() {
|
||||
// Message is received after importing is done in CloudImportService
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
true,
|
||||
ProgressDialogFragment.ServiceType.CLOUD_IMPORT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final ImportKeyResult result =
|
||||
returnData.getParcelable(OperationResult.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
Log.e(Constants.TAG, "result == null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())
|
||||
|| ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
|
||||
ImportKeysActivity.this.setResult(RESULT_OK, intent);
|
||||
ImportKeysActivity.this.finish();
|
||||
return;
|
||||
}
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) {
|
||||
ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData);
|
||||
ImportKeysActivity.this.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
result.createNotify(ImportKeysActivity.this)
|
||||
.show((ViewGroup) findViewById(R.id.import_snackbar));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
|
||||
if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
|
||||
Log.d(Constants.TAG, "importKeys started");
|
||||
|
||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
true,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
ImportKeysActivity.this.handleMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Currently not using CloudImport here due to https://github.com/open-keychain/open-keychain/issues/1221
|
||||
// Send all information needed to service to import key in other thread
|
||||
Intent intent = new Intent(this, CloudImportService.class);
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
@@ -417,14 +423,14 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
||||
new ParcelableFileCache<>(this, "key_import.pcl");
|
||||
cache.writeCache(selectedEntries);
|
||||
|
||||
intent.putExtra(CloudImportService.EXTRA_DATA, data);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger);
|
||||
Messenger messenger = new Messenger(serviceHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
serviceHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
@@ -436,6 +442,20 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
||||
} else if (ls instanceof ImportKeysListFragment.CloudLoaderState) {
|
||||
ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls;
|
||||
|
||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
true,
|
||||
ProgressDialogFragment.ServiceType.CLOUD_IMPORT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
ImportKeysActivity.this.handleMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Send all information needed to service to query keys in other thread
|
||||
Intent intent = new Intent(this, CloudImportService.class);
|
||||
|
||||
@@ -460,11 +480,11 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
||||
intent.putExtra(CloudImportService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
Messenger messenger = new Messenger(serviceHandler);
|
||||
intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
serviceHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
|
||||
@@ -37,7 +37,7 @@ import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
|
||||
@@ -70,7 +70,6 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
||||
@@ -64,7 +64,7 @@ public class MainActivity extends AppCompatActivity implements FabContainer {
|
||||
transaction.replace(R.id.main_fragment_container, mainFragment);
|
||||
transaction.commit();
|
||||
|
||||
mToolbar = (Toolbar) findViewById(R.id.activity_main_toolbar);
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
mToolbar.setTitle(R.string.app_name);
|
||||
setSupportActionBar(mToolbar);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
@@ -42,19 +43,24 @@ import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.NfcHelper;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
|
||||
public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
||||
@@ -175,11 +181,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
||||
boolean toClipboard) {
|
||||
try {
|
||||
String content;
|
||||
byte[] fingerprintData = (byte[]) providerHelper.getGenericData(
|
||||
KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
if (fingerprintOnly) {
|
||||
byte[] data = (byte[]) providerHelper.getGenericData(
|
||||
KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(data);
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintData);
|
||||
if (!toClipboard) {
|
||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
} else {
|
||||
@@ -213,13 +219,48 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, content);
|
||||
sendIntent.setType("text/plain");
|
||||
|
||||
String title;
|
||||
if (fingerprintOnly) {
|
||||
title = getResources().getString(R.string.title_share_fingerprint_with);
|
||||
} else {
|
||||
title = getResources().getString(R.string.title_share_key);
|
||||
}
|
||||
startActivity(Intent.createChooser(sendIntent, title));
|
||||
Intent shareChooser = Intent.createChooser(sendIntent, title);
|
||||
|
||||
// Bluetooth Share will convert text/plain sent via EXTRA_TEXT to HTML
|
||||
// Add replacement extra to send a text/plain file instead.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
try {
|
||||
String primaryUserId = UncachedKeyRing.decodeFromData(content.getBytes()).
|
||||
getPublicKey().getPrimaryUserIdWithFallback();
|
||||
|
||||
TemporaryStorageProvider shareFileProv = new TemporaryStorageProvider();
|
||||
Uri contentUri = TemporaryStorageProvider.createFile(getActivity(),
|
||||
primaryUserId + Constants.FILE_EXTENSION_ASC);
|
||||
|
||||
BufferedWriter contentWriter = new BufferedWriter(new OutputStreamWriter(
|
||||
new ParcelFileDescriptor.AutoCloseOutputStream(
|
||||
shareFileProv.openFile(contentUri, "w"))));
|
||||
contentWriter.write(content);
|
||||
contentWriter.close();
|
||||
|
||||
// create replacement extras inside try{}:
|
||||
// if file creation fails, just don't add the replacements
|
||||
Bundle replacements = new Bundle();
|
||||
shareChooser.putExtra(Intent.EXTRA_REPLACEMENT_EXTRAS, replacements);
|
||||
|
||||
Bundle bluetoothExtra = new Bundle(sendIntent.getExtras());
|
||||
replacements.putBundle("com.android.bluetooth", bluetoothExtra);
|
||||
|
||||
bluetoothExtra.putParcelable(Intent.EXTRA_STREAM, contentUri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "error creating temporary Bluetooth key share file!", e);
|
||||
Notify.create(getActivity(), R.string.error_bluetooth_file, Notify.Style.ERROR).show();
|
||||
}
|
||||
}
|
||||
|
||||
startActivity(shareChooser);
|
||||
}
|
||||
} catch (PgpGeneralException | IOException e) {
|
||||
Log.e(Constants.TAG, "error processing key!", e);
|
||||
@@ -379,4 +420,4 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +202,8 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
* In the case of a secret key, "me" (own profile) contact details are loaded.
|
||||
*/
|
||||
private void loadLinkedSystemContact(final long contactId) {
|
||||
// contact doesn't exist, stop
|
||||
if(contactId == -1) return;
|
||||
|
||||
final Context context = mSystemContactName.getContext();
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
@@ -18,15 +18,12 @@
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.format.DateFormat;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -176,9 +173,8 @@ public class KeyAdapter extends CursorAdapter {
|
||||
| DateUtils.FORMAT_SHOW_TIME
|
||||
| DateUtils.FORMAT_SHOW_YEAR
|
||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||
|
||||
mCreationDate.setText(context.getString(R.string.label_creation,
|
||||
dateTime));
|
||||
mCreationDate.setText(context.getString(R.string.label_key_created,
|
||||
dateTime));
|
||||
mCreationDate.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mCreationDate.setVisibility(View.GONE);
|
||||
@@ -281,20 +277,6 @@ public class KeyAdapter extends CursorAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasDuplicate() {
|
||||
return mHasDuplicate;
|
||||
}
|
||||
|
||||
public String getCreationDate(Context context) {
|
||||
Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
creationCal.setTime(mCreation);
|
||||
// convert from UTC to time zone of device
|
||||
creationCal.setTimeZone(TimeZone.getDefault());
|
||||
|
||||
return context.getString(R.string.label_creation) + ": "
|
||||
+ DateFormat.getDateFormat(context).format(creationCal.getTime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -140,8 +140,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter {
|
||||
| DateUtils.FORMAT_SHOW_TIME
|
||||
| DateUtils.FORMAT_SHOW_YEAR
|
||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||
|
||||
h.creation.setText(context.getString(R.string.label_creation, dateTime));
|
||||
h.creation.setText(context.getString(R.string.label_key_created, dateTime));
|
||||
h.creation.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
h.creation.setVisibility(View.GONE);
|
||||
|
||||
@@ -112,9 +112,11 @@ public class AddEmailDialogFragment extends DialogFragment implements OnEditorAc
|
||||
mEmail.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mEmail, InputMethodManager.SHOW_IMPLICIT);
|
||||
if(getActivity() != null) {
|
||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mEmail, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,12 +94,12 @@ public class ProgressDialogFragment extends DialogFragment {
|
||||
|
||||
/** Updates progress of dialog */
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
if (mIsCancelled) {
|
||||
ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
|
||||
if (mIsCancelled || dialog == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
|
||||
dialog.setMessage(message);
|
||||
dialog.setProgress(progress);
|
||||
dialog.setMax(max);
|
||||
|
||||
@@ -216,7 +216,16 @@ public class KeyFormattingUtils {
|
||||
* @return
|
||||
*/
|
||||
public static String convertFingerprintToHex(byte[] fingerprint) {
|
||||
return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH);
|
||||
// NOTE: Even though v3 keys are not imported we need to support both fingerprints for
|
||||
// display/comparison before import
|
||||
// Also better cut of unneeded parts, e.g., for fingerprints returned from YubiKeys
|
||||
if (fingerprint.length < 20) {
|
||||
// v3 key fingerprint with 128 bit (MD5)
|
||||
return Hex.toHexString(fingerprint, 0, 16).toLowerCase(Locale.ENGLISH);
|
||||
} else {
|
||||
// v4 key fingerprint with 160 bit (SHA1)
|
||||
return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
|
||||
public static long getKeyIdFromFingerprint(byte[] fingerprint) {
|
||||
|
||||
@@ -136,7 +136,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
|
||||
where += " AND " + KeyRings.USER_ID + " LIKE ?";
|
||||
|
||||
return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where,
|
||||
new String[] { "%" + query + "%" }, null);
|
||||
new String[]{"%" + query + "%"}, null);
|
||||
}
|
||||
|
||||
mAdapter.setSearchQuery(null);
|
||||
@@ -156,7 +156,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
|
||||
|
||||
@Override
|
||||
public void showDropDown() {
|
||||
if (mAdapter.getCursor().isClosed()) {
|
||||
if (mAdapter == null || mAdapter.getCursor() == null || mAdapter.getCursor().isClosed()) {
|
||||
return;
|
||||
}
|
||||
super.showDropDown();
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.support.v7.widget.AppCompatSpinner;
|
||||
import android.text.format.DateFormat;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -42,10 +42,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
|
||||
* Related: http://stackoverflow.com/a/27713090
|
||||
@@ -164,14 +160,14 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag
|
||||
|
||||
boolean duplicate = cursor.getLong(mIndexDuplicate) > 0;
|
||||
if (duplicate) {
|
||||
Date creationDate = new Date(cursor.getLong(mIndexCreationDate) * 1000);
|
||||
Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
creationCal.setTime(creationDate);
|
||||
// convert from UTC to time zone of device
|
||||
creationCal.setTimeZone(TimeZone.getDefault());
|
||||
String dateTime = DateUtils.formatDateTime(context,
|
||||
cursor.getLong(mIndexCreationDate) * 1000,
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
| DateUtils.FORMAT_SHOW_TIME
|
||||
| DateUtils.FORMAT_SHOW_YEAR
|
||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||
|
||||
vDuplicate.setText(context.getString(R.string.label_creation) + ": "
|
||||
+ DateFormat.getDateFormat(context).format(creationCal.getTime()));
|
||||
vDuplicate.setText(context.getString(R.string.label_key_created, dateTime));
|
||||
vDuplicate.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
vDuplicate.setVisibility(View.GONE);
|
||||
|
||||
@@ -446,6 +446,13 @@ public class ContactHelper {
|
||||
|
||||
writeKeysToMainProfileContact(context, resolver);
|
||||
|
||||
writeKeysToNormalContacts(context, resolver);
|
||||
}
|
||||
|
||||
private static void writeKeysToNormalContacts(Context context, ContentResolver resolver) {
|
||||
// delete raw contacts flagged for deletion by user so they can be reinserted
|
||||
deleteFlaggedNormalRawContacts(resolver);
|
||||
|
||||
Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver);
|
||||
|
||||
// Load all public Keys from OK
|
||||
@@ -519,6 +526,9 @@ public class ContactHelper {
|
||||
* @param context
|
||||
*/
|
||||
public static void writeKeysToMainProfileContact(Context context, ContentResolver resolver) {
|
||||
// deletes contacts hidden by the user so they can be reinserted if necessary
|
||||
deleteFlaggedMainProfileRawContacts(resolver);
|
||||
|
||||
Set<Long> keysToDelete = getMainProfileMasterKeyIds(resolver);
|
||||
|
||||
// get all keys which have associated secret keys
|
||||
@@ -585,7 +595,7 @@ public class ContactHelper {
|
||||
*
|
||||
* @param resolver
|
||||
* @param masterKeyId
|
||||
* @return
|
||||
* @return number of rows deleted
|
||||
*/
|
||||
private static int deleteMainProfileRawContactByMasterKeyId(ContentResolver resolver,
|
||||
long masterKeyId) {
|
||||
@@ -602,6 +612,28 @@ public class ContactHelper {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes all raw contact entries in the "me" contact flagged for deletion ('hidden'),
|
||||
* presumably by the user
|
||||
*
|
||||
* @param resolver
|
||||
* @return number of raw contacts deleted
|
||||
*/
|
||||
private static int deleteFlaggedMainProfileRawContacts(ContentResolver resolver) {
|
||||
// CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise
|
||||
// would be just flagged for deletion
|
||||
Uri deleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon().
|
||||
appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
|
||||
|
||||
return resolver.delete(deleteUri,
|
||||
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
|
||||
ContactsContract.RawContacts.DELETED + "=?",
|
||||
new String[]{
|
||||
Constants.ACCOUNT_TYPE,
|
||||
"1"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all raw contacts associated to OpenKeychain, including those from "me" contact
|
||||
* defined by ContactsContract.Profile
|
||||
@@ -677,6 +709,21 @@ public class ContactHelper {
|
||||
});
|
||||
}
|
||||
|
||||
private static int deleteFlaggedNormalRawContacts(ContentResolver resolver) {
|
||||
// CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise
|
||||
// would be just flagged for deletion
|
||||
Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().
|
||||
appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
|
||||
|
||||
return resolver.delete(deleteUri,
|
||||
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
|
||||
ContactsContract.RawContacts.DELETED + "=?",
|
||||
new String[]{
|
||||
Constants.ACCOUNT_TYPE,
|
||||
"1"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a set of all key master key ids currently present in the contact db
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user