From 38f7950d90c49ff27dcf2afb651337c2f4f2b663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Thu, 29 Jan 2015 21:00:58 +0100 Subject: [PATCH] Saving of allowed keys --- OpenKeychain/src/main/AndroidManifest.xml | 5 - .../keychain/provider/KeychainContract.java | 6 - .../keychain/provider/KeychainDatabase.java | 4 +- .../keychain/provider/KeychainProvider.java | 30 +- .../keychain/provider/ProviderHelper.java | 40 +++ .../remote/ui/AppSettingsActivity.java | 32 +- .../remote/ui/AppSettingsAllowedKeys.java | 292 ++++++++++++++++++ .../keychain/ui/KeyListFragment.java | 139 ++++----- .../keychain/ui/SelectPublicKeyActivity.java | 144 --------- .../res/layout/api_app_settings_activity.xml | 12 + OpenKeychain/src/main/res/values/strings.xml | 1 + extern/openpgp-api-lib | 2 +- 12 files changed, 471 insertions(+), 236 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeys.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index ff77e745b..d47729127 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -141,11 +141,6 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".ui.MainActivity" /> - + * + * 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.content.Context; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; +import org.sufficientlysecure.keychain.ui.widget.FixedListView; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +public class AppSettingsAllowedKeys extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks { + private static final String ARG_DATA_URI = "uri"; + + private SelectKeyCursorAdapter mAdapter; + private Set mSelectedMasterKeyIds; + private ProviderHelper mProviderHelper; + + private Uri mDataUri; + + /** + * Creates new instance of this fragment + */ + public static AppSettingsAllowedKeys newInstance(Uri dataUri) { + AppSettingsAllowedKeys frag = new AppSettingsAllowedKeys(); + Bundle args = new Bundle(); + + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mProviderHelper = new ProviderHelper(getActivity()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View layout = super.onCreateView(inflater, container, + savedInstanceState); + ListView lv = (ListView) layout.findViewById(android.R.id.list); + ViewGroup parent = (ViewGroup) lv.getParent(); + + /* + * http://stackoverflow.com/a/15880684 + * Remove ListView and add FixedListView in its place. + * This is done here programatically to be still able to use the progressBar of ListFragment. + * + * We want FixedListView to be able to put this ListFragment inside a ScrollView + */ + int lvIndex = parent.indexOfChild(lv); + parent.removeViewAt(lvIndex); + FixedListView newLv = new FixedListView(getActivity()); + newLv.setId(android.R.id.list); + parent.addView(newLv, lvIndex, lv.getLayoutParams()); + return layout; + } + + /** + * Define Adapter and Loader on create of Activity + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mDataUri = getArguments().getParcelable(ARG_DATA_URI); + + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + + // Give some text to display if there is no data. In a real + // application this would come from a resource. + setEmptyText(getString(R.string.list_empty)); + + mAdapter = new SecretKeyCursorAdapter(getActivity(), null, 0, getListView()); + + setListAdapter(mAdapter); + + // Start out with a progress indicator. + setListShown(false); + + mSelectedMasterKeyIds = mProviderHelper.getAllKeyIdsForApp(mDataUri); + Log.d(Constants.TAG, "allowed: " + mSelectedMasterKeyIds.toString()); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + /** + * Selects items based on master key ids in list view + * + * @param masterKeyIds + */ + private void preselectMasterKeyIds(Set masterKeyIds) { + for (int i = 0; i < getListView().getCount(); ++i) { + long listKeyId = mAdapter.getMasterKeyId(i); + for (long keyId : masterKeyIds) { + if (listKeyId == keyId) { + getListView().setItemChecked(i, true); + break; + } + } + } + } + + /** + * Returns all selected master key ids + * + * @return + */ + public Set getSelectedMasterKeyIds() { + // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key + // ids! + Set keyIds = new HashSet<>(); + for (int i = 0; i < getListView().getCount(); ++i) { + if (getListView().isItemChecked(i)) { + keyIds.add(mAdapter.getMasterKeyId(i)); + } + } + + return keyIds; + } + + /** + * Returns all selected user ids + * + * @return + */ + public String[] getSelectedUserIds() { + Vector userIds = new Vector<>(); + for (int i = 0; i < getListView().getCount(); ++i) { + if (getListView().isItemChecked(i)) { + userIds.add(mAdapter.getUserId(i)); + } + } + + // make empty array to not return null + String userIdArray[] = new String[0]; + return userIds.toArray(userIdArray); + } + + public void saveAllowedKeys() { + try { + mProviderHelper.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds()); + } catch (RemoteException | OperationApplicationException e) { + Log.e(Constants.TAG, "Problem saving allowed key ids!", e); + } + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); + + // These are the rows that we will retrieve. + String[] projection = new String[]{ + KeyRings._ID, + KeyRings.MASTER_KEY_ID, + KeyRings.USER_ID, + KeyRings.IS_EXPIRED, + KeyRings.IS_REVOKED, + KeyRings.HAS_ENCRYPT, + KeyRings.VERIFIED, + KeyRings.HAS_ANY_SECRET, + }; + + String inMasterKeyList = null; + if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.size() > 0) { + inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN ("; + Iterator iter = mSelectedMasterKeyIds.iterator(); + while (iter.hasNext()) { + inMasterKeyList += DatabaseUtils.sqlEscapeString("" + iter.next()); + if (iter.hasNext()) { + inMasterKeyList += ", "; + } + } + inMasterKeyList += ")"; + } + + String selection = KeyRings.HAS_ANY_SECRET + " != 0"; + + String orderBy = KeyRings.USER_ID + " ASC"; + if (inMasterKeyList != null) { + // sort by selected master keys + orderBy = inMasterKeyList + " DESC, " + orderBy; + } + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), baseUri, projection, selection, null, orderBy); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } + + // preselect given master keys + preselectMasterKeyIds(mSelectedMasterKeyIds); + } + + @Override + public void onLoaderReset(Loader loader) { + // This is called when the last Cursor provided to onLoadFinished() + // above is about to be closed. We need to make sure we are no + // longer using it. + mAdapter.swapCursor(null); + } + + private class SecretKeyCursorAdapter extends SelectKeyCursorAdapter { + + public SecretKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { + super(context, c, flags, listView); + } + + @Override + protected void initIndex(Cursor cursor) { + super.initIndex(cursor); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + super.bindView(view, context, cursor); + ViewHolderItem h = (ViewHolderItem) view.getTag(); + + // We care about the checkbox + h.selected.setVisibility(View.VISIBLE); + // the getListView works because this is not a static subclass! + h.selected.setChecked(getListView().isItemChecked(cursor.getPosition())); + + boolean enabled = false; + if ((Boolean) h.statusIcon.getTag()) { + h.statusIcon.setVisibility(View.GONE); + enabled = true; + } + + h.setEnabled(enabled); + } + + } + +} 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 4e5e8c631..8abb381f8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -110,7 +110,6 @@ public class KeyListFragment extends LoaderFragment super.onCreate(savedInstanceState); mExportHelper = new ExportHelper(getActivity()); - } /** @@ -205,84 +204,82 @@ public class KeyListFragment extends LoaderFragment mStickyList.setFastScrollEnabled(true); /* - * Multi-selection is only available for Android >= 3.0 + * Multi-selection */ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mStickyList.setFastScrollAlwaysVisible(true); + mStickyList.setFastScrollAlwaysVisible(true); - mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { + mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - android.view.MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.key_list_multi, menu); - mActionMode = mode; - return true; - } + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + android.view.MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.key_list_multi, menu); + mActionMode = mode; + return true; + } - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // get IDs for checked positions as long array - long[] ids; + // get IDs for checked positions as long array + long[] ids; - switch (item.getItemId()) { - case R.id.menu_key_list_multi_encrypt: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - encrypt(mode, ids); - break; - } - case R.id.menu_key_list_multi_delete: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected()); - break; - } - case R.id.menu_key_list_multi_export: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); - mExportHelper.showExportKeysDialog(ids, Constants.Path.APP_DIR_FILE, - mAdapter.isAnySecretSelected()); - break; - } - case R.id.menu_key_list_multi_select_all: { - // select all - for (int i = 0; i < mStickyList.getCount(); i++) { - mStickyList.setItemChecked(i, true); - } - break; - } + switch (item.getItemId()) { + case R.id.menu_key_list_multi_encrypt: { + ids = mAdapter.getCurrentSelectedMasterKeyIds(); + encrypt(mode, ids); + break; } - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - mActionMode = null; - mAdapter.clearSelection(); - } - - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - if (checked) { - mAdapter.setNewSelection(position, checked); - } else { - mAdapter.removeSelection(position); + case R.id.menu_key_list_multi_delete: { + ids = mAdapter.getCurrentSelectedMasterKeyIds(); + showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected()); + break; + } + case R.id.menu_key_list_multi_export: { + ids = mAdapter.getCurrentSelectedMasterKeyIds(); + ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); + mExportHelper.showExportKeysDialog(ids, Constants.Path.APP_DIR_FILE, + mAdapter.isAnySecretSelected()); + break; + } + case R.id.menu_key_list_multi_select_all: { + // select all + for (int i = 0; i < mStickyList.getCount(); i++) { + mStickyList.setItemChecked(i, true); + } + break; } - int count = mStickyList.getCheckedItemCount(); - String keysSelected = getResources().getQuantityString( - R.plurals.key_list_selected_keys, count, count); - mode.setTitle(keysSelected); } + return true; + } - }); - } + @Override + public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; + mAdapter.clearSelection(); + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + if (checked) { + mAdapter.setNewSelection(position, checked); + } else { + mAdapter.removeSelection(position); + } + int count = mStickyList.getCheckedItemCount(); + String keysSelected = getResources().getQuantityString( + R.plurals.key_list_selected_keys, count, count); + mode.setTitle(keysSelected); + } + + }); // We have a menu item to show in action bar. setHasOptionsMenu(true); @@ -369,9 +366,7 @@ public class KeyListFragment extends LoaderFragment // end action mode, if any if (mActionMode != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mActionMode.finish(); - } + mActionMode.finish(); } // The list should now be shown. @@ -401,7 +396,6 @@ public class KeyListFragment extends LoaderFragment startActivity(viewIntent); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) protected void encrypt(ActionMode mode, long[] masterKeyIds) { Intent intent = new Intent(getActivity(), EncryptFilesActivity.class); intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); @@ -418,7 +412,6 @@ public class KeyListFragment extends LoaderFragment * @param masterKeyIds * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) { // Can only work on singular secret keys if (hasSecret && masterKeyIds.length > 1) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java deleted file mode 100644 index 0e3374833..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyActivity.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann - * Copyright (C) 2010-2014 Thialfihar - * - * 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.ui; - -import android.content.Intent; -import android.os.Bundle; -import android.view.View; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; - -public class SelectPublicKeyActivity extends BaseActivity { - - // Actions for internal use only: - public static final String ACTION_SELECT_PUBLIC_KEYS = Constants.INTENT_PREFIX - + "SELECT_PUBLIC_KEYRINGS"; - - public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; - - public static final String RESULT_EXTRA_MASTER_KEY_IDS = "master_key_ids"; - public static final String RESULT_EXTRA_USER_IDS = "user_ids"; - - SelectPublicKeyFragment mSelectFragment; - - long mSelectedMasterKeyIds[]; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Inflate a "Done"/"Cancel" custom action bar view - setFullScreenDialogDoneClose(R.string.btn_okay, - new View.OnClickListener() { - @Override - public void onClick(View v) { - okClicked(); - } - }, - new View.OnClickListener() { - @Override - public void onClick(View v) { - cancelClicked(); - } - }); - - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - handleIntent(getIntent()); - - // Check that the activity is using the layout version with - // the fragment_container FrameLayout - if (findViewById(R.id.select_public_key_fragment_container) != null) { - - // 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 fragment - mSelectFragment = SelectPublicKeyFragment.newInstance(mSelectedMasterKeyIds); - - // Add the fragment to the 'fragment_container' FrameLayout - getSupportFragmentManager().beginTransaction() - .add(R.id.select_public_key_fragment_container, mSelectFragment).commit(); - } - - // TODO: reimplement! - // mFilterLayout = findViewById(R.id.layout_filter); - // mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); - // mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); - // - // mClearFilterButton.setOnClickListener(new OnClickListener() { - // public void onClick(View v) { - // handleIntent(new Intent()); - // } - // }); - - } - - @Override - protected void initLayout() { - setContentView(R.layout.select_public_key_activity); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - // TODO: reimplement search! - - // String searchString = null; - // if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - // searchString = intent.getStringExtra(SearchManager.QUERY); - // if (searchString != null && searchString.trim().length() == 0) { - // searchString = null; - // } - // } - - // if (searchString == null) { - // mFilterLayout.setVisibility(View.GONE); - // } else { - // mFilterLayout.setVisibility(View.VISIBLE); - // mFilterInfo.setText(getString(R.string.filterInfo, searchString)); - // } - - // preselected master keys - mSelectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); - } - - private void cancelClicked() { - setResult(RESULT_CANCELED, null); - finish(); - } - - private void okClicked() { - Intent data = new Intent(); - data.putExtra(RESULT_EXTRA_MASTER_KEY_IDS, mSelectFragment.getSelectedMasterKeyIds()); - data.putExtra(RESULT_EXTRA_USER_IDS, mSelectFragment.getSelectedUserIds()); - setResult(RESULT_OK, data); - finish(); - } - -} diff --git a/OpenKeychain/src/main/res/layout/api_app_settings_activity.xml b/OpenKeychain/src/main/res/layout/api_app_settings_activity.xml index 800e2eb87..63fc060f4 100644 --- a/OpenKeychain/src/main/res/layout/api_app_settings_activity.xml +++ b/OpenKeychain/src/main/res/layout/api_app_settings_activity.xml @@ -33,6 +33,18 @@ android:layout_height="wrap_content" tools:layout="@layout/api_app_settings_fragment" /> + + + + "Package Name" "SHA-256 of Package Signature" "Accounts" + "Allowed Keys" "Settings" "Account key:" "No accounts attached to this app." diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib index c8766a788..dccef45dc 160000 --- a/extern/openpgp-api-lib +++ b/extern/openpgp-api-lib @@ -1 +1 @@ -Subproject commit c8766a788884d331c0b61eb74241abd65e9b90b9 +Subproject commit dccef45dc1646d623c01dcde7a28711b2ac815e4