Merge pull request #395 from uberspot/master

Remove duplicate code from ActionBarHelper. Add export in multiselect.
This commit is contained in:
Dominik Schürmann 2014-03-12 16:23:22 +01:00
commit a9e5619a14
41 changed files with 608 additions and 280 deletions

View File

@ -50,6 +50,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! --> <!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
<application <application

View File

@ -43,6 +43,8 @@ public final class Constants {
public static final class path { public static final class path {
public static final String APP_DIR = Environment.getExternalStorageDirectory() public static final String APP_DIR = Environment.getExternalStorageDirectory()
+ "/OpenPGP-Keychain"; + "/OpenPGP-Keychain";
public static final String APP_DIR_FILE_SEC = APP_DIR + "/secexport.asc";
public static final String APP_DIR_FILE_PUB = APP_DIR + "/pubexport.asc";
} }
public static final class pref { public static final class pref {

View File

@ -56,28 +56,33 @@ public class ActionBarHelper {
* Sets custom view on ActionBar for Done/Cancel activities * Sets custom view on ActionBar for Done/Cancel activities
* *
* @param actionBar * @param actionBar
* @param doneText * @param firstText
* @param doneOnClickListener * @param firstDrawableId
* @param cancelText * @param firstOnClickListener
* @param cancelOnClickListener * @param secondText
* @param secondDrawableId
* @param secondOnClickListener
*/ */
public static void setDoneCancelView(ActionBar actionBar, int doneText, public static void setTwoButtonView(ActionBar actionBar, int firstText, int firstDrawableId,
OnClickListener doneOnClickListener, int cancelText, OnClickListener firstOnClickListener, int secondText, int secondDrawableId,
OnClickListener cancelOnClickListener) { OnClickListener secondOnClickListener) {
// Inflate a "Done"/"Cancel" custom action bar view // Inflate the custom action bar view
final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater.inflate( final View customActionBarView = inflater.inflate(
R.layout.actionbar_custom_view_done_cancel, null); R.layout.actionbar_custom_view_done_cancel, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText); TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text));
firstTextView.setText(firstText);
firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0);
customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener( customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
doneOnClickListener); firstOnClickListener);
((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text)) TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text));
.setText(cancelText); secondTextView.setText(secondText);
secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0);
customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener( customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
cancelOnClickListener); secondOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title. // Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowTitleEnabled(false);
@ -91,20 +96,22 @@ public class ActionBarHelper {
* Sets custom view on ActionBar for Done activities * Sets custom view on ActionBar for Done activities
* *
* @param actionBar * @param actionBar
* @param doneText * @param firstText
* @param doneOnClickListener * @param firstOnClickListener
*/ */
public static void setDoneView(ActionBar actionBar, int doneText, public static void setOneButtonView(ActionBar actionBar, int firstText, int firstDrawableId,
OnClickListener doneOnClickListener) { OnClickListener firstOnClickListener) {
// Inflate a "Done" custom action bar view to serve as the "Up" affordance. // Inflate a "Done" custom action bar view to serve as the "Up" affordance.
final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater final View customActionBarView = inflater
.inflate(R.layout.actionbar_custom_view_done, null); .inflate(R.layout.actionbar_custom_view_done, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText); TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text));
firstTextView.setText(firstText);
firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0);
customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener( customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
doneOnClickListener); firstOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title. // Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowTitleEnabled(false);
@ -112,65 +119,4 @@ public class ActionBarHelper {
actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(customActionBarView); actionBar.setCustomView(customActionBarView);
} }
/**
* Sets custom view on ActionBar for Save activities
*
* @param actionBar
* @param saveText
* @param saveOnClickListener
*/
public static void setSaveView(ActionBar actionBar, int saveText,
OnClickListener saveOnClickListener) {
// Inflate a "Save" custom action bar view to serve as the "Up" affordance.
final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater
.inflate(R.layout.actionbar_custom_view_save, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_save_text)).setText(saveText);
customActionBarView.findViewById(R.id.actionbar_save).setOnClickListener(
saveOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(customActionBarView);
}
/**
* Sets custom view on ActionBar for Save/Cancel activities
*
* @param actionBar
* @param saveText
* @param saveOnClickListener
* @param cancelText
* @param cancelOnClickListener
*/
public static void setSaveCancelView(ActionBar actionBar, int saveText,
OnClickListener saveOnClickListener, int cancelText,
OnClickListener cancelOnClickListener) {
// Inflate a "Done"/"Cancel" custom action bar view
final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater.inflate(
R.layout.actionbar_custom_view_save_cancel, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_save_text)).setText(saveText);
customActionBarView.findViewById(R.id.actionbar_save).setOnClickListener(
saveOnClickListener);
((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text))
.setText(cancelText);
customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
cancelOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
} }

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.sufficientlysecure.keychain.helper;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.util.Patterns;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ContactHelper {
public static final List<String> getMailAccounts(Context context) {
final Account[] accounts = AccountManager.get(context).getAccounts();
final Set<String> emailSet = new HashSet<String>();
for (Account account : accounts) {
if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
emailSet.add(account.name);
}
}
return new ArrayList<String>(emailSet);
}
}

View File

@ -20,7 +20,6 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
@ -63,7 +62,7 @@ public class ExportHelper {
/** /**
* Show dialog where to export keys * Show dialog where to export keys
*/ */
public void showExportKeysDialog(final Uri dataUri, final int keyType, public void showExportKeysDialog(final long[] rowIds, final int keyType,
final String exportFilename) { final String exportFilename) {
mExportFilename = exportFilename; mExportFilename = exportFilename;
@ -75,7 +74,7 @@ public class ExportHelper {
Bundle data = message.getData(); Bundle data = message.getData();
mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
exportKeys(dataUri, keyType); exportKeys(rowIds, keyType);
} }
} }
}; };
@ -86,7 +85,7 @@ public class ExportHelper {
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
public void run() { public void run() {
String title = null; String title = null;
if (dataUri == null) { if (rowIds == null) {
// export all keys // export all keys
title = activity.getString(R.string.title_export_keys); title = activity.getString(R.string.title_export_keys);
} else { } else {
@ -112,7 +111,7 @@ public class ExportHelper {
/** /**
* Export keys * Export keys
*/ */
public void exportKeys(Uri dataUri, int keyType) { public void exportKeys(long[] rowIds, int keyType) {
Log.d(Constants.TAG, "exportKeys started"); Log.d(Constants.TAG, "exportKeys started");
// Send all information needed to service to export key in other thread // Send all information needed to service to export key in other thread
@ -126,13 +125,10 @@ public class ExportHelper {
data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);
data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType); data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType);
if (dataUri == null) { if (rowIds == null) {
data.putBoolean(KeychainIntentService.EXPORT_ALL, true); data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
} else { } else {
// TODO: put data uri into service??? data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_ROW_ID, rowIds);
long keyRingMasterKeyId = ProviderHelper.getMasterKeyId(activity, dataUri);
data.putLong(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, keyRingMasterKeyId);
} }
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);

View File

@ -144,8 +144,8 @@ public class Preferences {
Constants.defaults.KEY_SERVERS); Constants.defaults.KEY_SERVERS);
Vector<String> servers = new Vector<String>(); Vector<String> servers = new Vector<String>();
String chunks[] = rawData.split(","); String chunks[] = rawData.split(",");
for (int i = 0; i < chunks.length; ++i) { for (String c : chunks) {
String tmp = chunks[i].trim(); String tmp = c.trim();
if (tmp.length() > 0) { if (tmp.length() > 0) {
servers.add(tmp); servers.add(tmp);
} }
@ -156,8 +156,8 @@ public class Preferences {
public void setKeyServers(String[] value) { public void setKeyServers(String[] value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
String rawData = ""; String rawData = "";
for (int i = 0; i < value.length; ++i) { for (String v : value) {
String tmp = value[i].trim(); String tmp = v.trim();
if (tmp.length() == 0) { if (tmp.length() == 0) {
continue; continue;
} }

View File

@ -193,11 +193,10 @@ public class PgpHelper {
* @param context * @param context
* @param progress * @param progress
* @param file * @param file
* @throws FileNotFoundException
* @throws IOException * @throws IOException
*/ */
public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file) public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file)
throws FileNotFoundException, IOException { throws IOException {
long length = file.length(); long length = file.length();
SecureRandom random = new SecureRandom(); SecureRandom random = new SecureRandom();
RandomAccessFile raf = new RandomAccessFile(file, "rws"); RandomAccessFile raf = new RandomAccessFile(file, "rws");

View File

@ -79,8 +79,9 @@ public class PgpImportExport {
public boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) { public boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = new ArmoredOutputStream(bos); ArmoredOutputStream aos = null;
try { try {
aos = new ArmoredOutputStream(bos);
aos.write(keyring.getEncoded()); aos.write(keyring.getEncoded());
aos.close(); aos.close();
@ -95,7 +96,8 @@ public class PgpImportExport {
return false; return false;
} finally { } finally {
try { try {
bos.close(); if (aos != null) aos.close();
if (bos != null) bos.close();
} catch (IOException e) { } catch (IOException e) {
} }
} }
@ -155,59 +157,53 @@ public class PgpImportExport {
return returnData; return returnData;
} }
public Bundle exportKeyRings(ArrayList<Long> keyRingMasterKeyIds, int keyType, public Bundle exportKeyRings(ArrayList<Long> keyRingRowIds, int keyType,
OutputStream outStream) throws PgpGeneralException, FileNotFoundException, OutputStream outStream) throws PgpGeneralException,
PGPException, IOException { PGPException, IOException {
Bundle returnData = new Bundle(); Bundle returnData = new Bundle();
int rowIdsSize = keyRingRowIds.size();
updateProgress( updateProgress(
mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
keyRingMasterKeyIds.size()), 0, 100); rowIdsSize), 0, 100);
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
throw new PgpGeneralException( throw new PgpGeneralException(
mContext.getString(R.string.error_external_storage_not_ready)); mContext.getString(R.string.error_external_storage_not_ready));
} }
// For each row id
for (int i = 0; i < rowIdsSize; ++i) {
// Create an output stream
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
// If the keyType is secret get the PGPSecretKeyRing
// based on the row id and encode it to the output
if (keyType == Id.type.secret_key) { if (keyType == Id.type.secret_key) {
ArmoredOutputStream outSec = new ArmoredOutputStream(outStream); updateProgress(i * 100 / rowIdsSize / 2, 100);
outSec.setHeader("Version", PgpHelper.getFullVersion(mContext)); PGPSecretKeyRing secretKeyRing =
ProviderHelper.getPGPSecretKeyRingByRowId(mContext, keyRingRowIds.get(i));
for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) {
updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100);
PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
mContext, keyRingMasterKeyIds.get(i));
if (secretKeyRing != null) { if (secretKeyRing != null) {
secretKeyRing.encode(outSec); secretKeyRing.encode(arOutStream);
} }
} // Else if it's a public key get the PGPPublicKeyRing
outSec.close(); // and encode that to the output
} else { } else {
// export public keyrings... updateProgress(i * 100 / rowIdsSize, 100);
ArmoredOutputStream outPub = new ArmoredOutputStream(outStream); PGPPublicKeyRing publicKeyRing =
outPub.setHeader("Version", PgpHelper.getFullVersion(mContext)); ProviderHelper.getPGPPublicKeyRingByRowId(mContext, keyRingRowIds.get(i));
for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) {
// double the needed time if exporting both public and secret parts
if (keyType == Id.type.secret_key) {
updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100);
} else {
updateProgress(i * 100 / keyRingMasterKeyIds.size(), 100);
}
PGPPublicKeyRing publicKeyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(
mContext, keyRingMasterKeyIds.get(i));
if (publicKeyRing != null) { if (publicKeyRing != null) {
publicKeyRing.encode(outPub); publicKeyRing.encode(arOutStream);
} }
} }
outPub.close();
}
returnData.putInt(KeychainIntentService.RESULT_EXPORT, keyRingMasterKeyIds.size()); arOutStream.close();
}
returnData.putInt(KeychainIntentService.RESULT_EXPORT, rowIdsSize);
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);

View File

@ -190,7 +190,7 @@ public class PgpKeyOperation {
} }
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase, public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
String newPassPhrase) throws IOException, PGPException, PGPException, String newPassPhrase) throws IOException, PGPException,
NoSuchProviderException { NoSuchProviderException {
updateProgress(R.string.progress_building_key, 0, 100); updateProgress(R.string.progress_building_key, 0, 100);

View File

@ -404,6 +404,30 @@ public class ProviderHelper {
return masterKeyIds; return masterKeyIds;
} }
/**
* Private helper method
*/
private static ArrayList<Long> getKeyRingsRowIds(Context context, Uri queryUri) {
Cursor cursor = context.getContentResolver().query(queryUri,
new String[]{KeyRings._ID}, null, null, null);
ArrayList<Long> rowIds = new ArrayList<Long>();
if (cursor != null) {
int IdCol = cursor.getColumnIndex(KeyRings._ID);
if (cursor.moveToFirst()) {
do {
rowIds.add(cursor.getLong(IdCol));
} while (cursor.moveToNext());
}
}
if (cursor != null) {
cursor.close();
}
return rowIds;
}
/** /**
* Retrieves ids of all SecretKeyRings * Retrieves ids of all SecretKeyRings
*/ */
@ -420,6 +444,22 @@ public class ProviderHelper {
return getKeyRingsMasterKeyIds(context, queryUri); return getKeyRingsMasterKeyIds(context, queryUri);
} }
/**
* Retrieves ids of all SecretKeyRings
*/
public static ArrayList<Long> getSecretKeyRingsRowIds(Context context) {
Uri queryUri = KeyRings.buildSecretKeyRingsUri();
return getKeyRingsRowIds(context, queryUri);
}
/**
* Retrieves ids of all PublicKeyRings
*/
public static ArrayList<Long> getPublicKeyRingsRowIds(Context context) {
Uri queryUri = KeyRings.buildPublicKeyRingsUri();
return getKeyRingsRowIds(context, queryUri);
}
public static void deletePublicKeyRing(Context context, long rowId) { public static void deletePublicKeyRing(Context context, long rowId) {
ContentResolver cr = context.getContentResolver(); ContentResolver cr = context.getContentResolver();
cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null); cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null);

View File

@ -50,6 +50,7 @@ import org.sufficientlysecure.keychain.pgp.PgpImportExport;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream; import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
@ -153,6 +154,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
public static final String EXPORT_KEY_TYPE = "export_key_type"; public static final String EXPORT_KEY_TYPE = "export_key_type";
public static final String EXPORT_ALL = "export_all"; public static final String EXPORT_ALL = "export_all";
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id"; public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
public static final String EXPORT_KEY_RING_ROW_ID = "export_key_rind_row_id";
// upload key // upload key
public static final String UPLOAD_KEY_SERVER = "upload_key_server"; public static final String UPLOAD_KEY_SERVER = "upload_key_server";
@ -675,10 +677,12 @@ public class KeychainIntentService extends IntentService implements ProgressDial
String outputFile = data.getString(EXPORT_FILENAME); String outputFile = data.getString(EXPORT_FILENAME);
long[] rowIds = new long[0];
// If not exporting all keys get the rowIds of the keys to export from the intent
boolean exportAll = data.getBoolean(EXPORT_ALL); boolean exportAll = data.getBoolean(EXPORT_ALL);
long keyRingMasterKeyId = -1;
if (!exportAll) { if (!exportAll) {
keyRingMasterKeyId = data.getLong(EXPORT_KEY_RING_MASTER_KEY_ID); rowIds = data.getLongArray(EXPORT_KEY_RING_ROW_ID);
} }
/* Operation */ /* Operation */
@ -691,24 +695,26 @@ public class KeychainIntentService extends IntentService implements ProgressDial
// OutputStream // OutputStream
FileOutputStream outStream = new FileOutputStream(outputFile); FileOutputStream outStream = new FileOutputStream(outputFile);
ArrayList<Long> keyRingMasterKeyIds = new ArrayList<Long>(); ArrayList<Long> keyRingRowIds = new ArrayList<Long>();
if (exportAll) { if (exportAll) {
// get all key ring row ids based on export type // get all key ring row ids based on export type
if (keyType == Id.type.public_key) { if (keyType == Id.type.public_key) {
keyRingMasterKeyIds = ProviderHelper.getPublicKeyRingsMasterKeyIds(this); keyRingRowIds = ProviderHelper.getPublicKeyRingsRowIds(this);
} else { } else {
keyRingMasterKeyIds = ProviderHelper.getSecretKeyRingsMasterKeyIds(this); keyRingRowIds = ProviderHelper.getSecretKeyRingsRowIds(this);
} }
} else { } else {
keyRingMasterKeyIds.add(keyRingMasterKeyId); for(long rowId : rowIds) {
keyRingRowIds.add(rowId);
}
} }
Bundle resultData = new Bundle(); Bundle resultData;
PgpImportExport pgpImportExport = new PgpImportExport(this, this); PgpImportExport pgpImportExport = new PgpImportExport(this, this);
resultData = pgpImportExport resultData = pgpImportExport
.exportKeyRings(keyRingMasterKeyIds, keyType, outStream); .exportKeyRings(keyRingRowIds, keyType, outStream);
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) { } catch (Exception e) {

View File

@ -21,6 +21,7 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import android.util.LongSparseArray;
import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
@ -77,7 +78,7 @@ public class PassphraseCacheService extends Service {
private BroadcastReceiver mIntentReceiver; private BroadcastReceiver mIntentReceiver;
private HashMap<Long, String> mPassphraseCache = new HashMap<Long, String>(); private LongSparseArray<String> mPassphraseCache = new LongSparseArray<String>();
Context mContext; Context mContext;
@ -347,7 +348,7 @@ public class PassphraseCacheService extends Service {
Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!"); Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!");
// stop whole service if no cached passphrases remaining // stop whole service if no cached passphrases remaining
if (mPassphraseCache.isEmpty()) { if (mPassphraseCache.size() == 0) {
Log.d(TAG, "No passphrases remaining in memory, stopping service!"); Log.d(TAG, "No passphrases remaining in memory, stopping service!");
stopSelf(); stopSelf();
} }

View File

@ -41,7 +41,7 @@ public class AppSettingsActivity extends ActionBarActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Inflate a "Done" custom action bar // Inflate a "Done" custom action bar
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.api_settings_save, ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.api_settings_save, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {

View File

@ -88,7 +88,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.api_register_allow, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -108,7 +108,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
RemoteServiceActivity.this.finish(); RemoteServiceActivity.this.finish();
} }
} }
}, R.string.api_register_disallow, new View.OnClickListener() { }, R.string.api_register_disallow, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// Disallow // Disallow
@ -161,7 +161,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
} }
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -173,7 +173,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
RemoteServiceActivity.this.setResult(RESULT_OK, resultData); RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish(); RemoteServiceActivity.this.finish();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// cancel // cancel
@ -214,7 +214,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
String text = "<font color=\"red\">" + errorMessage + "</font>"; String text = "<font color=\"red\">" + errorMessage + "</font>";
// Inflate a "Done" custom action bar view // Inflate a "Done" custom action bar view
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override

View File

@ -530,6 +530,10 @@ public class DecryptActivity extends DrawerActivity {
Log.e(Constants.TAG, "File not found!", e); Log.e(Constants.TAG, "File not found!", e);
AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()), AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
AppMsg.STYLE_ALERT).show(); AppMsg.STYLE_ALERT).show();
} finally {
try {
if (inStream != null) inStream.close();
} catch (Exception e){ }
} }
} else { } else {
inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes()); inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes());

View File

@ -134,14 +134,14 @@ public class EditKeyActivity extends ActionBarActivity {
* @param intent * @param intent
*/ */
private void handleActionCreateKey(Intent intent) { private void handleActionCreateKey(Intent intent) {
// Inflate a "Done"/"Cancel" custom action bar // Inflate a "Save"/"Cancel" custom action bar
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_save, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
saveClicked(); saveClicked();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
cancelClicked(); cancelClicked();
@ -249,8 +249,8 @@ public class EditKeyActivity extends ActionBarActivity {
* @param intent * @param intent
*/ */
private void handleActionEditKey(Intent intent) { private void handleActionEditKey(Intent intent) {
// Inflate a "Done"/"Cancel" custom action bar // Inflate a "Save"/"Cancel" custom action bar
ActionBarHelper.setSaveView(getSupportActionBar(), R.string.btn_save, ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -329,8 +329,8 @@ public class EditKeyActivity extends ActionBarActivity {
cancelClicked(); cancelClicked();
return true; return true;
case R.id.menu_key_edit_export_file: case R.id.menu_key_edit_export_file:
mExportHelper.showExportKeysDialog(mDataUri, Id.type.secret_key, Constants.path.APP_DIR long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())};
+ "/secexport.asc"); mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.path.APP_DIR_FILE_SEC);
return true; return true;
case R.id.menu_key_edit_delete: { case R.id.menu_key_edit_delete: {
// Message is received after key is deleted // Message is received after key is deleted

View File

@ -59,8 +59,7 @@ public class KeyListPublicActivity extends DrawerActivity {
return true; return true;
case R.id.menu_key_list_public_export: case R.id.menu_key_list_public_export:
mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB);
+ "/pubexport.asc");
return true; return true;
default: default:

View File

@ -18,10 +18,11 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
@ -47,6 +48,7 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat; import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.ActionMode; import android.view.ActionMode;
@ -179,6 +181,11 @@ public class KeyListPublicFragment extends Fragment implements SearchView.OnQuer
encrypt(mode, ids); encrypt(mode, ids);
break; break;
} }
case R.id.menu_key_list_public_multi_export: {
ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB);
break;
}
case R.id.menu_key_list_public_multi_delete: { case R.id.menu_key_list_public_multi_delete: {
showDeleteKeyDialog(mode, ids); showDeleteKeyDialog(mode, ids);
break; break;

View File

@ -62,8 +62,7 @@ public class KeyListSecretActivity extends DrawerActivity {
return true; return true;
case R.id.menu_key_list_secret_export: case R.id.menu_key_list_secret_export:
mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.path.APP_DIR mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.path.APP_DIR_FILE_SEC);
+ "/secexport.asc");
return true; return true;
case R.id.menu_key_list_secret_import: case R.id.menu_key_list_secret_import:

View File

@ -17,10 +17,10 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.Set; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
@ -41,6 +41,7 @@ import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v7.app.ActionBarActivity;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -106,6 +107,12 @@ public class KeyListSecretFragment extends ListFragment implements
} }
break; break;
} }
case R.id.menu_key_list_public_multi_export: {
ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.path.APP_DIR_FILE_SEC);
break;
}
} }
return true; return true;
} }

View File

@ -50,14 +50,14 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// ok // ok
okClicked(); okClicked();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// cancel // cancel
@ -81,11 +81,11 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
Intent intent = getIntent(); Intent intent = getIntent();
String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS); String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS);
if (servers != null) { if (servers != null) {
for (int i = 0; i < servers.length; ++i) { for (String serv : servers) {
KeyServerEditor view = (KeyServerEditor) mInflater.inflate( KeyServerEditor view = (KeyServerEditor) mInflater.inflate(
R.layout.key_server_editor, mEditors, false); R.layout.key_server_editor, mEditors, false);
view.setEditorListener(this); view.setEditorListener(this);
view.setValue(servers[i]); view.setValue(serv);
mEditors.addView(view); mEditors.addView(view);
} }
} }

View File

@ -46,14 +46,14 @@ public class SelectPublicKeyActivity extends ActionBarActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// ok // ok
okClicked(); okClicked();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// cancel // cancel

View File

@ -199,8 +199,8 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
if (masterKeyIds != null) { if (masterKeyIds != null) {
for (int i = 0; i < getListView().getCount(); ++i) { for (int i = 0; i < getListView().getCount(); ++i) {
long keyId = mAdapter.getMasterKeyId(i); long keyId = mAdapter.getMasterKeyId(i);
for (int j = 0; j < masterKeyIds.length; ++j) { for (long masterKeyId : masterKeyIds) {
if (keyId == masterKeyIds[j]) { if (keyId == masterKeyId) {
getListView().setItemChecked(i, true); getListView().setItemChecked(i, true);
break; break;
} }

View File

@ -118,8 +118,8 @@ public class ViewKeyActivity extends ActionBarActivity {
uploadToKeyserver(mDataUri); uploadToKeyserver(mDataUri);
return true; return true;
case R.id.menu_key_view_export_file: case R.id.menu_key_view_export_file:
mExportHelper.showExportKeysDialog(mDataUri, Id.type.public_key, Constants.path.APP_DIR long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())};
+ "/pubexport.asc"); mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB);
return true; return true;
case R.id.menu_key_view_share_default_fingerprint: case R.id.menu_key_view_share_default_fingerprint:
shareKey(mDataUri, true); shareKey(mDataUri, true);

View File

@ -280,13 +280,14 @@ public class ViewKeyMainFragment extends Fragment implements
// for each 4 characters of the fingerprint + 1 space // for each 4 characters of the fingerprint + 1 space
for (int i = 0; i < fingerprint.length(); i += 5) { for (int i = 0; i < fingerprint.length(); i += 5) {
String fourChars = fingerprint.substring(i, Math.min(i + 4, fingerprint.length())); int minFingLength = Math.min(i + 4, fingerprint.length());
String fourChars = fingerprint.substring(i, minFingLength);
// Create a foreground color by converting the 4 fingerprint chars to an int hashcode // Create a foreground color by converting the 4 fingerprint chars to an int hashcode
// and then converting that int to hex to use as a color // and then converting that int to hex to use as a color
fcs = new ForegroundColorSpan( fcs = new ForegroundColorSpan(
Color.parseColor(String.format("#%06X", (0xFFFFFF & fourChars.hashCode())))); Color.parseColor(String.format("#%06X", (0xFFFFFF & fourChars.hashCode()))));
sb.setSpan(fcs, i, Math.min(i+4, fingerprint.length()), Spannable.SPAN_INCLUSIVE_INCLUSIVE); sb.setSpan(fcs, i, minFingLength, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
} }
return sb; return sb;

View File

@ -18,8 +18,6 @@
package org.sufficientlysecure.keychain.ui.adapter; package org.sufficientlysecure.keychain.ui.adapter;
import java.util.HashMap; import java.util.HashMap;
import java.util.Set;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;

View File

@ -18,7 +18,6 @@
package org.sufficientlysecure.keychain.ui.adapter; package org.sufficientlysecure.keychain.ui.adapter;
import java.util.HashMap; import java.util.HashMap;
import java.util.Set;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;

View File

@ -32,6 +32,7 @@ import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Choice; import org.sufficientlysecure.keychain.util.Choice;
import java.util.ArrayList;
import java.util.Vector; import java.util.Vector;
public class CreateKeyDialogFragment extends DialogFragment { public class CreateKeyDialogFragment extends DialogFragment {
@ -78,7 +79,7 @@ public class CreateKeyDialogFragment extends DialogFragment {
boolean wouldBeMasterKey = (childCount == 0); boolean wouldBeMasterKey = (childCount == 0);
final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm); final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm);
Vector<Choice> choices = new Vector<Choice>(); ArrayList<Choice> choices = new ArrayList<Choice>();
choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString( choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString(
R.string.dsa))); R.string.dsa)));
if (!wouldBeMasterKey) { if (!wouldBeMasterKey) {

View File

@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.widget;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import android.widget.*;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import android.content.Context; import android.content.Context;
@ -26,11 +27,9 @@ import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.helper.ContactHelper;
public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
private EditorListener mEditorListener = null; private EditorListener mEditorListener = null;
@ -38,7 +37,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
private BootstrapButton mDeleteButton; private BootstrapButton mDeleteButton;
private RadioButton mIsMainUserId; private RadioButton mIsMainUserId;
private EditText mName; private EditText mName;
private EditText mEmail; private AutoCompleteTextView mEmail;
private EditText mComment; private EditText mComment;
// see http://www.regular-expressions.info/email.html // see http://www.regular-expressions.info/email.html
@ -102,9 +101,17 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
mIsMainUserId.setOnClickListener(this); mIsMainUserId.setOnClickListener(this);
mName = (EditText) findViewById(R.id.name); mName = (EditText) findViewById(R.id.name);
mEmail = (EditText) findViewById(R.id.email); mEmail = (AutoCompleteTextView) findViewById(R.id.email);
mComment = (EditText) findViewById(R.id.comment); mComment = (EditText) findViewById(R.id.comment);
mEmail.setThreshold(1); // Start working from first character
mEmail.setAdapter(
new ArrayAdapter<String>
(this.getContext(), android.R.layout.simple_dropdown_item_1line,
ContactHelper.getMailAccounts(getContext())
));
super.onFinishInflate(); super.onFinishInflate();
} }

View File

@ -1,27 +0,0 @@
<!--
Copyright 2013 The Android Open Source Project
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerPadding="12dp"
android:orientation="horizontal"
android:divider="@drawable/abc_list_divider_holo_light"
android:showDividers="end" >
<include layout="@layout/actionbar_include_save_button" />
</LinearLayout>

View File

@ -1,29 +0,0 @@
<!--
Copyright 2013 The Android Open Source Project
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerPadding="12dp"
android:divider="@drawable/abc_list_divider_holo_light"
android:orientation="horizontal"
android:showDividers="middle">
<include layout="@layout/actionbar_include_cancel_button" />
<include layout="@layout/actionbar_include_save_button" />
</LinearLayout>

View File

@ -1,36 +0,0 @@
<!--
Copyright 2013 The Android Open Source Project
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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/actionbar_save"
style="@style/Widget.AppCompat.ActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<TextView
android:id="@+id/actionbar_save_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_save"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
style="@style/Widget.AppCompat.Light.ActionBar.TabText"
android:text="Save (set in-code!)" />
</FrameLayout>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical" > android:orientation="vertical" >

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical" > android:orientation="vertical" >

View File

@ -49,7 +49,7 @@
android:paddingRight="5dip" android:paddingRight="5dip"
android:text="@string/label_email" /> android:text="@string/label_email" />
<EditText <AutoCompleteTextView
android:id="@+id/email" android:id="@+id/email"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto" xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent" >

View File

@ -5,6 +5,10 @@
android:id="@+id/menu_key_list_public_multi_select_all" android:id="@+id/menu_key_list_public_multi_select_all"
android:icon="@drawable/ic_action_select_all" android:icon="@drawable/ic_action_select_all"
android:title="@string/menu_select_all" /> android:title="@string/menu_select_all" />
<item
android:id="@+id/menu_key_list_public_multi_export"
android:icon="@drawable/ic_action_import_export"
android:title="@string/menu_export_key" />
<item <item
android:id="@+id/menu_key_list_public_multi_encrypt" android:id="@+id/menu_key_list_public_multi_encrypt"
android:icon="@drawable/ic_action_secure" android:icon="@drawable/ic_action_secure"

View File

@ -5,6 +5,10 @@
android:id="@+id/menu_key_list_public_multi_select_all" android:id="@+id/menu_key_list_public_multi_select_all"
android:icon="@drawable/ic_action_select_all" android:icon="@drawable/ic_action_select_all"
android:title="@string/menu_select_all" /> android:title="@string/menu_select_all" />
<item
android:id="@+id/menu_key_list_public_multi_export"
android:icon="@drawable/ic_action_import_export"
android:title="@string/menu_export_key" />
<item <item
android:id="@+id/menu_key_list_public_multi_delete" android:id="@+id/menu_key_list_public_multi_delete"
android:icon="@drawable/ic_action_discard" android:icon="@drawable/ic_action_discard"

View File

@ -155,6 +155,14 @@ See http://source.android.com/source/code-style.html
See http://www.androidpolice.com/2009/11/04/auto-formatting-android-xml-files-with-eclipse/ See http://www.androidpolice.com/2009/11/04/auto-formatting-android-xml-files-with-eclipse/
### Automated syntax check with CheckStyle
* Paste the tools/checkstyle.xml file to ~/.AndroidStudioPreview/config/codestyles/ (in Linux/Unix)
or ~/Library/Preferences/AndroidStudioPreview/codestyles (in Mac OSX)
* Go to Settings (or Preferences in Mac OS X) > Code Style > Java, select OpenPgpChecker,
as well as Code Style > XML and select OpenPgpChecker again.
* Start code inspection and see the results by selecting Analyze > Inspect Code from Android-Studio
or you can directly run checkstyle via cli with .tools/checkstyle. Make sure it's executable first.
## Licenses ## Licenses
OpenPGP Kechain is licensed under GPLv3+. OpenPGP Kechain is licensed under GPLv3+.
Some parts (older parts and some libraries are Apache License v2, MIT X11 License) Some parts (older parts and some libraries are Apache License v2, MIT X11 License)

1
tools/checkstyle Executable file
View File

@ -0,0 +1 @@
checkstyle -c tools/checkstyle.xml -r OpenPGP-Keychain/src/main/java 2>&1 | egrep -v 'log4j'

356
tools/checkstyle.xml Normal file
View File

@ -0,0 +1,356 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!-- This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html -->
<module name="OpenPgpChecker">
<module name="SuppressionFilter">
<property name="file" value="tools/suppressions.xml"/>
</module>
<module name="RegexpSingleline">
<!-- Requires a copyright notice in each file.
Code intended to be open-sourced may have a multi-line copyright
notice, so that this required text appears on the second line:
<pre>
/*
* Copyright 2008 Google Inc.
*
* (details of open-source license...)
</pre>
-->
<property name="format"
value="^(//| \*) Copyright (\([cC]\) )?[\d]{4}(\-[\d]{4})? .*$"/>
<property name="minimum" value="1"/>
<property name="maximum" value="10"/>
<property name="message" value="Copyright is missing or malformed."/>
<property name="severity" value="error"/>
</module>
<module name="FileTabCharacter">
<!-- Checks that there are no tab characters in the file.
-->
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<module name="RegexpSingleline">
<!-- Checks that FIXME is not used in comments. TODO is preferred.
-->
<property name="format" value="((//.*)|(\*.*))FIXME"/>
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."'/>
</module>
<!--
<module name="RegexpSingleline">
<property name="format" value="((//.*)|(\*.*))TODO[^(]"/>
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."'/>
</module>
-->
<!-- All Java AST specific tests live under TreeWalker module. -->
<module name="TreeWalker">
<!--
IMPORT CHECKS
-->
<module name="RedundantImport">
<property name="severity" value="error"/>
</module>
<module name="UnusedImports">
<property name="severity" value="error"/>
</module>
<module name="ImportOrder">
<!-- Checks for out of order import statements. -->
<property name="severity" value="warning"/>
<property name="groups" value="com.google,android,junit,com,net,org,se,java,javax"/>
<!-- This ensures that static imports go first. -->
<property name="option" value="top"/>
<property name="tokens" value="STATIC_IMPORT, IMPORT"/>
</module>
<!--
JAVADOC CHECKS
-->
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<!--
<module name="JavadocMethod">
<property name="scope" value="protected"/>
<property name="severity" value="warning"/>
<property name="allowMissingJavadoc" value="true"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
<property name="allowUndeclaredRTE" value="true"/>
</module>
<module name="JavadocType">
<property name="scope" value="protected"/>
<property name="severity" value="error"/>
</module>
<module name="JavadocStyle">
<property name="severity" value="warning"/>
</module>
-->
<!--
NAMING CHECKS
-->
<!-- Item 38 - Adhere to generally accepted naming conventions -->
<module name="PackageName">
<!-- Validates identifiers for package names against the
supplied expression. -->
<!-- Here the default checkstyle rule restricts package name parts to
seven characters, this is not in line with common practice at Google.
-->
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
<property name="severity" value="warning"/>
</module>
<module name="TypeNameCheck">
<!-- Validates static, final fields against the
expression "^[A-Z][a-zA-Z0-9]*$". -->
<metadata name="altname" value="TypeName"/>
<property name="severity" value="warning"/>
</module>
<module name="ConstantNameCheck">
<!-- Validates non-private, static, final fields against the supplied
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
<metadata name="altname" value="ConstantName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="false"/>
<property name="format" value="^([A-Z_][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
<message key="name.invalidPattern"
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
<property name="severity" value="warning"/>
</module>
<module name="StaticVariableNameCheck">
<!-- Validates static, non-final fields against the supplied
expression "^[a-z][a-zA-Z0-9]*_?$". -->
<metadata name="altname" value="StaticVariableName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^s[A-Z][a-zA-Z0-9]*_?$"/>
<property name="severity" value="warning"/>
</module>
<module name="MemberNameCheck">
<!-- Validates non-static members against the supplied expression. -->
<metadata name="altname" value="MemberName"/>
<property name="applyToPublic" value="false"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^m[A-Z][a-zA-Z0-9]*$"/>
<property name="severity" value="warning"/>
</module>
<module name="MemberNameCheck">
<!-- Validates non-static members against the supplied expression. -->
<metadata name="altname" value="MemberName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="false"/>
<property name="applyToPackage" value="false"/>
<property name="applyToPrivate" value="false"/>
<property name="format" value="^[a-z]([^A-Z]|$)[a-zA-Z0-9]*$"/>
<property name="severity" value="warning"/>
</module>
<module name="MethodNameCheck">
<!-- Validates identifiers for method names. -->
<metadata name="altname" value="MethodName"/>
<property name="format" value="^[a-z]([^A-Z]|$)[a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
<property name="severity" value="warning"/>
</module>
<module name="ParameterName">
<!-- Validates identifiers for method parameters against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalFinalVariableName">
<!-- Validates identifiers for local final variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalVariableName">
<!-- Validates identifiers for local variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<!--
LENGTH and CODING CHECKS
-->
<module name="LineLength">
<!-- Checks if a line is too long. -->
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="110"/>
<property name="severity" value="error"/>
<!--
The default ignore pattern exempts the following elements:
- import statements
- long URLs inside comments
-->
<property name="ignorePattern"
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
default="^(package .*;\s*)|(import .*;\s*)|( *\* *https?://.*)$"/>
</module>
<module name="LeftCurly">
<!-- Checks for placement of the left curly brace ('{'). -->
<property name="severity" value="warning"/>
</module>
<module name="RightCurly">
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
the same line. e.g., the following example is fine:
<pre>
if {
...
} else
</pre>
-->
<!-- This next example is not fine:
<pre>
if {
...
}
else
</pre>
-->
<property name="option" value="same"/>
<property name="severity" value="warning"/>
</module>
<!-- Checks for braces around if and else blocks -->
<module name="NeedBraces">
<property name="severity" value="warning"/>
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
</module>
<module name="UpperEll">
<!-- Checks that long constants are defined with an upper ell.-->
<property name="severity" value="error"/>
</module>
<module name="FallThrough">
<!-- Warn about falling through to the next case statement. Similar to
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
some other variants which we don't publicized to promote consistency).
-->
<property name="reliefPattern"
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
<property name="severity" value="error"/>
</module>
<!--
MODIFIERS CHECKS
-->
<module name="ModifierOrder">
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
8.4.3. The prescribed order is:
public, protected, private, abstract, static, final, transient, volatile,
synchronized, native, strictfp
-->
</module>
<!--
WHITESPACE CHECKS
-->
<module name="WhitespaceAround">
<!-- Checks that various tokens are surrounded by whitespace.
This includes most binary operators and keywords followed
by regular or curly braces.
-->
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
<property name="severity" value="error"/>
</module>
<module name="WhitespaceAfter">
<!-- Checks that commas, semicolons and typecasts are followed by
whitespace.
-->
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
</module>
<module name="NoWhitespaceAfter">
<!-- Checks that there is no whitespace after various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
UNARY_PLUS"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="NoWhitespaceBefore">
<!-- Checks that there is no whitespace before various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="ParenPad">
<!-- Checks that there is no whitespace before close parens or after
open parens.
-->
<property name="severity" value="warning"/>
</module>
</module>
</module>