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:
Vincent Breitmoser
2015-05-11 17:28:34 +02:00
104 changed files with 1597 additions and 1339 deletions

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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()) {

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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 =

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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()));
}

View File

@@ -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)");
}
}

View File

@@ -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";

View File

@@ -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();
}
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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!");

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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
}
}
}

View File

@@ -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();

View File

@@ -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());
}
}
}

View File

@@ -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);

View File

@@ -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);
}
}
});
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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);

View File

@@ -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
*/