diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceTest.java
index d12fc6333..1e84ea879 100644
--- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceTest.java
+++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceTest.java
@@ -82,7 +82,7 @@ public class OpenPgpServiceTest {
pi.send();
Thread.sleep(ACTIVITY_WAIT_TIME); // Wait for activity to start
- onView(withText(R.string.api_register_allow)).perform(click());
+ onView(withText(R.string.button_allow)).perform(click());
}
byte[] ciphertext;
@@ -121,10 +121,7 @@ public class OpenPgpServiceTest {
pi.send();
Thread.sleep(ACTIVITY_WAIT_TIME); // Wait for activity to start
- onData(withKeyItemId(0x9D604D2F310716A3L))
- .inAdapterView(isAssignableFrom(AdapterView.class))
- .perform(click());
- onView(withText(R.string.api_settings_save)).perform(click());
+ onView(withText(R.string.button_allow)).perform(click());
}
{ // decrypt again, this time pending passphrase
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index bad087468..f98a7f3e3 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -861,6 +861,7 @@
CREATOR = new Creator() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
index e3b3139a4..88334e9af 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
@@ -29,6 +29,7 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.SignatureException;
import java.util.Date;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
@@ -208,7 +209,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation skippedDisallowedEncryptionKeys = new HashSet<>();
boolean insecureEncryptionKey = false;
// convenience method to return with error
@@ -607,7 +608,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation 0) {
// allow user to select allowed keys
Intent result = new Intent();
String packageName = mApiPermissionHelper.getCurrentCallingPackage();
result.putExtra(OpenPgpApi.RESULT_INTENT,
- mApiPendingIntentFactory.createSelectAllowedKeysPendingIntent(data, packageName));
+ mApiPendingIntentFactory.createRequestKeyPermissionPendingIntent(
+ data, packageName, skippedDisallowedEncryptionKeys));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
return result;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java
index e869a2e25..3725df272 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java
@@ -2,7 +2,6 @@ package org.sufficientlysecure.keychain.remote;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.HashSet;
import android.app.PendingIntent;
@@ -10,7 +9,6 @@ import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
-import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import org.openintents.openpgp.OpenPgpError;
@@ -21,6 +19,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
@@ -146,7 +145,7 @@ class OpenPgpServiceKeyIdExtractor {
}
if (!hasUserIds || hasMissingUserIds || hasDuplicateUserIds) {
- long[] keyIdsArray = getUnboxedLongArray(keyIds);
+ long[] keyIdsArray = KeyFormattingUtils.getUnboxedLongArray(keyIds);
PendingIntent pi = apiPendingIntentFactory.createSelectPublicKeyPendingIntent(data, keyIdsArray,
missingEmails, duplicateEmails, hasUserIds);
@@ -164,16 +163,6 @@ class OpenPgpServiceKeyIdExtractor {
return new KeyIdResult(keyIds);
}
- @NonNull
- private static long[] getUnboxedLongArray(@NonNull Collection arrayList) {
- long[] result = new long[arrayList.size()];
- int i = 0;
- for (Long e : arrayList) {
- result[i++] = e;
- }
- return result;
- }
-
static class KeyIdResult {
private final Intent mResultIntent;
private final HashSet mKeyIds;
@@ -206,7 +195,7 @@ class OpenPgpServiceKeyIdExtractor {
if (mKeyIds == null) {
throw new AssertionError("key ids must not be null when getKeyIds is called!");
}
- return getUnboxedLongArray(mKeyIds);
+ return KeyFormattingUtils.getUnboxedLongArray(mKeyIds);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java
index 0ce9e66c9..567757c44 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java
@@ -18,73 +18,173 @@
package org.sufficientlysecure.keychain.remote.ui;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
-import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
-import org.sufficientlysecure.keychain.remote.AppSettings;
-import org.sufficientlysecure.keychain.ui.base.BaseActivity;
-import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.remote.ui.RemoteRegisterPresenter.RemoteRegisterView;
+import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
-public class RemoteRegisterActivity extends BaseActivity {
+public class RemoteRegisterActivity extends FragmentActivity {
public static final String EXTRA_PACKAGE_NAME = "package_name";
public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature";
-
public static final String EXTRA_DATA = "data";
- private AppSettingsHeaderFragment mAppSettingsHeaderFragment;
- @Override
- protected void initLayout() {
- setContentView(R.layout.api_remote_register_app);
- }
+ private RemoteRegisterPresenter presenter;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- final Bundle extras = getIntent().getExtras();
+ this.presenter = new RemoteRegisterPresenter(getBaseContext());
- final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
- final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
- Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
+ if (savedInstanceState == null) {
+ RemoteRegisterDialogFragment frag = new RemoteRegisterDialogFragment();
+ frag.show(getSupportFragmentManager(), "requestKeyDialog");
+ }
+ }
- final ApiDataAccessObject apiDao = new ApiDataAccessObject(this);
+ @Override
+ protected void onStart() {
+ super.onStart();
- mAppSettingsHeaderFragment = (AppSettingsHeaderFragment) getSupportFragmentManager().findFragmentById(
- R.id.api_app_settings_fragment);
+ Intent intent = getIntent();
+ Intent resultData = intent.getParcelableExtra(EXTRA_DATA);
+ String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
+ byte[] packageSignature = intent.getByteArrayExtra(EXTRA_PACKAGE_SIGNATURE);
- AppSettings settings = new AppSettings(packageName, packageSignature);
- mAppSettingsHeaderFragment.setAppSettings(settings);
+ presenter.setupFromIntentData(resultData, packageName, packageSignature);
+ }
- // Inflate a "Done"/"Cancel" custom action bar view
- setFullScreenDialogTwoButtons(
- R.string.api_register_allow, R.drawable.ic_check_white_24dp,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Allow
- apiDao.insertApiApp(mAppSettingsHeaderFragment.getAppSettings());
+ public static class RemoteRegisterDialogFragment extends DialogFragment {
+ private RemoteRegisterPresenter presenter;
+ private RemoteRegisterView mvpView;
- // give data through for new service call
- Intent resultData = extras.getParcelable(EXTRA_DATA);
- RemoteRegisterActivity.this.setResult(RESULT_OK, resultData);
- RemoteRegisterActivity.this.finish();
- }
- }, R.string.api_register_disallow, R.drawable.ic_close_white_24dp,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Disallow
- RemoteRegisterActivity.this.setResult(RESULT_CANCELED);
- RemoteRegisterActivity.this.finish();
+ private Button buttonAllow;
+ private Button buttonCancel;
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);
+ CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme);
+
+ @SuppressLint("InflateParams")
+ View view = LayoutInflater.from(theme).inflate(R.layout.api_remote_register_app, null, false);
+ alert.setView(view);
+
+ buttonAllow = (Button) view.findViewById(R.id.button_allow);
+ buttonCancel = (Button) view.findViewById(R.id.button_cancel);
+
+ setupListenersForPresenter();
+ mvpView = createMvpView(view);
+
+ return alert.create();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ presenter = ((RemoteRegisterActivity) getActivity()).presenter;
+ presenter.setView(mvpView);
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+
+ if (presenter != null) {
+ presenter.onCancel();
+ }
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+
+ if (presenter != null) {
+ presenter.setView(null);
+ presenter = null;
+ }
+ }
+
+ @NonNull
+ private RemoteRegisterView createMvpView(View view) {
+ final TextView titleText = (TextView) view.findViewById(R.id.api_register_text);
+ final ImageView iconClientApp = (ImageView) view.findViewById(R.id.icon_client_app);
+
+ return new RemoteRegisterView() {
+ @Override
+ public void finishWithResult(Intent resultIntent) {
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
}
+
+ activity.setResult(RESULT_OK, resultIntent);
+ activity.finish();
}
- );
+
+ @Override
+ public void finishAsCancelled() {
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ activity.setResult(RESULT_CANCELED);
+ activity.finish();
+ }
+
+ @Override
+ public void setTitleText(String text) {
+ titleText.setText(text);
+ }
+
+ @Override
+ public void setTitleClientIcon(Drawable drawable) {
+ iconClientApp.setImageDrawable(drawable);
+ }
+ };
+ }
+
+ private void setupListenersForPresenter() {
+ buttonAllow.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ presenter.onClickAllow();
+ }
+ });
+
+ buttonCancel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ presenter.onClickCancel();
+ }
+ });
+ }
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java
new file mode 100644
index 000000000..69c80446a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java
@@ -0,0 +1,83 @@
+package org.sufficientlysecure.keychain.remote.ui;
+
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
+import org.sufficientlysecure.keychain.remote.AppSettings;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+class RemoteRegisterPresenter {
+ private final ApiDataAccessObject apiDao;
+ private final PackageManager packageManager;
+ private final Context context;
+
+
+ private RemoteRegisterView view;
+ private Intent resultData;
+ private AppSettings appSettings;
+
+
+ RemoteRegisterPresenter(Context context) {
+ this.context = context;
+
+ apiDao = new ApiDataAccessObject(context);
+ packageManager = context.getPackageManager();
+ }
+
+ public void setView(RemoteRegisterView view) {
+ this.view = view;
+ }
+
+ void setupFromIntentData(Intent resultData, String packageName, byte[] packageSignature) {
+ this.appSettings = new AppSettings(packageName, packageSignature);
+ this.resultData = resultData;
+
+ try {
+ setPackageInfo(packageName);
+ } catch (NameNotFoundException e) {
+ Log.e(Constants.TAG, "Unable to find info of calling app!");
+ view.finishAsCancelled();
+ return;
+ }
+ }
+
+ private void setPackageInfo(String packageName) throws NameNotFoundException {
+ ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0);
+ Drawable appIcon = packageManager.getApplicationIcon(applicationInfo);
+ CharSequence appName = packageManager.getApplicationLabel(applicationInfo);
+
+ view.setTitleClientIcon(appIcon);
+ view.setTitleText(context.getString(R.string.api_register_text, appName));
+ }
+
+ void onClickAllow() {
+ apiDao.insertApiApp(appSettings);
+ view.finishWithResult(resultData);
+ }
+
+ void onClickCancel() {
+ view.finishAsCancelled();
+ }
+
+ void onCancel() {
+ view.finishAsCancelled();
+ }
+
+ interface RemoteRegisterView {
+ void finishWithResult(Intent resultData);
+ void finishAsCancelled();
+
+ void setTitleText(String text);
+ void setTitleClientIcon(Drawable drawable);
+ }
+}
+
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionActivity.java
new file mode 100644
index 000000000..0296e9b48
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionActivity.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 Vincent Breitmoser
+ *
+ * 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 .
+ */
+
+package org.sufficientlysecure.keychain.remote.ui;
+
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.openintents.openpgp.util.OpenPgpUtils.UserId;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.remote.ui.RequestKeyPermissionPresenter.RequestKeyPermissionMvpView;
+import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+
+
+public class RequestKeyPermissionActivity extends FragmentActivity {
+ public static final String EXTRA_PACKAGE_NAME = "package_name";
+ public static final String EXTRA_REQUESTED_KEY_IDS = "requested_key_ids";
+
+
+ private RequestKeyPermissionPresenter presenter;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ presenter = RequestKeyPermissionPresenter.createRequestKeyPermissionPresenter(getBaseContext());
+
+ if (savedInstanceState == null) {
+ RequestKeyPermissionFragment frag = new RequestKeyPermissionFragment();
+ frag.show(getSupportFragmentManager(), "requestKeyDialog");
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ Intent intent = getIntent();
+ String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
+ long masterKeyIds[] = intent.getLongArrayExtra(EXTRA_REQUESTED_KEY_IDS);
+
+ presenter.setupFromIntentData(packageName, masterKeyIds);
+ }
+
+ public static class RequestKeyPermissionFragment extends DialogFragment {
+ private RequestKeyPermissionMvpView mvpView;
+ private RequestKeyPermissionPresenter presenter;
+ private Button buttonCancel;
+ private Button buttonAllow;
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);
+ CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme);
+
+ @SuppressLint("InflateParams")
+ View view = LayoutInflater.from(theme).inflate(R.layout.api_remote_request_key_permission, null, false);
+ alert.setView(view);
+
+ buttonAllow = (Button) view.findViewById(R.id.button_allow);
+ buttonCancel = (Button) view.findViewById(R.id.button_cancel);
+
+ setupListenersForPresenter();
+ mvpView = createMvpView(view);
+
+ return alert.create();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ presenter = ((RequestKeyPermissionActivity) getActivity()).presenter;
+ presenter.setView(mvpView);
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+
+ if (presenter != null) {
+ presenter.onCancel();
+ }
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+
+ if (presenter != null) {
+ presenter.setView(null);
+ presenter = null;
+ }
+ }
+
+ @NonNull
+ private RequestKeyPermissionMvpView createMvpView(View view) {
+ final TextView titleText = (TextView) view.findViewById(R.id.select_identity_key_title);
+ final TextView keyUserIdView = (TextView) view.findViewById(R.id.select_key_item_name);
+ final ImageView iconClientApp = (ImageView) view.findViewById(R.id.icon_client_app);
+ final View keyUnavailableWarning = view.findViewById(R.id.requested_key_unavailable_warning);
+ final View keyInfoLayout = view.findViewById(R.id.key_info_layout);
+
+ return new RequestKeyPermissionMvpView() {
+ @Override
+ public void switchToLayoutRequestKeyChoice() {
+ keyInfoLayout.setVisibility(View.VISIBLE);
+ keyUnavailableWarning.setVisibility(View.GONE);
+ buttonAllow.setEnabled(true);
+ }
+
+ @Override
+ public void switchToLayoutNoSecret() {
+ keyInfoLayout.setVisibility(View.VISIBLE);
+ keyUnavailableWarning.setVisibility(View.VISIBLE);
+ buttonAllow.setEnabled(false);
+ }
+
+ @Override
+ public void displayKeyInfo(UserId userId) {
+ keyUserIdView.setText(userId.name);
+ }
+
+ @Override
+ public void finish() {
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ activity.setResult(Activity.RESULT_OK);
+ activity.finish();
+ }
+
+ @Override
+ public void finishAsCancelled() {
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ activity.setResult(Activity.RESULT_CANCELED);
+ activity.finish();
+ }
+
+ @Override
+ public void setTitleText(String text) {
+ titleText.setText(text);
+ }
+
+ @Override
+ public void setTitleClientIcon(Drawable drawable) {
+ iconClientApp.setImageDrawable(drawable);
+ }
+ };
+ }
+
+ private void setupListenersForPresenter() {
+ buttonAllow.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ presenter.onClickAllow();
+ }
+ });
+
+ buttonCancel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ presenter.onClickCancel();
+ }
+ });
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java
new file mode 100644
index 000000000..b4de0fe7d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java
@@ -0,0 +1,170 @@
+package org.sufficientlysecure.keychain.remote.ui;
+
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+
+import org.openintents.openpgp.util.OpenPgpUtils.UserId;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
+import org.sufficientlysecure.keychain.remote.ApiPermissionHelper;
+import org.sufficientlysecure.keychain.remote.ApiPermissionHelper.WrongPackageCertificateException;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+class RequestKeyPermissionPresenter {
+ private final Context context;
+ private final PackageManager packageManager;
+ private final ApiDataAccessObject apiDataAccessObject;
+ private final ApiPermissionHelper apiPermissionHelper;
+
+ private RequestKeyPermissionMvpView view;
+
+ private String packageName;
+ private long masterKeyId;
+ private ProviderHelper providerHelper;
+
+
+ static RequestKeyPermissionPresenter createRequestKeyPermissionPresenter(Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ ApiDataAccessObject apiDataAccessObject = new ApiDataAccessObject(context);
+ ApiPermissionHelper apiPermissionHelper = new ApiPermissionHelper(context, apiDataAccessObject);
+ ProviderHelper providerHelper = new ProviderHelper(context);
+
+ return new RequestKeyPermissionPresenter(context, apiDataAccessObject, apiPermissionHelper, packageManager,
+ providerHelper);
+ }
+
+ private RequestKeyPermissionPresenter(Context context, ApiDataAccessObject apiDataAccessObject,
+ ApiPermissionHelper apiPermissionHelper, PackageManager packageManager, ProviderHelper providerHelper) {
+ this.context = context;
+ this.apiDataAccessObject = apiDataAccessObject;
+ this.apiPermissionHelper = apiPermissionHelper;
+ this.packageManager = packageManager;
+ this.providerHelper = providerHelper;
+ }
+
+ void setView(RequestKeyPermissionMvpView view) {
+ this.view = view;
+ }
+
+ void setupFromIntentData(String packageName, long[] masterKeyIds) {
+ checkPackageAllowed(packageName);
+
+ try {
+ setPackageInfo(packageName);
+ } catch (NameNotFoundException e) {
+ Log.e(Constants.TAG, "Unable to find info of calling app!");
+ view.finishAsCancelled();
+ return;
+ }
+
+ try {
+ setRequestedMasterKeyId(masterKeyIds);
+ } catch (PgpKeyNotFoundException e) {
+ view.finishAsCancelled();
+ }
+ }
+
+ private void setRequestedMasterKeyId(long[] subKeyIds) throws PgpKeyNotFoundException {
+ CachedPublicKeyRing secretKeyRingOrPublicFallback = findSecretKeyRingOrPublicFallback(subKeyIds);
+
+ if (secretKeyRingOrPublicFallback == null) {
+ throw new PgpKeyNotFoundException("No key found among requested!");
+ }
+
+ this.masterKeyId = secretKeyRingOrPublicFallback.getMasterKeyId();
+
+ UserId userId = secretKeyRingOrPublicFallback.getSplitPrimaryUserIdWithFallback();
+ view.displayKeyInfo(userId);
+
+ if (secretKeyRingOrPublicFallback.hasAnySecret()) {
+ view.switchToLayoutRequestKeyChoice();
+ } else {
+ view.switchToLayoutNoSecret();
+ }
+ }
+
+ @Nullable
+ private CachedPublicKeyRing findSecretKeyRingOrPublicFallback(long[] subKeyIds) {
+ CachedPublicKeyRing publicFallbackRing = null;
+ for (long candidateSubKeyId : subKeyIds) {
+ try {
+ CachedPublicKeyRing cachedPublicKeyRing = providerHelper.getCachedPublicKeyRing(
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(candidateSubKeyId)
+ );
+
+ SecretKeyType secretKeyType = cachedPublicKeyRing.getSecretKeyType(candidateSubKeyId);
+ if (secretKeyType.isUsable()) {
+ return cachedPublicKeyRing;
+ }
+ if (publicFallbackRing == null) {
+ publicFallbackRing = cachedPublicKeyRing;
+ }
+ } catch (PgpKeyNotFoundException | NotFoundException e) {
+ // no matter
+ }
+ }
+ return publicFallbackRing;
+ }
+
+ private void setPackageInfo(String packageName) throws NameNotFoundException {
+ this.packageName = packageName;
+
+ ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0);
+ Drawable appIcon = packageManager.getApplicationIcon(applicationInfo);
+ CharSequence appName = packageManager.getApplicationLabel(applicationInfo);
+
+ view.setTitleClientIcon(appIcon);
+ view.setTitleText(context.getString(R.string.request_permission_msg, appName));
+ }
+
+ private void checkPackageAllowed(String packageName) {
+ boolean packageAllowed;
+ try {
+ packageAllowed = apiPermissionHelper.isPackageAllowed(packageName);
+ } catch (WrongPackageCertificateException e) {
+ packageAllowed = false;
+ }
+ if (!packageAllowed) {
+ throw new IllegalStateException("Pending intent launched by unknown app!");
+ }
+ }
+
+ void onClickAllow() {
+ apiDataAccessObject.addAllowedKeyIdForApp(packageName, masterKeyId);
+ view.finish();
+ }
+
+ void onClickCancel() {
+ view.finishAsCancelled();
+ }
+
+ void onCancel() {
+ view.finishAsCancelled();
+ }
+
+ interface RequestKeyPermissionMvpView {
+ void switchToLayoutRequestKeyChoice();
+ void switchToLayoutNoSecret();
+
+ void setTitleText(String text);
+ void setTitleClientIcon(Drawable drawable);
+
+ void displayKeyInfo(UserId userId);
+
+ void finish();
+ void finishAsCancelled();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java
deleted file mode 100644
index 1dc53e61a..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2015 Dominik Schürmann
- *
- * 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 .
- */
-
-package org.sufficientlysecure.keychain.remote.ui;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.View;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.provider.KeychainContract;
-import org.sufficientlysecure.keychain.ui.base.BaseActivity;
-import org.sufficientlysecure.keychain.util.Log;
-
-public class SelectAllowedKeysActivity extends BaseActivity {
-
- public static final String EXTRA_SERVICE_INTENT = "data";
-
- private AppSettingsAllowedKeysListFragment mAllowedKeysFragment;
-
- Intent mServiceData;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Inflate a "Done" custom action bar
- setFullScreenDialogDoneClose(R.string.api_settings_save,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- save();
- }
- },
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- cancel();
- }
- });
-
- Intent intent = getIntent();
- mServiceData = intent.getParcelableExtra(EXTRA_SERVICE_INTENT);
- Uri appUri = intent.getData();
- if (appUri == null) {
- Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
- finish();
- return;
- } else {
- Log.d(Constants.TAG, "uri: " + appUri);
- loadData(savedInstanceState, appUri);
- }
- }
-
- @Override
- protected void initLayout() {
- setContentView(R.layout.api_remote_select_allowed_keys);
- }
-
- private void save() {
- mAllowedKeysFragment.saveAllowedKeys();
- setResult(Activity.RESULT_OK, mServiceData);
- finish();
- }
-
- private void cancel() {
- setResult(Activity.RESULT_CANCELED);
- finish();
- }
-
- private void loadData(Bundle savedInstanceState, Uri appUri) {
- Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
- Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri);
- startListFragments(savedInstanceState, allowedKeysUri);
- }
-
- private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) {
- // However, if we're being restored from a previous state,
- // then we don't need to do anything and should return or else
- // we could end up with overlapping fragments.
- if (savedInstanceState != null) {
- return;
- }
-
- // Create an instance of the fragments
- mAllowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri);
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.api_allowed_keys_list_fragment, mAllowedKeysFragment)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java
index 84e130d92..27264a498 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java
@@ -293,7 +293,7 @@ public class BackupCodeFragment extends CryptoOperationFragment arrayList) {
+ long[] result = new long[arrayList.size()];
+ int i = 0;
+ for (Long e : arrayList) {
+ result[i++] = e;
+ }
+ return result;
+ }
+
public enum State {
REVOKED,
EXPIRED,
diff --git a/OpenKeychain/src/main/res/layout-w400dp/backup_code_fragment.xml b/OpenKeychain/src/main/res/layout-w400dp/backup_code_fragment.xml
index 407fa2d9e..dfeed3f2d 100644
--- a/OpenKeychain/src/main/res/layout-w400dp/backup_code_fragment.xml
+++ b/OpenKeychain/src/main/res/layout-w400dp/backup_code_fragment.xml
@@ -354,7 +354,7 @@
-
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:layout_marginTop="24dp"
+ >
-
-
-
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:elevation="4dp"
+ android:background="?attr/colorPrimary"
+ android:gravity="center_horizontal"
+ tools:targetApi="lollipop">
-
+
+
+
+
+
+
+
+
+
+
+ android:overScrollMode="ifContentScrolls"
+ tools:ignore="UselessParent">
-
-
-
+ android:orientation="vertical"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="24dp"
+ android:paddingBottom="16dp"
+ >
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_remote_request_key_permission.xml b/OpenKeychain/src/main/res/layout/api_remote_request_key_permission.xml
new file mode 100644
index 000000000..6598b48cc
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/api_remote_request_key_permission.xml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml b/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml
deleted file mode 100644
index e052ff333..000000000
--- a/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/backup_code_fragment.xml b/OpenKeychain/src/main/res/layout/backup_code_fragment.xml
index ecdaff5cc..a85f0273f 100644
--- a/OpenKeychain/src/main/res/layout/backup_code_fragment.xml
+++ b/OpenKeychain/src/main/res/layout/backup_code_fragment.xml
@@ -384,7 +384,7 @@
"No accounts attached to this app."
"No key is configured for this account. Please select one of your existing keys or create a new one.\nApps can only decrypt/sign with the keys selected here!"
"The key saved for this account has been deleted. Please select a different one!\nApps can only decrypt/sign with the keys selected here!"
- "The displayed app wants to encrypt/decrypt messages and sign them in your name.\nAllow access?\n\nWARNING: If you do not know why this screen appeared, disallow access! You can revoke access later using the 'Apps' screen."
+ "Allow access to OpenKeychain?"
+ "%s requests to use OpenKeychain as a crypto provider. You will still be asked for permission before the app can use any of your keys for decryption.\n\nYou can revoke access later in the 'Apps' screen."
"Allow access"
"Disallow access"
"Please select a key!"
@@ -1797,4 +1798,11 @@
"Scan again"
"Close"
"Key import redirection"
+
+ Allow access to your key?
+ %1$s requests access to one of your keys, which allows it to decrypt messages sent to this key. You can revoke access later in OpenKeychain.
+ This key is not available. To use it, you must import it as one of your own!
+ Allow
+ Cancel
+ Requested key:
diff --git a/OpenKeychain/src/main/res/values/styles.xml b/OpenKeychain/src/main/res/values/styles.xml
index d397d8e3a..39b8cd8d7 100644
--- a/OpenKeychain/src/main/res/values/styles.xml
+++ b/OpenKeychain/src/main/res/values/styles.xml
@@ -22,6 +22,17 @@
- @color/card_view_button
+
+