diff options
author | Thialfihar <thialfihar@gmail.com> | 2010-07-16 20:13:12 +0000 |
---|---|---|
committer | Thialfihar <thialfihar@gmail.com> | 2010-07-16 20:13:12 +0000 |
commit | f5efd73d33e80657b1d4af300f0916ea756e5753 (patch) | |
tree | 98771e46577deaa22099e2eae0bd816033ada083 /src/org/thialfihar/android/apg/KeyListActivity.java | |
parent | ec10329664cbb86ba81d59be91a656a8d7752262 (diff) | |
download | open-keychain-f5efd73d33e80657b1d4af300f0916ea756e5753.tar.gz open-keychain-f5efd73d33e80657b1d4af300f0916ea756e5753.tar.bz2 open-keychain-f5efd73d33e80657b1d4af300f0916ea756e5753.zip |
set svn:eol-style to LF for all code/resource files
Diffstat (limited to 'src/org/thialfihar/android/apg/KeyListActivity.java')
-rw-r--r-- | src/org/thialfihar/android/apg/KeyListActivity.java | 1524 |
1 files changed, 762 insertions, 762 deletions
diff --git a/src/org/thialfihar/android/apg/KeyListActivity.java b/src/org/thialfihar/android/apg/KeyListActivity.java index 26c744f33..fc074e75d 100644 --- a/src/org/thialfihar/android/apg/KeyListActivity.java +++ b/src/org/thialfihar/android/apg/KeyListActivity.java @@ -1,762 +1,762 @@ -/*
- * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.thialfihar.android.apg;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Vector;
-
-import org.bouncycastle2.openpgp.PGPException;
-import org.bouncycastle2.openpgp.PGPPublicKeyRing;
-import org.bouncycastle2.openpgp.PGPSecretKeyRing;
-import org.thialfihar.android.apg.provider.KeyRings;
-import org.thialfihar.android.apg.provider.Keys;
-import org.thialfihar.android.apg.provider.UserIds;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.SearchManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Message;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.Button;
-import android.widget.ExpandableListView;
-import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class KeyListActivity extends BaseActivity {
- protected ExpandableListView mList;
- protected KeyListAdapter mListAdapter;
- protected View mFilterLayout;
- protected Button mClearFilterButton;
- protected TextView mFilterInfo;
-
- protected int mSelectedItem = -1;
- protected int mTask = 0;
-
- protected String mImportFilename = Constants.path.app_dir + "/";
- protected String mExportFilename = Constants.path.app_dir + "/";
-
- protected String mImportData;
-
- protected int mKeyType = Id.type.public_key;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.key_list);
-
- setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
-
- mList = (ExpandableListView) findViewById(R.id.list);
- registerForContextMenu(mList);
-
- mFilterLayout = (View) findViewById(R.id.layout_filter);
- mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
- mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
-
- mClearFilterButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- handleIntent(new Intent());
- }
- });
-
- handleIntent(getIntent());
-
- Intent intent = getIntent();
- if (Apg.Intent.IMPORT.equals(intent.getAction())) {
- if ("file".equals(intent.getScheme()) && intent.getDataString() != null) {
- mImportFilename = intent.getDataString().replace("file://", "");
- } else {
- mImportData = intent.getStringExtra(Apg.EXTRA_TEXT);
- }
- importKeys();
- }
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- handleIntent(intent);
- }
-
- private void handleIntent(Intent intent) {
- 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));
- }
-
- if (mListAdapter != null) {
- mListAdapter.cleanup();
- }
- mListAdapter = new KeyListAdapter(this, searchString);
- mList.setAdapter(mListAdapter);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case Id.menu.option.import_keys: {
- showDialog(Id.dialog.import_keys);
- return true;
- }
-
- case Id.menu.option.export_keys: {
- showDialog(Id.dialog.export_keys);
- return true;
- }
-
- default: {
- return super.onOptionsItemSelected(item);
- }
- }
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem menuItem) {
- ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo();
- int type = ExpandableListView.getPackedPositionType(info.packedPosition);
- int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
-
- if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
- return super.onContextItemSelected(menuItem);
- }
-
- switch (menuItem.getItemId()) {
- case Id.menu.export: {
- mSelectedItem = groupPosition;
- showDialog(Id.dialog.export_key);
- return true;
- }
-
- case Id.menu.delete: {
- mSelectedItem = groupPosition;
- showDialog(Id.dialog.delete_key);
- return true;
- }
-
- default: {
- return super.onContextItemSelected(menuItem);
- }
- }
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- boolean singleKeyExport = false;
-
- switch (id) {
- case Id.dialog.delete_key: {
- final int keyRingId = mListAdapter.getKeyRingId(mSelectedItem);
- mSelectedItem = -1;
- // TODO: better way to do this?
- String userId = "<unknown>";
- Object keyRing = Apg.getKeyRing(keyRingId);
- if (keyRing != null) {
- if (keyRing instanceof PGPPublicKeyRing) {
- userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey((PGPPublicKeyRing) keyRing));
- } else {
- userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey((PGPSecretKeyRing) keyRing));
- }
- }
-
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.warning);
- builder.setMessage(getString(mKeyType == Id.type.public_key ?
- R.string.keyDeletionConfirmation :
- R.string.secretKeyDeletionConfirmation, userId));
- builder.setIcon(android.R.drawable.ic_dialog_alert);
- builder.setPositiveButton(R.string.btn_delete,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- deleteKey(keyRingId);
- removeDialog(Id.dialog.delete_key);
- }
- });
- builder.setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- removeDialog(Id.dialog.delete_key);
- }
- });
- return builder.create();
- }
-
- case Id.dialog.import_keys: {
- return FileDialog.build(this, getString(R.string.title_importKeys),
- getString(R.string.specifyFileToImportFrom),
- mImportFilename,
- new FileDialog.OnClickListener() {
-
- @Override
- public void onOkClick(String filename) {
- removeDialog(Id.dialog.import_keys);
- mImportFilename = filename;
- importKeys();
- }
-
- @Override
- public void onCancelClick() {
- removeDialog(Id.dialog.import_keys);
- }
- },
- getString(R.string.filemanager_titleOpen),
- getString(R.string.filemanager_btnOpen),
- Id.request.filename);
- }
-
- case Id.dialog.export_key: {
- singleKeyExport = true;
- // break intentionally omitted, to use the Id.dialog.export_keys dialog
- }
-
- case Id.dialog.export_keys: {
- String title = (singleKeyExport ?
- getString(R.string.title_exportKey) :
- getString(R.string.title_exportKeys));
-
- final int thisDialogId = (singleKeyExport ? Id.dialog.export_key : Id.dialog.export_keys);
-
- return FileDialog.build(this, title,
- getString(mKeyType == Id.type.public_key ?
- R.string.specifyFileToExportTo :
- R.string.specifyFileToExportSecretKeysTo),
- mExportFilename,
- new FileDialog.OnClickListener() {
- @Override
- public void onOkClick(String filename) {
- removeDialog(thisDialogId);
- mExportFilename = filename;
- exportKeys();
- }
-
- @Override
- public void onCancelClick() {
- removeDialog(thisDialogId);
- }
- },
- getString(R.string.filemanager_titleSave),
- getString(R.string.filemanager_btnSave),
- Id.request.filename);
- }
-
- default: {
- return super.onCreateDialog(id);
- }
- }
- }
-
- public void importKeys() {
- showDialog(Id.dialog.importing);
- mTask = Id.task.import_keys;
- startThread();
- }
-
- public void exportKeys() {
- showDialog(Id.dialog.exporting);
- mTask = Id.task.export_keys;
- startThread();
- }
-
- @Override
- public void run() {
- String error = null;
- Bundle data = new Bundle();
- Message msg = new Message();
-
- try {
- InputStream importInputStream = null;
- OutputStream exportOutputStream = null;
- long size = 0;
- if (mTask == Id.task.import_keys) {
- if (mImportData != null) {
- byte[] bytes = mImportData.getBytes();
- size = bytes.length;
- importInputStream = new ByteArrayInputStream(bytes);
- } else {
- File file = new File(mImportFilename);
- size = file.length();
- importInputStream = new FileInputStream(file);
- }
- } else {
- exportOutputStream = new FileOutputStream(mExportFilename);
- }
-
- if (mTask == Id.task.import_keys) {
- data = Apg.importKeyRings(this, mKeyType, new InputData(importInputStream, size), this);
- } else {
- Vector<Integer> keyRingIds = new Vector<Integer>();
- if (mSelectedItem == -1) {
- keyRingIds = Apg.getKeyRingIds(mKeyType == Id.type.public_key ?
- Id.database.type_public :
- Id.database.type_secret);
- } else {
- int keyRingId = mListAdapter.getKeyRingId(mSelectedItem);
- keyRingIds.add(keyRingId);
- mSelectedItem = -1;
- }
- data = Apg.exportKeyRings(this, keyRingIds, exportOutputStream, this);
- }
- } catch (FileNotFoundException e) {
- error = getString(R.string.error_fileNotFound);
- } catch (IOException e) {
- error = "" + e;
- } catch (PGPException e) {
- error = "" + e;
- } catch (Apg.GeneralException e) {
- error = "" + e;
- }
-
- mImportData = null;
-
- if (mTask == Id.task.import_keys) {
- data.putInt(Apg.EXTRA_STATUS, Id.message.import_done);
- } else {
- data.putInt(Apg.EXTRA_STATUS, Id.message.export_done);
- }
-
- if (error != null) {
- data.putString(Apg.EXTRA_ERROR, error);
- }
-
- msg.setData(data);
- sendMessage(msg);
- }
-
- protected void deleteKey(int keyRingId) {
- Apg.deleteKey(keyRingId);
- refreshList();
- }
-
- protected void refreshList() {
- mListAdapter.rebuild(true);
- mListAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void doneCallback(Message msg) {
- super.doneCallback(msg);
-
- Bundle data = msg.getData();
- if (data != null) {
- int type = data.getInt(Apg.EXTRA_STATUS);
- switch (type) {
- case Id.message.import_done: {
- removeDialog(Id.dialog.importing);
-
- String error = data.getString(Apg.EXTRA_ERROR);
- if (error != null) {
- Toast.makeText(KeyListActivity.this,
- getString(R.string.errorMessage, error),
- Toast.LENGTH_SHORT).show();
- } else {
- int added = data.getInt("added");
- int updated = data.getInt("updated");
- String message;
- if (added > 0 && updated > 0) {
- message = getString(R.string.keysAddedAndUpdated, added, updated);
- } else if (added > 0) {
- message = getString(R.string.keysAdded, added);
- } else if (updated > 0) {
- message = getString(R.string.keysUpdated, updated);
- } else {
- message = getString(R.string.noKeysAddedOrUpdated);
- }
- Toast.makeText(KeyListActivity.this, message,
- Toast.LENGTH_SHORT).show();
- }
- refreshList();
- break;
- }
-
- case Id.message.export_done: {
- removeDialog(Id.dialog.exporting);
-
- String error = data.getString(Apg.EXTRA_ERROR);
- if (error != null) {
- Toast.makeText(KeyListActivity.this,
- getString(R.string.errorMessage, error),
- Toast.LENGTH_SHORT).show();
- } else {
- int exported = data.getInt("exported");
- String message;
- if (exported == 1) {
- message = getString(R.string.keyExported);
- } else if (exported > 0) {
- message = getString(R.string.keysExported, exported);
- } else{
- message = getString(R.string.noKeysExported);
- }
- Toast.makeText(KeyListActivity.this, message,
- Toast.LENGTH_SHORT).show();
- }
- break;
- }
-
- default: {
- break;
- }
- }
- }
- }
-
- protected class KeyListAdapter extends BaseExpandableListAdapter {
- private LayoutInflater mInflater;
- private Vector<Vector<KeyChild>> mChildren;
- private SQLiteDatabase mDatabase;
- private Cursor mCursor;
- private String mSearchString;
-
- private class KeyChild {
- public static final int KEY = 0;
- public static final int USER_ID = 1;
-
- public int type;
- public String userId;
- public long keyId;
- public boolean isMasterKey;
- public int algorithm;
- public int keySize;
- public boolean canSign;
- public boolean canEncrypt;
-
- public KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize,
- boolean canSign, boolean canEncrypt) {
- this.keyId = keyId;
- this.isMasterKey = isMasterKey;
- this.algorithm = algorithm;
- this.keySize = keySize;
- this.canSign = canSign;
- this.canEncrypt = canEncrypt;
- }
-
- public KeyChild(String userId) {
- type = USER_ID;
- this.userId = userId;
- }
- }
-
- public KeyListAdapter(Context context, String searchString) {
- mSearchString = searchString;
-
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mDatabase = Apg.getDatabase().db();
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
- "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
- Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
- Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
- ") " +
- " INNER JOIN " + UserIds.TABLE_NAME + " ON " +
- "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
- UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
- UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0')");
-
- if (searchString != null && searchString.trim().length() > 0) {
- String[] chunks = searchString.trim().split(" +");
- qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " +
- UserIds.TABLE_NAME + " AS tmp WHERE " +
- "tmp." + UserIds.KEY_ID + " = " +
- Keys.TABLE_NAME + "." + Keys._ID);
- for (int i = 0; i < chunks.length; ++i) {
- qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
- qb.appendWhereEscapeString("%" + chunks[i] + "%");
- }
- qb.appendWhere(")");
- }
-
- mCursor = qb.query(mDatabase,
- new String[] {
- KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0
- KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
- UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2
- },
- KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?",
- new String[] { "" + (mKeyType == Id.type.public_key ?
- Id.database.type_public : Id.database.type_secret) },
- null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC");
-
- // content provider way for reference, might have to go back to it sometime:
- /*Uri contentUri = null;
- if (mKeyType == Id.type.secret_key) {
- contentUri = Apg.CONTENT_URI_SECRET_KEY_RINGS;
- } else {
- contentUri = Apg.CONTENT_URI_PUBLIC_KEY_RINGS;
- }
- mCursor = getContentResolver().query(
- contentUri,
- new String[] {
- DataProvider._ID, // 0
- DataProvider.MASTER_KEY_ID, // 1
- DataProvider.USER_ID, // 2
- },
- null, null, null);*/
-
- startManagingCursor(mCursor);
- rebuild(false);
- }
-
- public void cleanup() {
- if (mCursor != null) {
- stopManagingCursor(mCursor);
- mCursor.close();
- }
- }
-
- public void rebuild(boolean requery) {
- if (requery) {
- mCursor.requery();
- }
- mChildren = new Vector<Vector<KeyChild>>();
- for (int i = 0; i < mCursor.getCount(); ++i) {
- mChildren.add(null);
- }
- }
-
- protected Vector<KeyChild> getChildrenOfGroup(int groupPosition) {
- Vector<KeyChild> children = mChildren.get(groupPosition);
- if (children != null) {
- return children;
- }
-
- mCursor.moveToPosition(groupPosition);
- children = new Vector<KeyChild>();
- Cursor c = mDatabase.query(Keys.TABLE_NAME,
- new String[] {
- Keys._ID, // 0
- Keys.KEY_ID, // 1
- Keys.IS_MASTER_KEY, // 2
- Keys.ALGORITHM, // 3
- Keys.KEY_SIZE, // 4
- Keys.CAN_SIGN, // 5
- Keys.CAN_ENCRYPT, // 6
- },
- Keys.KEY_RING_ID + " = ?",
- new String[] { mCursor.getString(0) },
- null, null, Keys.RANK + " ASC");
-
- long masterKeyId = -1;
- for (int i = 0; i < c.getCount(); ++i) {
- c.moveToPosition(i);
- children.add(new KeyChild(c.getLong(1), c.getInt(2) == 1, c.getInt(3), c.getInt(4),
- c.getInt(5) == 1, c.getInt(6) == 1));
- if (i == 0) {
- masterKeyId = c.getInt(0);
- }
- }
- c.close();
-
- if (masterKeyId != -1) {
- c = mDatabase.query(UserIds.TABLE_NAME,
- new String[] {
- UserIds.USER_ID, // 0
- },
- UserIds.KEY_ID + " = ? AND " + UserIds.RANK + " > 0",
- new String[] { "" + masterKeyId },
- null, null, UserIds.RANK + " ASC");
-
- for (int i = 0; i < c.getCount(); ++i) {
- c.moveToPosition(i);
- children.add(new KeyChild(c.getString(0)));
- }
- c.close();
- }
-
- mChildren.set(groupPosition, children);
- return children;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- @Override
- public boolean isChildSelectable(int groupPosition, int childPosition) {
- return true;
- }
-
- public int getGroupCount() {
- return mCursor.getCount();
- }
-
- public Object getChild(int groupPosition, int childPosition) {
- return null;
- }
-
- public long getChildId(int groupPosition, int childPosition) {
- return childPosition;
- }
-
- public int getChildrenCount(int groupPosition) {
- return getChildrenOfGroup(groupPosition).size();
- }
-
- public Object getGroup(int position) {
- return position;
- }
-
- public long getGroupId(int position) {
- mCursor.moveToPosition(position);
- return mCursor.getLong(1); // MASTER_KEY_ID
- }
-
- public int getKeyRingId(int position) {
- mCursor.moveToPosition(position);
- return mCursor.getInt(0); // _ID
- }
-
- public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
- ViewGroup parent) {
- mCursor.moveToPosition(groupPosition);
-
- View view = mInflater.inflate(R.layout.key_list_group_item, null);
- view.setBackgroundResource(android.R.drawable.list_selector_background);
-
- TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
- mainUserId.setText("");
- TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
- mainUserIdRest.setText("");
-
- String userId = mCursor.getString(2); // USER_ID
- if (userId != null) {
- String chunks[] = userId.split(" <", 2);
- userId = chunks[0];
- if (chunks.length > 1) {
- mainUserIdRest.setText("<" + chunks[1]);
- }
- mainUserId.setText(userId);
- }
-
- if (mainUserId.getText().length() == 0) {
- mainUserId.setText(R.string.unknownUserId);
- }
-
- if (mainUserIdRest.getText().length() == 0) {
- mainUserIdRest.setVisibility(View.GONE);
- }
- return view;
- }
-
- public View getChildView(int groupPosition, int childPosition,
- boolean isLastChild, View convertView,
- ViewGroup parent) {
- mCursor.moveToPosition(groupPosition);
-
- Vector<KeyChild> children = getChildrenOfGroup(groupPosition);
-
- KeyChild child = children.get(childPosition);
- View view = null;
- switch (child.type) {
- case KeyChild.KEY: {
- if (child.isMasterKey) {
- view = mInflater.inflate(R.layout.key_list_child_item_master_key, null);
- } else {
- view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null);
- }
-
- TextView keyId = (TextView) view.findViewById(R.id.keyId);
- String keyIdStr = Long.toHexString(child.keyId & 0xffffffffL);
- while (keyIdStr.length() < 8) {
- keyIdStr = "0" + keyIdStr;
- }
- keyId.setText(keyIdStr);
- TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
- String algorithmStr = Apg.getAlgorithmInfo(child.algorithm, child.keySize);
- keyDetails.setText("(" + algorithmStr + ")");
-
- ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
- if (!child.canEncrypt) {
- encryptIcon.setVisibility(View.GONE);
- }
-
- ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
- if (!child.canSign) {
- signIcon.setVisibility(View.GONE);
- }
- break;
- }
-
- case KeyChild.USER_ID: {
- view = mInflater.inflate(R.layout.key_list_child_item_user_id, null);
- TextView userId = (TextView) view.findViewById(R.id.userId);
- userId.setText(child.userId);
- break;
- }
- }
- return view;
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case Id.request.filename: {
- if (resultCode == RESULT_OK && data != null) {
- String filename = data.getDataString();
- if (filename != null) {
- // Get rid of URI prefix:
- if (filename.startsWith("file://")) {
- filename = filename.substring(7);
- }
- // replace %20 and so on
- filename = Uri.decode(filename);
-
- FileDialog.setFilename(filename);
- }
- }
- return;
- }
-
- default: {
- break;
- }
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-}
+/* + * Copyright (C) 2010 Thialfihar <thi@thialfihar.org> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +import org.bouncycastle2.openpgp.PGPException; +import org.bouncycastle2.openpgp.PGPPublicKeyRing; +import org.bouncycastle2.openpgp.PGPSecretKeyRing; +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.SearchManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.Button; +import android.widget.ExpandableListView; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +public class KeyListActivity extends BaseActivity { + protected ExpandableListView mList; + protected KeyListAdapter mListAdapter; + protected View mFilterLayout; + protected Button mClearFilterButton; + protected TextView mFilterInfo; + + protected int mSelectedItem = -1; + protected int mTask = 0; + + protected String mImportFilename = Constants.path.app_dir + "/"; + protected String mExportFilename = Constants.path.app_dir + "/"; + + protected String mImportData; + + protected int mKeyType = Id.type.public_key; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.key_list); + + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + + mList = (ExpandableListView) findViewById(R.id.list); + registerForContextMenu(mList); + + mFilterLayout = (View) findViewById(R.id.layout_filter); + mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); + mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); + + mClearFilterButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + handleIntent(new Intent()); + } + }); + + handleIntent(getIntent()); + + Intent intent = getIntent(); + if (Apg.Intent.IMPORT.equals(intent.getAction())) { + if ("file".equals(intent.getScheme()) && intent.getDataString() != null) { + mImportFilename = intent.getDataString().replace("file://", ""); + } else { + mImportData = intent.getStringExtra(Apg.EXTRA_TEXT); + } + importKeys(); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + 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)); + } + + if (mListAdapter != null) { + mListAdapter.cleanup(); + } + mListAdapter = new KeyListAdapter(this, searchString); + mList.setAdapter(mListAdapter); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.import_keys: { + showDialog(Id.dialog.import_keys); + return true; + } + + case Id.menu.option.export_keys: { + showDialog(Id.dialog.export_keys); + return true; + } + + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); + + if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + return super.onContextItemSelected(menuItem); + } + + switch (menuItem.getItemId()) { + case Id.menu.export: { + mSelectedItem = groupPosition; + showDialog(Id.dialog.export_key); + return true; + } + + case Id.menu.delete: { + mSelectedItem = groupPosition; + showDialog(Id.dialog.delete_key); + return true; + } + + default: { + return super.onContextItemSelected(menuItem); + } + } + } + + @Override + protected Dialog onCreateDialog(int id) { + boolean singleKeyExport = false; + + switch (id) { + case Id.dialog.delete_key: { + final int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); + mSelectedItem = -1; + // TODO: better way to do this? + String userId = "<unknown>"; + Object keyRing = Apg.getKeyRing(keyRingId); + if (keyRing != null) { + if (keyRing instanceof PGPPublicKeyRing) { + userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey((PGPPublicKeyRing) keyRing)); + } else { + userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey((PGPSecretKeyRing) keyRing)); + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.warning); + builder.setMessage(getString(mKeyType == Id.type.public_key ? + R.string.keyDeletionConfirmation : + R.string.secretKeyDeletionConfirmation, userId)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setPositiveButton(R.string.btn_delete, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + deleteKey(keyRingId); + removeDialog(Id.dialog.delete_key); + } + }); + builder.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.delete_key); + } + }); + return builder.create(); + } + + case Id.dialog.import_keys: { + return FileDialog.build(this, getString(R.string.title_importKeys), + getString(R.string.specifyFileToImportFrom), + mImportFilename, + new FileDialog.OnClickListener() { + + @Override + public void onOkClick(String filename) { + removeDialog(Id.dialog.import_keys); + mImportFilename = filename; + importKeys(); + } + + @Override + public void onCancelClick() { + removeDialog(Id.dialog.import_keys); + } + }, + getString(R.string.filemanager_titleOpen), + getString(R.string.filemanager_btnOpen), + Id.request.filename); + } + + case Id.dialog.export_key: { + singleKeyExport = true; + // break intentionally omitted, to use the Id.dialog.export_keys dialog + } + + case Id.dialog.export_keys: { + String title = (singleKeyExport ? + getString(R.string.title_exportKey) : + getString(R.string.title_exportKeys)); + + final int thisDialogId = (singleKeyExport ? Id.dialog.export_key : Id.dialog.export_keys); + + return FileDialog.build(this, title, + getString(mKeyType == Id.type.public_key ? + R.string.specifyFileToExportTo : + R.string.specifyFileToExportSecretKeysTo), + mExportFilename, + new FileDialog.OnClickListener() { + @Override + public void onOkClick(String filename) { + removeDialog(thisDialogId); + mExportFilename = filename; + exportKeys(); + } + + @Override + public void onCancelClick() { + removeDialog(thisDialogId); + } + }, + getString(R.string.filemanager_titleSave), + getString(R.string.filemanager_btnSave), + Id.request.filename); + } + + default: { + return super.onCreateDialog(id); + } + } + } + + public void importKeys() { + showDialog(Id.dialog.importing); + mTask = Id.task.import_keys; + startThread(); + } + + public void exportKeys() { + showDialog(Id.dialog.exporting); + mTask = Id.task.export_keys; + startThread(); + } + + @Override + public void run() { + String error = null; + Bundle data = new Bundle(); + Message msg = new Message(); + + try { + InputStream importInputStream = null; + OutputStream exportOutputStream = null; + long size = 0; + if (mTask == Id.task.import_keys) { + if (mImportData != null) { + byte[] bytes = mImportData.getBytes(); + size = bytes.length; + importInputStream = new ByteArrayInputStream(bytes); + } else { + File file = new File(mImportFilename); + size = file.length(); + importInputStream = new FileInputStream(file); + } + } else { + exportOutputStream = new FileOutputStream(mExportFilename); + } + + if (mTask == Id.task.import_keys) { + data = Apg.importKeyRings(this, mKeyType, new InputData(importInputStream, size), this); + } else { + Vector<Integer> keyRingIds = new Vector<Integer>(); + if (mSelectedItem == -1) { + keyRingIds = Apg.getKeyRingIds(mKeyType == Id.type.public_key ? + Id.database.type_public : + Id.database.type_secret); + } else { + int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); + keyRingIds.add(keyRingId); + mSelectedItem = -1; + } + data = Apg.exportKeyRings(this, keyRingIds, exportOutputStream, this); + } + } catch (FileNotFoundException e) { + error = getString(R.string.error_fileNotFound); + } catch (IOException e) { + error = "" + e; + } catch (PGPException e) { + error = "" + e; + } catch (Apg.GeneralException e) { + error = "" + e; + } + + mImportData = null; + + if (mTask == Id.task.import_keys) { + data.putInt(Apg.EXTRA_STATUS, Id.message.import_done); + } else { + data.putInt(Apg.EXTRA_STATUS, Id.message.export_done); + } + + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } + + msg.setData(data); + sendMessage(msg); + } + + protected void deleteKey(int keyRingId) { + Apg.deleteKey(keyRingId); + refreshList(); + } + + protected void refreshList() { + mListAdapter.rebuild(true); + mListAdapter.notifyDataSetChanged(); + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + Bundle data = msg.getData(); + if (data != null) { + int type = data.getInt(Apg.EXTRA_STATUS); + switch (type) { + case Id.message.import_done: { + removeDialog(Id.dialog.importing); + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(KeyListActivity.this, + getString(R.string.errorMessage, error), + Toast.LENGTH_SHORT).show(); + } else { + int added = data.getInt("added"); + int updated = data.getInt("updated"); + String message; + if (added > 0 && updated > 0) { + message = getString(R.string.keysAddedAndUpdated, added, updated); + } else if (added > 0) { + message = getString(R.string.keysAdded, added); + } else if (updated > 0) { + message = getString(R.string.keysUpdated, updated); + } else { + message = getString(R.string.noKeysAddedOrUpdated); + } + Toast.makeText(KeyListActivity.this, message, + Toast.LENGTH_SHORT).show(); + } + refreshList(); + break; + } + + case Id.message.export_done: { + removeDialog(Id.dialog.exporting); + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(KeyListActivity.this, + getString(R.string.errorMessage, error), + Toast.LENGTH_SHORT).show(); + } else { + int exported = data.getInt("exported"); + String message; + if (exported == 1) { + message = getString(R.string.keyExported); + } else if (exported > 0) { + message = getString(R.string.keysExported, exported); + } else{ + message = getString(R.string.noKeysExported); + } + Toast.makeText(KeyListActivity.this, message, + Toast.LENGTH_SHORT).show(); + } + break; + } + + default: { + break; + } + } + } + } + + protected class KeyListAdapter extends BaseExpandableListAdapter { + private LayoutInflater mInflater; + private Vector<Vector<KeyChild>> mChildren; + private SQLiteDatabase mDatabase; + private Cursor mCursor; + private String mSearchString; + + private class KeyChild { + public static final int KEY = 0; + public static final int USER_ID = 1; + + public int type; + public String userId; + public long keyId; + public boolean isMasterKey; + public int algorithm; + public int keySize; + public boolean canSign; + public boolean canEncrypt; + + public KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize, + boolean canSign, boolean canEncrypt) { + this.keyId = keyId; + this.isMasterKey = isMasterKey; + this.algorithm = algorithm; + this.keySize = keySize; + this.canSign = canSign; + this.canEncrypt = canEncrypt; + } + + public KeyChild(String userId) { + type = USER_ID; + this.userId = userId; + } + } + + public KeyListAdapter(Context context, String searchString) { + mSearchString = searchString; + + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDatabase = Apg.getDatabase().db(); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0')"); + + if (searchString != null && searchString.trim().length() > 0) { + String[] chunks = searchString.trim().split(" +"); + qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + + UserIds.TABLE_NAME + " AS tmp WHERE " + + "tmp." + UserIds.KEY_ID + " = " + + Keys.TABLE_NAME + "." + Keys._ID); + for (int i = 0; i < chunks.length; ++i) { + qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); + qb.appendWhereEscapeString("%" + chunks[i] + "%"); + } + qb.appendWhere(")"); + } + + mCursor = qb.query(mDatabase, + new String[] { + KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 + UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 + }, + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { "" + (mKeyType == Id.type.public_key ? + Id.database.type_public : Id.database.type_secret) }, + null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"); + + // content provider way for reference, might have to go back to it sometime: + /*Uri contentUri = null; + if (mKeyType == Id.type.secret_key) { + contentUri = Apg.CONTENT_URI_SECRET_KEY_RINGS; + } else { + contentUri = Apg.CONTENT_URI_PUBLIC_KEY_RINGS; + } + mCursor = getContentResolver().query( + contentUri, + new String[] { + DataProvider._ID, // 0 + DataProvider.MASTER_KEY_ID, // 1 + DataProvider.USER_ID, // 2 + }, + null, null, null);*/ + + startManagingCursor(mCursor); + rebuild(false); + } + + public void cleanup() { + if (mCursor != null) { + stopManagingCursor(mCursor); + mCursor.close(); + } + } + + public void rebuild(boolean requery) { + if (requery) { + mCursor.requery(); + } + mChildren = new Vector<Vector<KeyChild>>(); + for (int i = 0; i < mCursor.getCount(); ++i) { + mChildren.add(null); + } + } + + protected Vector<KeyChild> getChildrenOfGroup(int groupPosition) { + Vector<KeyChild> children = mChildren.get(groupPosition); + if (children != null) { + return children; + } + + mCursor.moveToPosition(groupPosition); + children = new Vector<KeyChild>(); + Cursor c = mDatabase.query(Keys.TABLE_NAME, + new String[] { + Keys._ID, // 0 + Keys.KEY_ID, // 1 + Keys.IS_MASTER_KEY, // 2 + Keys.ALGORITHM, // 3 + Keys.KEY_SIZE, // 4 + Keys.CAN_SIGN, // 5 + Keys.CAN_ENCRYPT, // 6 + }, + Keys.KEY_RING_ID + " = ?", + new String[] { mCursor.getString(0) }, + null, null, Keys.RANK + " ASC"); + + long masterKeyId = -1; + for (int i = 0; i < c.getCount(); ++i) { + c.moveToPosition(i); + children.add(new KeyChild(c.getLong(1), c.getInt(2) == 1, c.getInt(3), c.getInt(4), + c.getInt(5) == 1, c.getInt(6) == 1)); + if (i == 0) { + masterKeyId = c.getInt(0); + } + } + c.close(); + + if (masterKeyId != -1) { + c = mDatabase.query(UserIds.TABLE_NAME, + new String[] { + UserIds.USER_ID, // 0 + }, + UserIds.KEY_ID + " = ? AND " + UserIds.RANK + " > 0", + new String[] { "" + masterKeyId }, + null, null, UserIds.RANK + " ASC"); + + for (int i = 0; i < c.getCount(); ++i) { + c.moveToPosition(i); + children.add(new KeyChild(c.getString(0))); + } + c.close(); + } + + mChildren.set(groupPosition, children); + return children; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public int getGroupCount() { + return mCursor.getCount(); + } + + public Object getChild(int groupPosition, int childPosition) { + return null; + } + + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + public int getChildrenCount(int groupPosition) { + return getChildrenOfGroup(groupPosition).size(); + } + + public Object getGroup(int position) { + return position; + } + + public long getGroupId(int position) { + mCursor.moveToPosition(position); + return mCursor.getLong(1); // MASTER_KEY_ID + } + + public int getKeyRingId(int position) { + mCursor.moveToPosition(position); + return mCursor.getInt(0); // _ID + } + + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) { + mCursor.moveToPosition(groupPosition); + + View view = mInflater.inflate(R.layout.key_list_group_item, null); + view.setBackgroundResource(android.R.drawable.list_selector_background); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(""); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + + String userId = mCursor.getString(2); // USER_ID + if (userId != null) { + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mainUserIdRest.setText("<" + chunks[1]); + } + mainUserId.setText(userId); + } + + if (mainUserId.getText().length() == 0) { + mainUserId.setText(R.string.unknownUserId); + } + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } + return view; + } + + public View getChildView(int groupPosition, int childPosition, + boolean isLastChild, View convertView, + ViewGroup parent) { + mCursor.moveToPosition(groupPosition); + + Vector<KeyChild> children = getChildrenOfGroup(groupPosition); + + KeyChild child = children.get(childPosition); + View view = null; + switch (child.type) { + case KeyChild.KEY: { + if (child.isMasterKey) { + view = mInflater.inflate(R.layout.key_list_child_item_master_key, null); + } else { + view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null); + } + + TextView keyId = (TextView) view.findViewById(R.id.keyId); + String keyIdStr = Long.toHexString(child.keyId & 0xffffffffL); + while (keyIdStr.length() < 8) { + keyIdStr = "0" + keyIdStr; + } + keyId.setText(keyIdStr); + TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); + String algorithmStr = Apg.getAlgorithmInfo(child.algorithm, child.keySize); + keyDetails.setText("(" + algorithmStr + ")"); + + ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); + if (!child.canEncrypt) { + encryptIcon.setVisibility(View.GONE); + } + + ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); + if (!child.canSign) { + signIcon.setVisibility(View.GONE); + } + break; + } + + case KeyChild.USER_ID: { + view = mInflater.inflate(R.layout.key_list_child_item_user_id, null); + TextView userId = (TextView) view.findViewById(R.id.userId); + userId.setText(child.userId); + break; + } + } + return view; + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.filename: { + if (resultCode == RESULT_OK && data != null) { + String filename = data.getDataString(); + if (filename != null) { + // Get rid of URI prefix: + if (filename.startsWith("file://")) { + filename = filename.substring(7); + } + // replace %20 and so on + filename = Uri.decode(filename); + + FileDialog.setFilename(filename); + } + } + return; + } + + default: { + break; + } + } + super.onActivityResult(requestCode, resultCode, data); + } +} |