diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index fd0576100..7b4f3ee28 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -864,6 +864,11 @@ android:exported="false" android:theme="@style/Theme.Keychain.Transparent" android:label="@string/app_name" /> + duplicateEmails) { + Intent intent = new Intent(mContext, RemoteDeduplicateActivity.class); + + intent.putExtra(RemoteDeduplicateActivity.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(RemoteDeduplicateActivity.EXTRA_DUPLICATE_EMAILS, duplicateEmails); + + return createInternal(data, intent); + } + + PendingIntent createImportFromKeyserverPendingIntent(Intent data, long masterKeyId) { Intent intent = new Intent(mContext, RemoteImportKeysActivity.class); intent.setAction(RemoteImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 4ce5776fb..7fa062915 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -333,6 +333,7 @@ public class OpenPgpService extends Service { switch (combinedAutocryptState) { case EXTERNAL: + case SELECTED: case GOSSIP: case RESET: { result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_DISCOURAGE); 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 9310bec80..92eba893d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java @@ -18,7 +18,6 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; -import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -153,7 +152,7 @@ class OpenPgpServiceKeyIdExtractor { } if (!duplicateEmails.isEmpty()) { - return createDuplicateKeysResult(data, keyIds, missingEmails, duplicateEmails); + return createDuplicateKeysResult(data, callingPackageName, duplicateEmails); } if (keyIds.isEmpty()) { @@ -222,7 +221,7 @@ class OpenPgpServiceKeyIdExtractor { } enum AutocryptState { - EXTERNAL, RESET, GOSSIP, AVAILABLE, MUTUAL; + EXTERNAL, RESET, GOSSIP, SELECTED, AVAILABLE, MUTUAL; static AutocryptState fromDbValue(int state) { switch (state) { @@ -230,6 +229,8 @@ class OpenPgpServiceKeyIdExtractor { return RESET; case ApiAutocryptPeer.AVAILABLE: return AVAILABLE; + case ApiAutocryptPeer.SELECTED: + return SELECTED; case ApiAutocryptPeer.GOSSIP: return GOSSIP; case ApiAutocryptPeer.MUTUAL: @@ -249,6 +250,9 @@ class OpenPgpServiceKeyIdExtractor { if (this == GOSSIP || other == GOSSIP) { return GOSSIP; } + if (this == SELECTED || other == SELECTED) { + return SELECTED; + } if (this == AVAILABLE || other == AVAILABLE) { return AVAILABLE; } @@ -358,11 +362,9 @@ class OpenPgpServiceKeyIdExtractor { return new KeyIdResult(selectKeyPendingIntent, KeyIdResultStatus.NO_KEYS); } - private KeyIdResult createDuplicateKeysResult(Intent data, - HashSet selectedKeyIds, ArrayList missingEmails, ArrayList duplicateEmails) { - long[] keyIdsArray = KeyFormattingUtils.getUnboxedLongArray(selectedKeyIds); - PendingIntent selectKeyPendingIntent = apiPendingIntentFactory.createSelectPublicKeyPendingIntent( - data, keyIdsArray, missingEmails, duplicateEmails, false); + private KeyIdResult createDuplicateKeysResult(Intent data, String packageName, ArrayList duplicateEmails) { + PendingIntent selectKeyPendingIntent = apiPendingIntentFactory.createDeduplicatePendingIntent( + packageName, data, duplicateEmails); return new KeyIdResult(selectKeyPendingIntent, KeyIdResultStatus.DUPLICATE); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/KeyLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/KeyLoader.java new file mode 100644 index 000000000..53055b40a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/KeyLoader.java @@ -0,0 +1,149 @@ +/* + * 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.dialog; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.Nullable; +import android.support.v4.content.AsyncTaskLoader; + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.remote.ui.dialog.KeyLoader.KeyInfo; + + +public class KeyLoader extends AsyncTaskLoader> { + // These are the rows that we will retrieve. + private String[] QUERY_PROJECTION = new String[]{ + KeyRings._ID, + KeyRings.MASTER_KEY_ID, + KeyRings.CREATION, + KeyRings.HAS_ENCRYPT, + KeyRings.VERIFIED, + KeyRings.NAME, + KeyRings.EMAIL, + KeyRings.COMMENT, + KeyRings.IS_EXPIRED, + KeyRings.IS_REVOKED, + }; + private static final int INDEX_MASTER_KEY_ID = 1; + private static final int INDEX_CREATION = 2; + private static final int INDEX_HAS_ENCRYPT = 3; + private static final int INDEX_VERIFIED = 4; + private static final int INDEX_NAME = 5; + private static final int INDEX_EMAIL = 6; + private static final int INDEX_COMMENT = 7; + + private static final String QUERY_WHERE = Tables.KEYS + "." + KeyRings.IS_REVOKED + + " = 0 AND " + KeyRings.IS_EXPIRED + " = 0"; + private static final String QUERY_ORDER = Tables.KEYS + "." + KeyRings.CREATION + " DESC"; + + private final ContentResolver contentResolver; + private final String emailAddress; + + private List cachedResult; + + + KeyLoader(Context context, ContentResolver contentResolver, String emailAddress) { + super(context); + + this.contentResolver = contentResolver; + this.emailAddress = emailAddress; + } + + @Override + public List loadInBackground() { + ArrayList keyInfos = new ArrayList<>(); + + Cursor cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsFindByEmailUri(emailAddress), + QUERY_PROJECTION, QUERY_WHERE, null, QUERY_ORDER); + if (cursor == null) { + return null; + } + + while (cursor.moveToNext()) { + KeyInfo keyInfo = KeyInfo.fromCursor(cursor); + keyInfos.add(keyInfo); + } + + return Collections.unmodifiableList(keyInfos); + } + + @Override + public void deliverResult(List keySubkeyStatus) { + cachedResult = keySubkeyStatus; + + if (isStarted()) { + super.deliverResult(keySubkeyStatus); + } + } + + @Override + protected void onStartLoading() { + if (cachedResult != null) { + deliverResult(cachedResult); + } + + if (takeContentChanged() || cachedResult == null) { + forceLoad(); + } + } + + @Override + protected void onStopLoading() { + super.onStopLoading(); + + cachedResult = null; + } + + @AutoValue + public abstract static class KeyInfo { + public abstract long getMasterKeyId(); + public abstract long getCreationDate(); + public abstract boolean getHasEncrypt(); + public abstract boolean getIsVerified(); + + @Nullable + public abstract String getName(); + @Nullable + public abstract String getEmail(); + @Nullable + public abstract String getComment(); + + static KeyInfo fromCursor(Cursor cursor) { + long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); + long creationDate = cursor.getLong(INDEX_CREATION) * 1000L; + boolean hasEncrypt = cursor.getInt(INDEX_HAS_ENCRYPT) != 0; + boolean isVerified = cursor.getInt(INDEX_VERIFIED) == 2; + + String name = cursor.getString(INDEX_NAME); + String email = cursor.getString(INDEX_EMAIL); + String comment = cursor.getString(INDEX_COMMENT); + + return new AutoValue_KeyLoader_KeyInfo( + masterKeyId, creationDate, hasEncrypt, isVerified, name, email, comment); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java new file mode 100644 index 000000000..3872ce352 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java @@ -0,0 +1,331 @@ +/* + * 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.dialog; + + +import java.util.List; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Drawable.ConstantState; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.content.res.ResourcesCompat; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.Adapter; +import android.text.format.DateUtils; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.mikepenz.materialdrawer.util.KeyboardUtil; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.remote.ui.dialog.KeyLoader.KeyInfo; +import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicatePresenter.RemoteDeduplicateView; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; + + +public class RemoteDeduplicateActivity extends FragmentActivity { + public static final String EXTRA_PACKAGE_NAME = "package_name"; + public static final String EXTRA_DUPLICATE_EMAILS = "duplicate_emails"; + + public static final int LOADER_ID_KEYS = 0; + + + private RemoteDeduplicatePresenter presenter; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + presenter = new RemoteDeduplicatePresenter(getBaseContext(), LOADER_ID_KEYS); + + KeyboardUtil.hideKeyboard(this); + + if (savedInstanceState == null) { + RemoteDeduplicateDialogFragment frag = new RemoteDeduplicateDialogFragment(); + frag.show(getSupportFragmentManager(), "requestKeyDialog"); + } + } + + @Override + protected void onStart() { + super.onStart(); + + Intent intent = getIntent(); + List dupAddresses = intent.getStringArrayListExtra(EXTRA_DUPLICATE_EMAILS); + String duplicateAddress = dupAddresses.get(0); + String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); + + presenter.setupFromIntentData(packageName, duplicateAddress); + presenter.startLoaders(getSupportLoaderManager()); + } + + public static class RemoteDeduplicateDialogFragment extends DialogFragment { + private RemoteDeduplicatePresenter presenter; + private RemoteDeduplicateView mvpView; + + private Button buttonSelect; + private Button buttonCancel; + private RecyclerView keyChoiceList; + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity activity = getActivity(); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme); + + LayoutInflater layoutInflater = LayoutInflater.from(theme); + @SuppressLint("InflateParams") + View view = layoutInflater.inflate(R.layout.api_remote_deduplicate, null, false); + alert.setView(view); + + buttonSelect = (Button) view.findViewById(R.id.button_select); + buttonCancel = (Button) view.findViewById(R.id.button_cancel); + + keyChoiceList = (RecyclerView) view.findViewById(R.id.duplicate_key_list); + keyChoiceList.setLayoutManager(new LinearLayoutManager(activity)); + keyChoiceList.addItemDecoration(new DividerItemDecoration(activity, DividerItemDecoration.VERTICAL_LIST)); + + setupListenersForPresenter(); + mvpView = createMvpView(view, layoutInflater); + + return alert.create(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + presenter = ((RemoteDeduplicateActivity) 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 RemoteDeduplicateView createMvpView(View view, LayoutInflater layoutInflater) { + final ImageView iconClientApp = (ImageView) view.findViewById(R.id.icon_client_app); + final KeyChoiceAdapter keyChoiceAdapter = new KeyChoiceAdapter(layoutInflater, getResources()); + final TextView addressText = (TextView) view.findViewById(R.id.select_key_item_name); + keyChoiceList.setAdapter(keyChoiceAdapter); + + return new RemoteDeduplicateView() { + @Override + public void finish() { + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + activity.setResult(RESULT_OK, null); + activity.finish(); + } + + @Override + public void finishAsCancelled() { + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + activity.setResult(RESULT_CANCELED); + activity.finish(); + } + + @Override + public void setTitleClientIcon(Drawable drawable) { + iconClientApp.setImageDrawable(drawable); + keyChoiceAdapter.setSelectionDrawable(drawable); + } + + @Override + public void setAddressText(String text) { + addressText.setText(text); + } + + @Override + public void setKeyListData(List data) { + keyChoiceAdapter.setData(data); + } + + @Override + public void setActiveItem(Integer position) { + keyChoiceAdapter.setActiveItem(position); + } + + @Override + public void setEnableSelectButton(boolean enabled) { + buttonSelect.setEnabled(enabled); + } + }; + } + + private void setupListenersForPresenter() { + buttonSelect.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + presenter.onClickSelect(); + } + }); + + buttonCancel.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + presenter.onClickCancel(); + } + }); + + keyChoiceList.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), + new RecyclerItemClickListener.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + presenter.onKeyItemClick(position); + } + })); + } + } + + private static class KeyChoiceAdapter extends Adapter { + private final LayoutInflater layoutInflater; + private final Resources resources; + private List data; + private Drawable iconUnselected; + private Drawable iconSelected; + private Integer activeItem; + + KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) { + this.layoutInflater = layoutInflater; + this.resources = resources; + } + + @Override + public KeyChoiceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View keyChoiceItemView = layoutInflater.inflate(R.layout.duplicate_key_item, parent, false); + return new KeyChoiceViewHolder(keyChoiceItemView); + } + + @Override + public void onBindViewHolder(KeyChoiceViewHolder holder, int position) { + KeyInfo keyInfo = data.get(position); + Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected; + holder.bind(keyInfo, icon); + } + + @Override + public int getItemCount() { + return data != null ? data.size() : 0; + } + + public void setData(List data) { + this.data = data; + notifyDataSetChanged(); + } + + void setSelectionDrawable(Drawable drawable) { + ConstantState constantState = drawable.getConstantState(); + if (constantState == null) { + return; + } + + iconSelected = constantState.newDrawable(resources); + + iconUnselected = constantState.newDrawable(resources); + DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null)); + + notifyDataSetChanged(); + } + + void setActiveItem(Integer newActiveItem) { + Integer prevActiveItem = this.activeItem; + this.activeItem = newActiveItem; + + if (prevActiveItem != null) { + notifyItemChanged(prevActiveItem); + } + if (newActiveItem != null) { + notifyItemChanged(newActiveItem); + } + } + } + + private static class KeyChoiceViewHolder extends RecyclerView.ViewHolder { + private final TextView vName; + private final TextView vCreation; + private final ImageView vIcon; + + KeyChoiceViewHolder(View itemView) { + super(itemView); + + vName = (TextView) itemView.findViewById(R.id.key_list_item_name); + vCreation = (TextView) itemView.findViewById(R.id.key_list_item_creation); + vIcon = (ImageView) itemView.findViewById(R.id.key_list_item_icon); + } + + void bind(KeyInfo keyInfo, Drawable selectionIcon) { + vName.setText(keyInfo.getName()); + + Context context = vCreation.getContext(); + String dateTime = DateUtils.formatDateTime(context, keyInfo.getCreationDate(), + DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | + DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); + vCreation.setText(context.getString(R.string.label_key_created, dateTime)); + + vIcon.setImageDrawable(selectionIcon); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java new file mode 100644 index 000000000..5697dc9b3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -0,0 +1,152 @@ +/* + * 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.dialog; + + +import java.util.List; + +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.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.Loader; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; +import org.sufficientlysecure.keychain.remote.ui.dialog.KeyLoader.KeyInfo; +import org.sufficientlysecure.keychain.util.Log; + + +class RemoteDeduplicatePresenter implements LoaderCallbacks> { + private final PackageManager packageManager; + private final Context context; + private final int loaderId; + + + private AutocryptPeerDataAccessObject autocryptPeerDao; + private String duplicateAddress; + + private RemoteDeduplicateView view; + private Integer selectedItem; + private List keyInfoData; + + + RemoteDeduplicatePresenter(Context context, int loaderId) { + this.context = context; + + packageManager = context.getPackageManager(); + + this.loaderId = loaderId; + } + + public void setView(RemoteDeduplicateView view) { + this.view = view; + } + + void setupFromIntentData(String packageName, String duplicateAddress) { + try { + setPackageInfo(packageName); + } catch (NameNotFoundException e) { + Log.e(Constants.TAG, "Unable to find info of calling app!"); + view.finishAsCancelled(); + return; + } + + autocryptPeerDao = new AutocryptPeerDataAccessObject(context, packageName); + + this.duplicateAddress = duplicateAddress; + view.setAddressText(duplicateAddress); + } + + private void setPackageInfo(String packageName) throws NameNotFoundException { + ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0); + Drawable appIcon = packageManager.getApplicationIcon(applicationInfo); + + view.setTitleClientIcon(appIcon); + } + + void startLoaders(LoaderManager loaderManager) { + loaderManager.restartLoader(loaderId, null, this); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new KeyLoader(context, context.getContentResolver(), duplicateAddress); + } + + @Override + public void onLoadFinished(Loader> loader, List data) { + this.keyInfoData = data; + view.setKeyListData(data); + } + + @Override + public void onLoaderReset(Loader loader) { + view.setKeyListData(null); + } + + void onClickSelect() { + if (keyInfoData == null) { + Log.e(Constants.TAG, "got click on select with no data…?"); + return; + } + if (selectedItem == null) { + Log.e(Constants.TAG, "got click on select with no selection…?"); + return; + } + + long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId(); + autocryptPeerDao.updateToSelectedState(duplicateAddress, masterKeyId); + + view.finish(); + } + + void onClickCancel() { + view.finishAsCancelled(); + } + + public void onCancel() { + view.finishAsCancelled(); + } + + void onKeyItemClick(int position) { + if (selectedItem != null && position == selectedItem) { + selectedItem = null; + } else { + selectedItem = position; + } + view.setActiveItem(selectedItem); + view.setEnableSelectButton(selectedItem != null); + } + + interface RemoteDeduplicateView { + void finish(); + void finishAsCancelled(); + + void setAddressText(String text); + void setTitleClientIcon(Drawable drawable); + + void setKeyListData(List data); + void setActiveItem(Integer position); + void setEnableSelectButton(boolean enabled); + } +} diff --git a/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml b/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml new file mode 100644 index 000000000..b02f0f053 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +