diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
36 files changed, 677 insertions, 190 deletions
| diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java index e796bdc91..40dcbd78d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-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.operations;  import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 2e9551826..ebf0dc70b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-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.operations;  import android.content.Context; @@ -20,6 +37,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;  import org.sufficientlysecure.keychain.service.CertifyActionsParcel;  import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;  import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;  import org.sufficientlysecure.keychain.util.Log; @@ -191,6 +209,9 @@ public class CertifyOperation extends BaseOperation {          }          log.add(LogType.MSG_CRT_SUCCESS, 0); +        //since only verified keys are synced to contacts, we need to initiate a sync now +        ContactSyncAdapterService.requestSync(); +                  return new CertifyResult(CertifyResult.RESULT_OK, log, certifyOk, certifyError, uploadOk, uploadError);      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java index 124dd1aaf..5ef04ab05 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-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.operations;  import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index 7f14b08d9..bcd842dd0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-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.operations;  import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java index 783b32477..fd86d4b92 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-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.operations;  import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java index e98f6b150..b5552a40d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014-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.operations;  import android.content.Context; @@ -48,6 +65,7 @@ public class SignEncryptOperation extends BaseOperation {          byte[] inputBytes = input.getBytes();          byte[] outputBytes = null; +        int total = inputBytes != null ? 1 : inputUris.size(), count = 0;          ArrayList<PgpSignEncryptResult> results = new ArrayList<>();          do { @@ -104,7 +122,7 @@ public class SignEncryptOperation extends BaseOperation {              }              PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper, -                    new ProgressScaler(), mCancelled); +                    new ProgressScaler(mProgressable, 100 * count / total, 100 * ++count / total, 100), mCancelled);              PgpSignEncryptResult result = op.execute(input, inputData, outStream);              results.add(result);              log.add(result, 2); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 5856589c4..6af5f4217 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -123,6 +123,7 @@ public class KeychainContract {          public static final String HAS_SIGN = "has_sign";          public static final String HAS_CERTIFY = "has_certify";          public static final String HAS_AUTHENTICATE = "has_authenticate"; +        public static final String HAS_DUPLICATE_USER_ID = "has_duplicate_user_id";          public static final String PUBKEY_DATA = "pubkey_data";          public static final String PRIVKEY_DATA = "privkey_data"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 4ccfc3cd9..1351e0cbc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -273,7 +273,15 @@ public class KeychainProvider extends ContentProvider {                  projectionMap.put(KeyRings.EXPIRY, Tables.KEYS + "." + Keys.EXPIRY);                  projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM);                  projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT); -                projectionMap.put(KeyRings.USER_ID, UserPackets.USER_ID); +                projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); +                projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID, +                        "(SELECT COUNT (*) FROM " + Tables.USER_PACKETS + " AS dups" +                                + " WHERE dups." + UserPackets.MASTER_KEY_ID +                                    + " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID +                                + " AND dups." + UserPackets.RANK + " = 0" +                                + " AND dups." + UserPackets.USER_ID +                                    + " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID +                                + ") AS " + KeyRings.HAS_DUPLICATE_USER_ID);                  projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED);                  projectionMap.put(KeyRings.PUBKEY_DATA,                          Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java index 940ae27d0..4bb64bcaa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java @@ -28,8 +28,8 @@ import android.widget.TextView;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult;  import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;  import org.sufficientlysecure.keychain.pgp.KeyRing;  import org.sufficientlysecure.keychain.remote.AccountSettings;  import org.sufficientlysecure.keychain.ui.CreateKeyActivity; @@ -106,8 +106,8 @@ public class AccountSettingsFragment extends Fragment {              case REQUEST_CODE_CREATE_KEY: {                  if (resultCode == Activity.RESULT_OK) {                      if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { -                        SaveKeyringResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); -                        mSelectKeySpinner.setSelectedKeyId(result.mRingMasterKeyId); +                        EditKeyResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); +                        mSelectKeySpinner.setSelectedKeyId(result.mMasterKeyId);                      } else {                          Log.e(Constants.TAG, "missing result!");                      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java index 8578bb384..5ec47f4c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java @@ -127,9 +127,9 @@ public class SelectSignKeyIdActivity extends BaseActivity {              case REQUEST_CODE_CREATE_KEY: {                  if (resultCode == Activity.RESULT_OK) {                      if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { -//                        SaveKeyringResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);                          // TODO: select? -//                            mSelectKeySpinner.setSelectedKeyId(result.mRingMasterKeyId); +//                        EditKeyResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); +//                        mSelectKeySpinner.setSelectedKeyId(result.mMasterKeyId);                      } else {                          Log.e(Constants.TAG, "missing result!");                      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index d95701458..bf6a62782 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -60,6 +60,7 @@ import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;  import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;  import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;  import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus;  import org.sufficientlysecure.keychain.util.FileHelper;  import org.sufficientlysecure.keychain.util.InputData;  import org.sufficientlysecure.keychain.util.Log; @@ -126,8 +127,21 @@ public class KeychainIntentService extends IntentService implements Progressable      public static final String SOURCE = "source";      // possible targets: -    public static final int IO_BYTES = 1; -    public static final int IO_URI = 2; +    public static enum IOType { +        UNKNOWN, +        BYTES, +        URI; + +        private static final IOType[] values = values(); + +        public static IOType fromInt(int n) { +            if(n < 0 || n >= values.length) { +                return UNKNOWN; +            } else { +                return values[n]; +            } +        } +    }      // encrypt      public static final String ENCRYPT_DECRYPT_INPUT_URI = "input_uri"; @@ -244,7 +258,7 @@ public class KeychainIntentService extends IntentService implements Progressable                  CertifyResult result = op.certify(parcel, keyServerUri);                  // Result -                sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); +                sendMessageToHandler(MessageStatus.OKAY, result);                  break;              } @@ -259,7 +273,7 @@ public class KeychainIntentService extends IntentService implements Progressable                  }                  // Result -                sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); +                sendMessageToHandler(MessageStatus.OKAY, result);                  break;              } @@ -288,7 +302,7 @@ public class KeychainIntentService extends IntentService implements Progressable                      DecryptVerifyResult decryptVerifyResult = builder.build().execute(); -                    sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, decryptVerifyResult); +                    sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);                  } catch (Exception e) {                      sendErrorToHandler(e);                  } @@ -349,7 +363,7 @@ public class KeychainIntentService extends IntentService implements Progressable                      }                      // kind of awkward, but this whole class wants to pull bytes out of “data” -                    data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); +                    data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());                      data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes);                      InputData inputData = createDecryptInputData(data); @@ -386,7 +400,7 @@ public class KeychainIntentService extends IntentService implements Progressable                      resultData.putString(KeychainIntentServiceHandler.KEYBASE_PROOF_URL, prover.getProofUrl());                      resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());                      resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel()); -                    sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); +                    sendMessageToHandler(MessageStatus.OKAY, resultData);                  } catch (Exception e) {                      sendErrorToHandler(e);                  } @@ -429,7 +443,7 @@ public class KeychainIntentService extends IntentService implements Progressable                      Log.logDebugBundle(resultData, "resultData"); -                    sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); +                    sendMessageToHandler(MessageStatus.OKAY, resultData);                  } catch (Exception e) {                      sendErrorToHandler(e);                  } @@ -447,7 +461,7 @@ public class KeychainIntentService extends IntentService implements Progressable                  DeleteResult result = op.execute(masterKeyIds, isSecret);                  // Result -                sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); +                sendMessageToHandler(MessageStatus.OKAY, result);                  break;              } @@ -462,7 +476,7 @@ public class KeychainIntentService extends IntentService implements Progressable                  EditKeyResult result = op.execute(saveParcel, passphrase);                  // Result -                sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); +                sendMessageToHandler(MessageStatus.OKAY, result);                  break;              } @@ -476,7 +490,7 @@ public class KeychainIntentService extends IntentService implements Progressable                  PromoteKeyResult result = op.execute(keyRingId);                  // Result -                sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); +                sendMessageToHandler(MessageStatus.OKAY, result);                  break;              } @@ -500,7 +514,7 @@ public class KeychainIntentService extends IntentService implements Progressable                  }                  // Result -                sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); +                sendMessageToHandler(MessageStatus.OKAY, result);                  break;              } @@ -521,7 +535,7 @@ public class KeychainIntentService extends IntentService implements Progressable                          : importExportOperation.importKeyRings(cache, keyServer);                  // Result -                sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); +                sendMessageToHandler(MessageStatus.OKAY, result);                  break; @@ -537,7 +551,7 @@ public class KeychainIntentService extends IntentService implements Progressable                  SignEncryptResult result = op.execute(inputParcel);                  // Result -                sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); +                sendMessageToHandler(MessageStatus.OKAY, result);                  break;              } @@ -560,7 +574,7 @@ public class KeychainIntentService extends IntentService implements Progressable                          throw new PgpGeneralException("Unable to export key to selected server");                      } -                    sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); +                    sendMessageToHandler(MessageStatus.OKAY);                  } catch (Exception e) {                      sendErrorToHandler(e);                  } @@ -582,7 +596,7 @@ public class KeychainIntentService extends IntentService implements Progressable      private void sendProofError(String msg) {          Bundle bundle = new Bundle();          bundle.putString(KeychainIntentServiceHandler.DATA_ERROR, msg); -        sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, bundle); +        sendMessageToHandler(MessageStatus.OKAY, bundle);      }      private void sendErrorToHandler(Exception e) { @@ -599,14 +613,14 @@ public class KeychainIntentService extends IntentService implements Progressable          Bundle data = new Bundle();          data.putString(KeychainIntentServiceHandler.DATA_ERROR, message); -        sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_EXCEPTION, null, data); +        sendMessageToHandler(MessageStatus.EXCEPTION, null, data);      } -    private void sendMessageToHandler(Integer arg1, Integer arg2, Bundle data) { +    private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {          Message msg = Message.obtain();          assert msg != null; -        msg.arg1 = arg1; +        msg.arg1 = status.ordinal();          if (arg2 != null) {              msg.arg2 = arg2;          } @@ -623,18 +637,18 @@ public class KeychainIntentService extends IntentService implements Progressable          }      } -    private void sendMessageToHandler(Integer arg1, OperationResult data) { +    private void sendMessageToHandler(MessageStatus status, OperationResult data) {          Bundle bundle = new Bundle();          bundle.putParcelable(OperationResult.EXTRA_RESULT, data); -        sendMessageToHandler(arg1, null, bundle); +        sendMessageToHandler(status, null, bundle);      } -    private void sendMessageToHandler(Integer arg1, Bundle data) { -        sendMessageToHandler(arg1, null, data); +    private void sendMessageToHandler(MessageStatus status, Bundle data) { +        sendMessageToHandler(status, null, data);      } -    private void sendMessageToHandler(Integer arg1) { -        sendMessageToHandler(arg1, null, null); +    private void sendMessageToHandler(MessageStatus status) { +        sendMessageToHandler(status, null, null);      }      /** @@ -651,7 +665,7 @@ public class KeychainIntentService extends IntentService implements Progressable          data.putInt(KeychainIntentServiceHandler.DATA_PROGRESS, progress);          data.putInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX, max); -        sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS, null, data); +        sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);      }      public void setProgress(int resourceId, int progress, int max) { @@ -664,7 +678,7 @@ public class KeychainIntentService extends IntentService implements Progressable      @Override      public void setPreventCancel() { -        sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_PREVENT_CANCEL); +        sendMessageToHandler(MessageStatus.PREVENT_CANCEL);      }      private InputData createDecryptInputData(Bundle data) throws IOException, PgpGeneralException { @@ -673,35 +687,37 @@ public class KeychainIntentService extends IntentService implements Progressable      private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException {          int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); -        switch (source) { -            case IO_BYTES: /* encrypting bytes directly */ +        IOType type = IOType.fromInt(source); +        switch (type) { +            case BYTES: /* encrypting bytes directly */                  byte[] bytes = data.getByteArray(bytesName);                  return new InputData(new ByteArrayInputStream(bytes), bytes.length); -            case IO_URI: /* encrypting content uri */ +            case URI: /* encrypting content uri */                  Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_INPUT_URI);                  // InputStream                  return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0));              default: -                throw new PgpGeneralException("No target choosen!"); +                throw new PgpGeneralException("No target chosen!");          }      }      private OutputStream createCryptOutputStream(Bundle data) throws PgpGeneralException, FileNotFoundException {          int target = data.getInt(TARGET); -        switch (target) { -            case IO_BYTES: +        IOType type = IOType.fromInt(target); +        switch (type) { +            case BYTES:                  return new ByteArrayOutputStream(); -            case IO_URI: +            case URI:                  Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_OUTPUT_URI);                  return getContentResolver().openOutputStream(providerUri);              default: -                throw new PgpGeneralException("No target choosen!"); +                throw new PgpGeneralException("No target chosen!");          }      } @@ -711,12 +727,13 @@ public class KeychainIntentService extends IntentService implements Progressable      private void finalizeCryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream, String bytesName) {          int target = data.getInt(TARGET); -        switch (target) { -            case IO_BYTES: +        IOType type = IOType.fromInt(target); +        switch (type) { +            case BYTES:                  byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();                  resultData.putByteArray(bytesName, output);                  break; -            case IO_URI: +            case URI:                  // nothing, output was written, just send okay and verification bundle                  break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java index 635fe86f1..bd047518d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java @@ -32,11 +32,25 @@ import org.sufficientlysecure.keychain.util.Log;  public class KeychainIntentServiceHandler extends Handler { -    // possible messages send from this service to handler on ui -    public static final int MESSAGE_OKAY = 1; -    public static final int MESSAGE_EXCEPTION = 2; -    public static final int MESSAGE_UPDATE_PROGRESS = 3; -    public static final int MESSAGE_PREVENT_CANCEL = 4; +    // possible messages sent from this service to handler on ui +    public static enum MessageStatus{ +        UNKNOWN, +        OKAY, +        EXCEPTION, +        UPDATE_PROGRESS, +        PREVENT_CANCEL; + +        private static final MessageStatus[] values = values(); + +        public static MessageStatus fromInt(int n) +        { +            if(n < 0 || n >= values.length) { +                return UNKNOWN; +            } else { +                return values[n]; +            } +        } +    }      // possible data keys for messages      public static final String DATA_ERROR = "error"; @@ -103,13 +117,14 @@ public class KeychainIntentServiceHandler extends Handler {              return;          } -        switch (message.arg1) { -            case MESSAGE_OKAY: +        MessageStatus status = MessageStatus.fromInt(message.arg1); +        switch (status) { +            case OKAY:                  mProgressDialogFragment.dismissAllowingStateLoss();                  break; -            case MESSAGE_EXCEPTION: +            case EXCEPTION:                  mProgressDialogFragment.dismissAllowingStateLoss();                  // show error from service @@ -121,7 +136,7 @@ public class KeychainIntentServiceHandler extends Handler {                  break; -            case MESSAGE_UPDATE_PROGRESS: +            case UPDATE_PROGRESS:                  if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) {                      // update progress from service @@ -139,7 +154,7 @@ public class KeychainIntentServiceHandler extends Handler {                  break; -            case MESSAGE_PREVENT_CANCEL: +            case PREVENT_CANCEL:                  mProgressDialogFragment.setPreventCancel(true);                  break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index e1467ec61..1e1bd32c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -395,7 +395,7 @@ public class CertifyKeyFragment extends LoaderFragment                      // handle messages by standard KeychainIntentServiceHandler first                      super.handleMessage(message); -                    if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                    if (message.arg1 == MessageStatus.OKAY.ordinal()) {                          Bundle data = message.getData();                          CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java index f0ef8b9ef..c55aad1f9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java @@ -57,7 +57,7 @@ public class ConsolidateDialogActivity extends FragmentActivity {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      /* don't care about the results (for now?)                      // get returned data bundle diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index 4853c61c5..ae42c891d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -214,7 +214,7 @@ public class CreateKeyFinalFragment extends Fragment {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData();                      if (returnData == null) { @@ -284,7 +284,7 @@ public class CreateKeyFinalFragment extends Fragment {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // TODO: upload operation needs a result!                      // TODO: then combine these results                      //if (result.getResult() == OperationResultParcel.RESULT_OK) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java index 5606523be..c808557a6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;  import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;  import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;  import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;  import org.sufficientlysecure.keychain.ui.util.Notify; @@ -182,10 +183,10 @@ public class DecryptFilesFragment extends DecryptFragment {          // data          Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); -        data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); +        data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());          data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri); -        data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); +        data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());          data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);          data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase); @@ -200,7 +201,7 @@ public class DecryptFilesFragment extends DecryptFragment {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData(); @@ -256,10 +257,10 @@ public class DecryptFilesFragment extends DecryptFragment {          // data          Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); -        data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); +        data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());          data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri); -        data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); +        data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());          data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);          data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase); @@ -274,7 +275,7 @@ public class DecryptFilesFragment extends DecryptFragment {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java index a15b23c06..1b34f6bf0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;  import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;  import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;  import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;  import org.sufficientlysecure.keychain.ui.util.Notify;  import org.sufficientlysecure.keychain.util.Log; @@ -158,7 +159,7 @@ public class DecryptTextFragment extends DecryptFragment {          intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);          // data -        data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); +        data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());          data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());          data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);          data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey); @@ -172,7 +173,7 @@ public class DecryptTextFragment extends DecryptFragment {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 1e9a90fc8..8b9323f19 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -605,7 +605,7 @@ public class EditKeyFragment extends LoaderFragment implements                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index ed5920cde..35dfcb87c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -128,7 +128,7 @@ public abstract class EncryptActivity extends BaseActivity {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      SignEncryptResult result =                              message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index d95b5cda3..b862d5b11 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -36,7 +36,6 @@ import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;  import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;  import org.sufficientlysecure.keychain.ui.util.Notify;  import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Preferences;  import org.sufficientlysecure.keychain.util.ShareHelper;  import java.util.ArrayList; @@ -177,22 +176,36 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi      }      @Override -    public void onEncryptSuccess(SignEncryptResult result) { +    public void onEncryptSuccess(final SignEncryptResult result) {          if (mDeleteAfterEncrypt) { -            for (Uri inputUri : mInputUris) { -                DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUri); -                deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); -            } +            final Uri[] inputUris = mInputUris.toArray(new Uri[mInputUris.size()]); +            DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUris); +            deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() { + +                @Override +                public void onDeleted() { +                    if (mShareAfterEncrypt) { +                        // Share encrypted message/file +                        startActivity(sendWithChooserExcludingEncrypt()); +                    } else { +                        // Save encrypted file +                        result.createNotify(EncryptFilesActivity.this).show(); +                    } +                } + +            }); +            deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); +              mInputUris.clear();              notifyUpdate(); -        } - -        if (mShareAfterEncrypt) { -            // Share encrypted message/file -            startActivity(sendWithChooserExcludingEncrypt());          } else { -            // Save encrypted file -            result.createNotify(EncryptFilesActivity.this).show(); +            if (mShareAfterEncrypt) { +                // Share encrypted message/file +                startActivity(sendWithChooserExcludingEncrypt()); +            } else { +                // Save encrypted file +                result.createNotify(EncryptFilesActivity.this).show(); +            }          }      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index ace58b165..48737d223 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -114,6 +114,13 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt              return;          } +        if (mEncryptInterface.getInputUris().contains(inputUri)) { +            Notify.showNotify(getActivity(), +                    getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)), +                    Notify.Style.ERROR); +            return; +        } +          mEncryptInterface.getInputUris().add(inputUri);          mEncryptInterface.notifyUpdate();          mSelectedFiles.requestFocus(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 71f6fd4bf..d51e2c7fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -302,7 +302,7 @@ public class ImportKeysActivity extends BaseActivity {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData();                      if (returnData == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java index 948da94d8..cc8b47971 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -222,7 +222,7 @@ public class ImportKeysProxyActivity extends FragmentActivity {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData();                      if (returnData == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 43d893fa6..8c34efba2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -270,7 +270,8 @@ public class KeyListFragment extends LoaderFragment              KeyRings.IS_REVOKED,              KeyRings.IS_EXPIRED,              KeyRings.VERIFIED, -            KeyRings.HAS_ANY_SECRET +            KeyRings.HAS_ANY_SECRET, +            KeyRings.HAS_DUPLICATE_USER_ID,      };      static final int INDEX_MASTER_KEY_ID = 1; @@ -279,6 +280,7 @@ public class KeyListFragment extends LoaderFragment      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_HAS_DUPLICATE_USER_ID = 7;      static final String ORDER =              KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC"; @@ -552,7 +554,7 @@ public class KeyListFragment extends LoaderFragment                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData();                      if (returnData == null) { @@ -707,6 +709,7 @@ public class KeyListFragment extends LoaderFragment                  boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;                  boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0;                  boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0; +                boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) == 1;                  h.mMasterKeyId = masterKeyId; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index d1df2906d..d0cea5f05 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -132,7 +132,7 @@ public class SafeSlingerActivity extends BaseActivity {                      // handle messages by standard KeychainIntentServiceHandler first                      super.handleMessage(message); -                    if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                    if (message.arg1 == MessageStatus.OKAY.ordinal()) {                          // get returned data bundle                          Bundle returnData = message.getData();                          if (returnData == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index e19793fd5..4fb2074a5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -113,7 +113,7 @@ public class UploadKeyActivity extends BaseActivity {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success,                              Toast.LENGTH_SHORT).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index c94b29bac..b25f6bbf2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -25,6 +25,7 @@ import android.annotation.TargetApi;  import android.app.Activity;  import android.app.ActivityOptions;  import android.content.Intent; +import android.content.pm.PackageManager;  import android.database.Cursor;  import android.graphics.Bitmap;  import android.net.Uri; @@ -72,6 +73,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;  import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.service.KeychainIntentService;  import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus;  import org.sufficientlysecure.keychain.ui.util.FormattingUtils;  import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;  import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -373,6 +375,11 @@ public class ViewKeyActivity extends BaseActivity implements      @TargetApi(Build.VERSION_CODES.LOLLIPOP)      private void invokeNfcBeam() { +        // Check if device supports NFC +        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) { +            Notify.createNotify(this, R.string.no_nfc_support, Notify.LENGTH_LONG, Notify.Style.ERROR).show(); +            return; +        }          // Check for available NFC Adapter          mNfcAdapter = NfcAdapter.getDefaultAdapter(this);          if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) { @@ -429,7 +436,7 @@ public class ViewKeyActivity extends BaseActivity implements                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      Bundle data = message.getData();                      CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); @@ -486,7 +493,7 @@ public class ViewKeyActivity extends BaseActivity implements          Handler returnHandler = new Handler() {              @Override              public void handleMessage(Message message) { -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      setResult(RESULT_CANCELED);                      finish();                  } @@ -595,7 +602,7 @@ public class ViewKeyActivity extends BaseActivity implements                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index bb3df2541..c3a8d60f8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -44,6 +44,8 @@ import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;  import org.sufficientlysecure.keychain.util.ContactHelper;  import org.sufficientlysecure.keychain.util.Log; +import java.util.List; +  public class ViewKeyFragment extends LoaderFragment implements          LoaderManager.LoaderCallbacks<Cursor> { @@ -53,7 +55,7 @@ public class ViewKeyFragment extends LoaderFragment implements      //private ListView mLinkedSystemContact;      boolean mIsSecret = false; -    private String mName; +    boolean mSystemContactLoaded = false;      LinearLayout mSystemContactLayout;      ImageView mSystemContactPicture; @@ -119,28 +121,48 @@ public class ViewKeyFragment extends LoaderFragment implements      /**       * Checks if a system contact exists for given masterKeyId, and if it does, sets name, picture       * and onClickListener for the linked system contact's layout +     * In the case of a secret key, "me" contact details are loaded       * -     * @param name       * @param masterKeyId       */ -    private void loadLinkedSystemContact(String name, final long masterKeyId) { +    private void loadLinkedSystemContact(final long masterKeyId) {          final Context context = mSystemContactName.getContext();          final ContentResolver resolver = context.getContentResolver(); -        final long contactId = ContactHelper.findContactId(resolver, masterKeyId); +        long contactId; +        String contactName = null; + +        if (mIsSecret) {//all secret keys are linked to "me" profile in contacts +            contactId = ContactHelper.getMainProfileContactId(resolver); +            List<String> mainProfileNames = ContactHelper.getMainProfileContactName(context); +            if (mainProfileNames != null && mainProfileNames.size() > 0) { +                contactName = mainProfileNames.get(0); +            } -        if (contactId != -1) {//contact exists for given master key -            mSystemContactName.setText(name); +        } else { +            contactId = ContactHelper.findContactId(resolver, masterKeyId); +            contactName = ContactHelper.getContactName(resolver, contactId); +        } -            Bitmap picture = ContactHelper.loadPhotoByMasterKeyId(resolver, masterKeyId, true); +        if (contactName != null) {//contact name exists for given master key +            mSystemContactName.setText(contactName); + +            Bitmap picture; +            if (mIsSecret) { +                picture = ContactHelper.loadMainProfilePhoto(resolver, false); +            } else { +                picture = ContactHelper.loadPhotoByMasterKeyId(resolver, masterKeyId, false); +            }              if (picture != null) mSystemContactPicture.setImageBitmap(picture); +            final long finalContactId = contactId;              mSystemContactLayout.setOnClickListener(new View.OnClickListener() {                  @Override                  public void onClick(View v) { -                    launchContactActivity(contactId, context); +                    launchContactActivity(finalContactId, context);                  }              }); +            mSystemContactLoaded = true;          }      } @@ -240,11 +262,11 @@ public class ViewKeyFragment extends LoaderFragment implements                  if (data.moveToFirst()) {                      mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; -                    if (mName == null) {//to ensure we load the linked system contact only once -                        String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); -                        mName = mainUserId[0]; + +                    //TODO system to allow immediate refreshing of system contact on verification +                    if (!mSystemContactLoaded) {//ensure we load linked system contact only once                          long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); -                        loadLinkedSystemContact(mName, masterKeyId); +                        loadLinkedSystemContact(masterKeyId);                      }                      // load user ids after we know if it's a secret key                      mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java index d22f01a48..e20796f8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java @@ -368,7 +368,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      Bundle returnData = message.getData();                      String msg = returnData.getString(KeychainIntentServiceHandler.DATA_MESSAGE);                      SpannableStringBuilder ssb = new SpannableStringBuilder(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index c4b437593..bd4e5577b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -18,7 +18,6 @@  package org.sufficientlysecure.keychain.ui.dialog;  import android.app.Dialog; -import android.content.ContentResolver;  import android.content.DialogInterface;  import android.net.Uri;  import android.os.Build; @@ -34,18 +33,22 @@ import org.sufficientlysecure.keychain.util.FileHelper;  import org.sufficientlysecure.keychain.util.Log;  import java.io.File; +import java.util.ArrayList; +import java.util.HashMap;  public class DeleteFileDialogFragment extends DialogFragment { -    private static final String ARG_DELETE_URI = "delete_uri"; +    private static final String ARG_DELETE_URIS = "delete_uris"; + +    private OnDeletedListener onDeletedListener;      /**       * Creates new instance of this delete file dialog fragment       */ -    public static DeleteFileDialogFragment newInstance(Uri deleteUri) { +    public static DeleteFileDialogFragment newInstance(Uri... deleteUris) {          DeleteFileDialogFragment frag = new DeleteFileDialogFragment();          Bundle args = new Bundle(); -        args.putParcelable(ARG_DELETE_URI, deleteUri); +        args.putParcelableArray(ARG_DELETE_URIS, deleteUris);          frag.setArguments(args); @@ -59,12 +62,21 @@ public class DeleteFileDialogFragment extends DialogFragment {      public Dialog onCreateDialog(Bundle savedInstanceState) {          final FragmentActivity activity = getActivity(); -        final Uri deleteUri = getArguments().getParcelable(ARG_DELETE_URI); -        final String deleteFilename = FileHelper.getFilename(getActivity(), deleteUri); +        final Uri[] deleteUris = (Uri[]) getArguments().getParcelableArray(ARG_DELETE_URIS); + +        final StringBuilder deleteFileNames = new StringBuilder(); +        //Retrieving file names after deletion gives unexpected results +        final HashMap<Uri, String> deleteFileNameMap = new HashMap<>(); +        for (Uri deleteUri : deleteUris) { +            String deleteFileName = FileHelper.getFilename(getActivity(), deleteUri); +            deleteFileNames.append('\n').append(deleteFileName); +            deleteFileNameMap.put(deleteUri, deleteFileName); +        }          CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); -        alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFilename)); +        alert.setTitle(getString(R.string.file_delete_confirmation_title)); +        alert.setMessage(getString(R.string.file_delete_confirmation, deleteFileNames.toString()));          alert.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { @@ -72,43 +84,55 @@ public class DeleteFileDialogFragment extends DialogFragment {              public void onClick(DialogInterface dialog, int id) {                  dismiss(); -                // NOTE: Use Toasts, not Snackbars. When sharing to another application snackbars -                // would not show up! +                ArrayList<String> failedFileNameList = new ArrayList<>(); + +                for (Uri deleteUri : deleteUris) { +                    // Use DocumentsContract on Android >= 4.4 +                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { +                        try { +                            if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) { +                                continue; +                            } +                        } catch (Exception e) { +                            Log.d(Constants.TAG, "Catched Exception, can happen when delete is not supported!", e); +                        } +                    } -                // Use DocumentsContract on Android >= 4.4 -                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {                      try { -                        if (DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri)) { -                            Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful, -                                    deleteFilename), Toast.LENGTH_LONG).show(); -                            return; +                        if (getActivity().getContentResolver().delete(deleteUri, null, null) > 0) { +                            continue;                          } -                    } catch (UnsupportedOperationException e) { -                        Log.d(Constants.TAG, "Catched UnsupportedOperationException, can happen when delete is not supported!", e); +                    } catch (Exception e) { +                        Log.d(Constants.TAG, "Catched Exception, can happen when delete is not supported!", e);                      } -                } -                try { -                    if (getActivity().getContentResolver().delete(deleteUri, null, null) > 0) { -                        Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful, -                                deleteFilename), Toast.LENGTH_LONG).show(); -                        return; +                    // some Uri's a ContentResolver fails to delete is handled by the java.io.File's delete +                    // via the path of the Uri +                    if (new File(deleteUri.getPath()).delete()) { +                        continue;                      } -                } catch (UnsupportedOperationException e) { -                    Log.d(Constants.TAG, "Catched UnsupportedOperationException, can happen when delete is not supported!", e); + +                    // Note: We can't delete every file... +                    failedFileNameList.add(deleteFileNameMap.get(deleteUri));                  } -                // some Uri's a ContentResolver fails to delete is handled by the java.io.File's delete -                // via the path of the Uri -                if (new File(deleteUri.getPath()).delete()) { -                    Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful, -                            deleteFilename), Toast.LENGTH_LONG).show(); -                    return; +                StringBuilder failedFileNames = new StringBuilder(); +                if (!failedFileNameList.isEmpty()) { +                    for (String failedFileName : failedFileNameList) { +                        failedFileNames.append('\n').append(failedFileName); +                    } +                    failedFileNames.append('\n').append(getActivity().getString(R.string.error_file_delete_failed));                  } -                // Note: We can't delete every file... -                Toast.makeText(getActivity(), getActivity().getString(R.string.error_file_delete_failed, -                        deleteFilename), Toast.LENGTH_LONG).show(); +                // NOTE: Use Toasts, not Snackbars. When sharing to another application snackbars +                // would not show up! +                Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful, +                                deleteUris.length - failedFileNameList.size(), deleteUris.length, failedFileNames.toString()), +                        Toast.LENGTH_LONG).show(); + +                if (onDeletedListener != null) { +                    onDeletedListener.onDeleted(); +                }              }          });          alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @@ -120,4 +144,18 @@ public class DeleteFileDialogFragment extends DialogFragment {          return alert.show();      } + +    public void setOnDeletedListener(OnDeletedListener onDeletedListener) { +        this.onDeletedListener = onDeletedListener; +    } + +    /** +     * Callback for performing tasks after the deletion of files +     */ +    public interface OnDeletedListener { + +        public void onDeleted(); + +    } +  } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 32789d53b..20f20c32e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -142,7 +142,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {                      public void handleMessage(Message message) {                          super.handleMessage(message);                          // handle messages by standard KeychainIntentServiceHandler first -                        if (message.arg1 == MESSAGE_OKAY) { +                        if (message.arg1 == MessageStatus.OKAY.ordinal()) {                              try {                                  Message msg = Message.obtain();                                  msg.copyFrom(message); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 6dd254aa1..fb6b84f58 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -68,7 +68,9 @@ public class CertifyKeySpinner extends KeySpinner {                  KeychainContract.KeyRings.IS_REVOKED,                  KeychainContract.KeyRings.IS_EXPIRED,                  KeychainContract.KeyRings.HAS_CERTIFY, -                KeychainContract.KeyRings.HAS_ANY_SECRET +                KeychainContract.KeyRings.HAS_ANY_SECRET, +                KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID, +                KeychainContract.KeyRings.CREATION          };          String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND " diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index c8eceea50..ab5b02301 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -25,6 +25,7 @@ import android.support.v4.app.LoaderManager;  import android.support.v4.content.Loader;  import android.support.v4.widget.CursorAdapter;  import android.support.v7.internal.widget.TintSpinner; +import android.text.format.DateFormat;  import android.util.AttributeSet;  import android.view.View;  import android.view.ViewGroup; @@ -41,6 +42,10 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;  import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;  import org.sufficientlysecure.keychain.util.Log; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; +  /**   * Use TintSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.   * Related: http://stackoverflow.com/a/27713090 @@ -139,11 +144,12 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo      protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {          private CursorAdapter inner;          private int mIndexUserId; -        private int mIndexKeyId; +        private int mIndexDuplicate;          private int mIndexMasterKeyId; +        private int mIndexCreationDate;          public SelectKeyAdapter() { -            inner = new CursorAdapter(null, null, 0) { +            inner = new CursorAdapter(getContext(), null, 0) {                  @Override                  public View newView(Context context, Cursor cursor, ViewGroup parent) {                      return View.inflate(getContext(), R.layout.keyspinner_item, null); @@ -154,12 +160,26 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo                      TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);                      ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);                      TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); -                    TextView vKeyId = (TextView) view.findViewById(R.id.keyspinner_key_id); +                    TextView vDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);                      String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId));                      vKeyName.setText(userId[2] == null ? userId[0] : (userId[0] + " (" + userId[2] + ")"));                      vKeyEmail.setText(userId[1]); -                    vKeyId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(getContext(), cursor.getLong(mIndexKeyId))); + +                    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()); + +                        vDuplicate.setText(context.getString(R.string.label_creation) + ": " +                                + DateFormat.getDateFormat(context).format(creationCal.getTime())); +                        vDuplicate.setVisibility(View.VISIBLE); +                    } else { +                        vDuplicate.setVisibility(View.GONE); +                    }                      boolean valid = setStatus(getContext(), cursor, vKeyStatus);                      setItemEnabled(view, valid); @@ -181,18 +201,18 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo              TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);              ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);              TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); -            TextView vKeyId = (TextView) view.findViewById(R.id.keyspinner_key_id); +            TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);              if (enabled) {                  vKeyName.setTextColor(Color.BLACK);                  vKeyEmail.setTextColor(Color.BLACK); -                vKeyId.setTextColor(Color.BLACK); +                vKeyDuplicate.setTextColor(Color.BLACK);                  vKeyStatus.setVisibility(View.GONE);                  view.setClickable(false);              } else {                  vKeyName.setTextColor(Color.GRAY);                  vKeyEmail.setTextColor(Color.GRAY); -                vKeyId.setTextColor(Color.GRAY); +                vKeyDuplicate.setTextColor(Color.GRAY);                  vKeyStatus.setVisibility(View.VISIBLE);                  // this is a HACK. the trick is, if the element itself is clickable, the                  // click is not passed on to the view list @@ -203,9 +223,10 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo          public Cursor swapCursor(Cursor newCursor) {              if (newCursor == null) return inner.swapCursor(null); -            mIndexKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.KEY_ID); +            mIndexDuplicate = newCursor.getColumnIndex(KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID);              mIndexUserId = newCursor.getColumnIndex(KeychainContract.KeyRings.USER_ID);              mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID); +            mIndexCreationDate = newCursor.getColumnIndex(KeychainContract.KeyRings.CREATION);              if (newCursor.moveToFirst()) {                  do {                      if (newCursor.getLong(mIndexMasterKeyId) == mSelectedKeyId) { @@ -257,19 +278,17 @@ public abstract class KeySpinner extends TintSpinner implements LoaderManager.Lo                  TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);                  ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);                  TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); -                TextView vKeyId = (TextView) view.findViewById(R.id.keyspinner_key_id); +                TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);                  vKeyName.setText(R.string.choice_none);                  vKeyEmail.setVisibility(View.GONE); -                vKeyId.setVisibility(View.GONE); +                vKeyDuplicate.setVisibility(View.GONE);                  vKeyStatus.setVisibility(View.GONE);                  setItemEnabled(view, true);              } else {                  view = inner.getView(position - 1, convertView, parent);                  TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email); -                TextView vKeyId = (TextView) view.findViewById(R.id.keyspinner_key_id);                  vKeyEmail.setVisibility(View.VISIBLE); -                vKeyId.setVisibility(View.VISIBLE);              }              return view;          } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index 10327a6a4..df7347fa4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -59,7 +59,9 @@ public class SignKeySpinner extends KeySpinner {                  KeychainContract.KeyRings.IS_REVOKED,                  KeychainContract.KeyRings.IS_EXPIRED,                  KeychainContract.KeyRings.HAS_SIGN, -                KeychainContract.KeyRings.HAS_ANY_SECRET +                KeychainContract.KeyRings.HAS_ANY_SECRET, +                KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID, +                KeychainContract.KeyRings.CREATION          };          String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index 1413273e8..6efc0a5ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -190,7 +190,7 @@ public class ContactHelper {       * @param context       * @return       */ -    private static List<String> getMainProfileContactName(Context context) { +    public static List<String> getMainProfileContactName(Context context) {          ContentResolver resolver = context.getContentResolver();          Cursor profileCursor = resolver.query(                  ContactsContract.Profile.CONTENT_URI, @@ -214,6 +214,55 @@ public class ContactHelper {          return new ArrayList<>(names);      } +    /** +     * returns the CONTACT_ID of the main ("me") contact +     * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html +     * +     * @param resolver +     * @return +     */ +    public static long getMainProfileContactId(ContentResolver resolver) { +        Cursor profileCursor = resolver.query(ContactsContract.Profile.CONTENT_URI, +                new String[]{ ContactsContract.Profile._ID}, null, null, null); + +        if(profileCursor != null && profileCursor.getCount() != 0 && profileCursor.moveToNext()) { +            long contactId = profileCursor.getLong(0); +            profileCursor.close(); +            return contactId; +        } +        else { +            if(profileCursor != null) { +                profileCursor.close(); +            } +            return -1; +        } +    } + +    /** +     * loads the profile picture of the main ("me") contact +     * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html +     * +     * @param contentResolver +     * @param highRes         true for large image if present, false for thumbnail +     * @return bitmap of loaded photo +     */ +    public static Bitmap loadMainProfilePhoto(ContentResolver contentResolver, boolean highRes) { +        try { +            long mainProfileContactId = getMainProfileContactId(contentResolver); + +            Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, +                    Long.toString(mainProfileContactId)); +            InputStream photoInputStream = +                    ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, contactUri, highRes); +            if (photoInputStream == null) { +                return null; +            } +            return BitmapFactory.decodeStream(photoInputStream); +        } catch (Throwable ignored) { +            return null; +        } +    } +      public static List<String> getContactMails(Context context) {          ContentResolver resolver = context.getContentResolver();          Cursor mailCursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, @@ -269,7 +318,7 @@ public class ContactHelper {      /**       * returns the CONTACT_ID of the raw contact to which a masterKeyId is associated, if the -     * raw contact has not been marked for deletion +     * raw contact has not been marked for deletion.       *       * @param resolver       * @param masterKeyId @@ -296,6 +345,34 @@ public class ContactHelper {          return contactId;      } +    /** +     * Returns the display name of the system contact associated with contactId, null if the +     * contact does not exist +     * +     * @param resolver +     * @param contactId +     * @return primary display name of system contact associated with contactId, null if it does +     * not exist +     */ +    public static String getContactName(ContentResolver resolver, long contactId) { +        String contactName = null; +        Cursor raw = resolver.query(ContactsContract.Contacts.CONTENT_URI, +                new String[]{ +                        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY +                }, +                ContactsContract.Contacts._ID + "=?", +                new String[]{//"0" for "not deleted" +                        Long.toString(contactId) +                }, null); +        if (raw != null) { +            if (raw.moveToNext()) { +                contactName = raw.getString(0); +            } +            raw.close(); +        } +        return contactName; +    } +      public static Bitmap getCachedPhotoByMasterKeyId(ContentResolver contentResolver, long masterKeyId) {          if (masterKeyId == -1) {              return null; @@ -333,46 +410,47 @@ public class ContactHelper {              KeychainContract.KeyRings.MASTER_KEY_ID,              KeychainContract.KeyRings.USER_ID,              KeychainContract.KeyRings.IS_EXPIRED, -            KeychainContract.KeyRings.IS_REVOKED}; +            KeychainContract.KeyRings.IS_REVOKED, +            KeychainContract.KeyRings.VERIFIED, +            KeychainContract.KeyRings.HAS_SECRET, +            KeychainContract.KeyRings.HAS_ANY_SECRET};      public static final int INDEX_MASTER_KEY_ID = 0;      public static final int INDEX_USER_ID = 1;      public static final int INDEX_IS_EXPIRED = 2;      public static final int INDEX_IS_REVOKED = 3; +    public static final int INDEX_VERIFIED = 4; +    public static final int INDEX_HAS_SECRET = 5; +    public static final int INDEX_HAS_ANY_SECRET = 6;      /**       * Write/Update the current OpenKeychain keys to the contact db       */      public static void writeKeysToContacts(Context context) {          ContentResolver resolver = context.getContentResolver(); -        Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver);          if (Constants.DEBUG_SYNC_REMOVE_CONTACTS) {              debugDeleteRawContacts(resolver);          } -//        ContentProviderClient client = resolver.acquireContentProviderClient(ContactsContract.AUTHORITY_URI); -//        ContentValues values = new ContentValues(); -//        Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE); -//        values.put(ContactsContract.Settings.ACCOUNT_NAME, account.name); -//        values.put(ContactsContract.Settings.ACCOUNT_TYPE, account.type); -//        values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, true); -//        try { -//            client.insert(ContactsContract.Settings.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(), values); -//        } catch (RemoteException e) { -//            e.printStackTrace(); -//        } - -        // Load all Keys from OK -        Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION, -                null, null, null); +        writeKeysToMainProfileContact(context, resolver); + +        Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver); + +        // Load all public Keys from OK +        // TODO: figure out why using selectionArgs does not work in this case +        Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), +                KEYS_TO_CONTACT_PROJECTION, +                KeychainContract.KeyRings.HAS_ANY_SECRET + "=0", +                null, null); +          if (cursor != null) {              while (cursor.moveToNext()) {                  long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);                  String[] userIdSplit = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID)); -                String keyIdShort = KeyFormattingUtils.convertKeyIdToHexShort(cursor.getLong(INDEX_MASTER_KEY_ID));                  boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0;                  boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; +                boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;                  Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); @@ -384,9 +462,11 @@ public class ContactHelper {                  ArrayList<ContentProviderOperation> ops = new ArrayList<>(); -                // Do not store expired or revoked keys in contact db - and remove them if they already exist -                if (isExpired || isRevoked) { -                    Log.d(Constants.TAG, "Expired or revoked: Deleting rawContactId " + rawContactId); +                // Do not store expired or revoked or unverified keys in contact db - and +                // remove them if they already exist. Secret keys do not reach this point +                if (isExpired || isRevoked || !isVerified) { +                    Log.d(Constants.TAG, "Expired or revoked or unverified: Deleting rawContactId " +                            + rawContactId);                      if (rawContactId != -1) {                          deleteRawContactById(resolver, rawContactId);                      } @@ -397,7 +477,7 @@ public class ContactHelper {                          Log.d(Constants.TAG, "Insert new raw contact with masterKeyId " + masterKeyId);                          insertContact(ops, context, masterKeyId); -                        writeContactKey(ops, context, rawContactId, masterKeyId, keyIdShort); +                        writeContactKey(ops, context, rawContactId, masterKeyId, userIdSplit[0]);                      }                      // We always update the display name (which is derived from primary user id) @@ -422,43 +502,165 @@ public class ContactHelper {      }      /** -     * Delete all raw contacts associated to OpenKeychain. +     * Links all keys with secrets to the main ("me") contact +     * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html +     * +     * @param context +     */ +    public static void writeKeysToMainProfileContact(Context context, ContentResolver resolver) { +        Set<Long> keysToDelete = getMainProfileMasterKeyIds(resolver); + +        // get all keys which have associated secret keys +        // TODO: figure out why using selectionArgs does not work in this case +        Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), +                KEYS_TO_CONTACT_PROJECTION, +                KeychainContract.KeyRings.HAS_ANY_SECRET + "!=0", +                null, null); +        if (cursor != null) { +            while (cursor.moveToNext()) { +                long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); +                boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; +                boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; +                String[] userIdSplit = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID)); + +                if (!isExpired && !isRevoked && userIdSplit[0] != null) { +                    // if expired or revoked will not be removed from keysToDelete or inserted +                    // into main profile ("me" contact) +                    boolean existsInMainProfile = keysToDelete.remove(masterKeyId); +                    if (!existsInMainProfile) { +                        long rawContactId = -1;//new raw contact + +                        Log.d(Constants.TAG, "masterKeyId with secret " + masterKeyId); + +                        ArrayList<ContentProviderOperation> ops = new ArrayList<>(); +                        insertMainProfileRawContact(ops, masterKeyId); +                        writeContactKey(ops, context, rawContactId, masterKeyId, userIdSplit[0]); + +                        try { +                            resolver.applyBatch(ContactsContract.AUTHORITY, ops); +                        } catch (Exception e) { +                            Log.w(Constants.TAG, e); +                        } +                    } +                } +            } +        } + +        for (long masterKeyId : keysToDelete) { +            deleteMainProfileRawContactByMasterKeyId(resolver, masterKeyId); +            Log.d(Constants.TAG, "Delete main profile raw contact with masterKeyId " + masterKeyId); +        } +    } + +    /** +     * Inserts a raw contact into the table defined by ContactsContract.Profile +     * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html +     * +     * @param ops +     * @param masterKeyId +     */ +    private static void insertMainProfileRawContact(ArrayList<ContentProviderOperation> ops, +                                                    long masterKeyId) { +        ops.add(ContentProviderOperation.newInsert(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI) +                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME) +                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE) +                .withValue(ContactsContract.RawContacts.SOURCE_ID, Long.toString(masterKeyId)) +                .build()); +    } + +    /** +     * deletes a raw contact from the main profile table ("me" contact) +     * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html +     * +     * @param resolver +     * @param masterKeyId +     * @return +     */ +    private static int deleteMainProfileRawContactByMasterKeyId(ContentResolver resolver, +                                                                long masterKeyId) { +        // 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.SOURCE_ID + "=?", +                new String[]{ +                        Constants.ACCOUNT_TYPE, Long.toString(masterKeyId) +                }); +    } + +    /** +     * Delete all raw contacts associated to OpenKeychain, including those from "me" contact +     * defined by ContactsContract.Profile +     * +     * @return number of rows deleted       */      private static int debugDeleteRawContacts(ContentResolver resolver) { -        //allows us to actually wipe the RawContact from the device, otherwise would be just flagged -        //for deletion +        // 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();          Log.d(Constants.TAG, "Deleting all raw contacts associated to OK..."); -        return resolver.delete(deleteUri, +        int delete = resolver.delete(deleteUri, +                ContactsContract.RawContacts.ACCOUNT_TYPE + "=?", +                new String[]{ +                        Constants.ACCOUNT_TYPE +                }); + +        Uri mainProfileDeleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon() +                .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); + +        delete += resolver.delete(mainProfileDeleteUri,                  ContactsContract.RawContacts.ACCOUNT_TYPE + "=?",                  new String[]{                          Constants.ACCOUNT_TYPE                  }); + +        return delete;      } +    /** +     * Deletes raw contacts from ContactsContract.RawContacts based on rawContactId. Does not +     * delete contacts from the "me" contact defined in ContactsContract.Profile +     * +     * @param resolver +     * @param rawContactId +     * @return number of rows deleted +     */      private static int deleteRawContactById(ContentResolver resolver, long rawContactId) { -        //allows us to actually wipe the RawContact from the device, otherwise would be just flagged -        //for deletion +        // 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._ID + "=?", +                ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + +                        ContactsContract.RawContacts._ID + "=?",                  new String[]{                          Constants.ACCOUNT_TYPE, Long.toString(rawContactId)                  });      } +    /** +     * Deletes raw contacts from ContactsContract.RawContacts based on masterKeyId. Does not +     * delete contacts from the "me" contact defined in ContactsContract.Profile +     * +     * @param resolver +     * @param masterKeyId +     * @return number of rows deleted +     */      private static int deleteRawContactByMasterKeyId(ContentResolver resolver, long masterKeyId) { -        //allows us to actually wipe the RawContact from the device, otherwise would be just flagged -        //for deletion +        // 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.SOURCE_ID + "=?", +                ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + +                        ContactsContract.RawContacts.SOURCE_ID + "=?",                  new String[]{                          Constants.ACCOUNT_TYPE, Long.toString(masterKeyId)                  }); @@ -487,6 +689,28 @@ public class ContactHelper {      }      /** +     * @return a set of all key master key ids currently present in the contact db +     */ +    private static Set<Long> getMainProfileMasterKeyIds(ContentResolver resolver) { +        HashSet<Long> result = new HashSet<>(); +        Cursor masterKeyIds = resolver.query(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI, +                new String[]{ +                        ContactsContract.RawContacts.SOURCE_ID +                }, +                ContactsContract.RawContacts.ACCOUNT_TYPE + "=?", +                new String[]{ +                        Constants.ACCOUNT_TYPE +                }, null); +        if (masterKeyIds != null) { +            while (masterKeyIds.moveToNext()) { +                result.add(masterKeyIds.getLong(0)); +            } +            masterKeyIds.close(); +        } +        return result; +    } + +    /**       * This will search the contact db for a raw contact with a given master key id       *       * @return raw contact id or -1 if not found @@ -528,10 +752,10 @@ public class ContactHelper {       * This creates the link to OK in contact details       */      private static void writeContactKey(ArrayList<ContentProviderOperation> ops, Context context, long rawContactId, -                                        long masterKeyId, String keyIdShort) { +                                        long masterKeyId, String keyName) {          ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), rawContactId)                  .withValue(ContactsContract.Data.MIMETYPE, Constants.CUSTOM_CONTACT_DATA_MIME_TYPE) -                .withValue(ContactsContract.Data.DATA1, context.getString(R.string.contact_show_key, keyIdShort)) +                .withValue(ContactsContract.Data.DATA1, context.getString(R.string.contact_show_key, keyName))                  .withValue(ContactsContract.Data.DATA2, masterKeyId)                  .build());      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java index 1d78ed80e..cda5892fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java @@ -124,7 +124,7 @@ public class ExportHelper {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); -                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle data = message.getData(); | 
