changing package name back
This commit is contained in:
2280
org_apg/src/org/thialfihar/android/apg/Apg.java
Normal file
2280
org_apg/src/org/thialfihar/android/apg/Apg.java
Normal file
File diff suppressed because it is too large
Load Diff
658
org_apg/src/org/thialfihar/android/apg/ApgService.java
Normal file
658
org_apg/src/org/thialfihar/android/apg/ApgService.java
Normal file
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.thialfihar.android.apg.IApgService;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.provider.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.Keys;
|
||||
import org.thialfihar.android.apg.provider.UserIds;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class ApgService extends Service {
|
||||
private final static String TAG = "ApgService";
|
||||
public static final boolean LOCAL_LOGV = true;
|
||||
public static final boolean LOCAL_LOGD = true;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (LOCAL_LOGD)
|
||||
Log.d(TAG, "bound");
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/** error status */
|
||||
private static enum error {
|
||||
ARGUMENTS_MISSING, APG_FAILURE, NO_MATCHING_SECRET_KEY, PRIVATE_KEY_PASSPHRASE_WRONG, PRIVATE_KEY_PASSPHRASE_MISSING;
|
||||
|
||||
public int shiftedOrdinal() {
|
||||
return ordinal() + 100;
|
||||
}
|
||||
}
|
||||
|
||||
private static enum call {
|
||||
encrypt_with_passphrase, encrypt_with_public_key, decrypt, get_keys
|
||||
}
|
||||
|
||||
/** all arguments that can be passed by calling application */
|
||||
public static enum arg {
|
||||
MESSAGE, // message to encrypt or to decrypt
|
||||
SYMMETRIC_PASSPHRASE, // key for symmetric en/decryption
|
||||
PUBLIC_KEYS, // public keys for encryption
|
||||
ENCRYPTION_ALGORYTHM, // encryption algorithm
|
||||
HASH_ALGORYTHM, // hash algorithm
|
||||
ARMORED_OUTPUT, // whether to armor output
|
||||
FORCE_V3_SIGNATURE, // whether to force v3 signature
|
||||
COMPRESSION, // what compression to use for encrypted output
|
||||
SIGNATURE_KEY, // key for signing
|
||||
PRIVATE_KEY_PASSPHRASE, // passphrase for encrypted private key
|
||||
KEY_TYPE, // type of key (private or public)
|
||||
BLOB, // blob passed
|
||||
}
|
||||
|
||||
/** all things that might be returned */
|
||||
private static enum ret {
|
||||
ERRORS, // string array list with errors
|
||||
WARNINGS, // string array list with warnings
|
||||
ERROR, // numeric error
|
||||
RESULT, // en-/decrypted
|
||||
FINGERPRINTS, // fingerprints of keys
|
||||
USER_IDS, // user ids
|
||||
}
|
||||
|
||||
/** required arguments for each AIDL function */
|
||||
private static final HashMap<String, HashSet<arg>> FUNCTIONS_REQUIRED_ARGS = new HashMap<String, HashSet<arg>>();
|
||||
static {
|
||||
HashSet<arg> args = new HashSet<arg>();
|
||||
args.add(arg.SYMMETRIC_PASSPHRASE);
|
||||
FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_passphrase.name(), args);
|
||||
|
||||
args = new HashSet<arg>();
|
||||
args.add(arg.PUBLIC_KEYS);
|
||||
FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_public_key.name(), args);
|
||||
|
||||
args = new HashSet<arg>();
|
||||
FUNCTIONS_REQUIRED_ARGS.put(call.decrypt.name(), args);
|
||||
|
||||
args = new HashSet<arg>();
|
||||
args.add(arg.KEY_TYPE);
|
||||
FUNCTIONS_REQUIRED_ARGS.put(call.get_keys.name(), args);
|
||||
}
|
||||
|
||||
/** optional arguments for each AIDL function */
|
||||
private static final HashMap<String, HashSet<arg>> FUNCTIONS_OPTIONAL_ARGS = new HashMap<String, HashSet<arg>>();
|
||||
static {
|
||||
HashSet<arg> args = new HashSet<arg>();
|
||||
args.add(arg.ENCRYPTION_ALGORYTHM);
|
||||
args.add(arg.HASH_ALGORYTHM);
|
||||
args.add(arg.ARMORED_OUTPUT);
|
||||
args.add(arg.FORCE_V3_SIGNATURE);
|
||||
args.add(arg.COMPRESSION);
|
||||
args.add(arg.PRIVATE_KEY_PASSPHRASE);
|
||||
args.add(arg.SIGNATURE_KEY);
|
||||
args.add(arg.BLOB);
|
||||
args.add(arg.MESSAGE);
|
||||
FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_passphrase.name(), args);
|
||||
FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_public_key.name(), args);
|
||||
|
||||
args = new HashSet<arg>();
|
||||
args.add(arg.SYMMETRIC_PASSPHRASE);
|
||||
args.add(arg.PUBLIC_KEYS);
|
||||
args.add(arg.PRIVATE_KEY_PASSPHRASE);
|
||||
args.add(arg.MESSAGE);
|
||||
args.add(arg.BLOB);
|
||||
FUNCTIONS_OPTIONAL_ARGS.put(call.decrypt.name(), args);
|
||||
}
|
||||
|
||||
/** a map from ApgService parameters to function calls to get the default */
|
||||
private static final HashMap<arg, String> FUNCTIONS_DEFAULTS = new HashMap<arg, String>();
|
||||
static {
|
||||
FUNCTIONS_DEFAULTS.put(arg.ENCRYPTION_ALGORYTHM, "getDefaultEncryptionAlgorithm");
|
||||
FUNCTIONS_DEFAULTS.put(arg.HASH_ALGORYTHM, "getDefaultHashAlgorithm");
|
||||
FUNCTIONS_DEFAULTS.put(arg.ARMORED_OUTPUT, "getDefaultAsciiArmour");
|
||||
FUNCTIONS_DEFAULTS.put(arg.FORCE_V3_SIGNATURE, "getForceV3Signatures");
|
||||
FUNCTIONS_DEFAULTS.put(arg.COMPRESSION, "getDefaultMessageCompression");
|
||||
}
|
||||
|
||||
/** a map of the default function names to their method */
|
||||
private static final HashMap<String, Method> FUNCTIONS_DEFAULTS_METHODS = new HashMap<String, Method>();
|
||||
static {
|
||||
try {
|
||||
FUNCTIONS_DEFAULTS_METHODS.put("getDefaultEncryptionAlgorithm",
|
||||
Preferences.class.getMethod("getDefaultEncryptionAlgorithm"));
|
||||
FUNCTIONS_DEFAULTS_METHODS.put("getDefaultHashAlgorithm",
|
||||
Preferences.class.getMethod("getDefaultHashAlgorithm"));
|
||||
FUNCTIONS_DEFAULTS_METHODS.put("getDefaultAsciiArmour",
|
||||
Preferences.class.getMethod("getDefaultAsciiArmour"));
|
||||
FUNCTIONS_DEFAULTS_METHODS.put("getForceV3Signatures",
|
||||
Preferences.class.getMethod("getForceV3Signatures"));
|
||||
FUNCTIONS_DEFAULTS_METHODS.put("getDefaultMessageCompression",
|
||||
Preferences.class.getMethod("getDefaultMessageCompression"));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Function method exception: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeToOutputStream(InputStream is, OutputStream os) throws IOException {
|
||||
byte[] buffer = new byte[8];
|
||||
int len = 0;
|
||||
while ((len = is.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
private static Cursor getKeyEntries(HashMap<String, Object> pParams) {
|
||||
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') ");
|
||||
|
||||
String orderBy = pParams.containsKey("order_by") ? (String) pParams.get("order_by")
|
||||
: UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC";
|
||||
|
||||
String typeVal[] = null;
|
||||
String typeWhere = null;
|
||||
if (pParams.containsKey("key_type")) {
|
||||
typeWhere = KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?";
|
||||
typeVal = new String[] { "" + pParams.get("key_type") };
|
||||
}
|
||||
return qb.query(Apg.getDatabase().db(), (String[]) pParams.get("columns"), typeWhere,
|
||||
typeVal, null, null, orderBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* maps a fingerprint or user id of a key to a master key in database
|
||||
*
|
||||
* @param search_key
|
||||
* fingerprint or user id to search for
|
||||
* @return master key if found, or 0
|
||||
*/
|
||||
private static long getMasterKey(String pSearchKey, Bundle pReturn) {
|
||||
if (pSearchKey == null || pSearchKey.length() != 8) {
|
||||
return 0;
|
||||
}
|
||||
ArrayList<String> keyList = new ArrayList<String>();
|
||||
keyList.add(pSearchKey);
|
||||
long[] keys = getMasterKey(keyList, pReturn);
|
||||
if (keys.length > 0) {
|
||||
return keys[0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* maps fingerprints or user ids of keys to master keys in database
|
||||
*
|
||||
* @param search_keys
|
||||
* a list of keys (fingerprints or user ids) to look for in database
|
||||
* @return an array of master keys
|
||||
*/
|
||||
private static long[] getMasterKey(ArrayList<String> pSearchKeys, Bundle pReturn) {
|
||||
|
||||
HashMap<String, Object> qParams = new HashMap<String, Object>();
|
||||
qParams.put("columns", new String[] { KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0
|
||||
UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1
|
||||
});
|
||||
qParams.put("key_type", Id.database.type_public);
|
||||
|
||||
Cursor mCursor = getKeyEntries(qParams);
|
||||
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "going through installed user keys");
|
||||
ArrayList<Long> masterKeys = new ArrayList<Long>();
|
||||
while (mCursor.moveToNext()) {
|
||||
long curMkey = mCursor.getLong(0);
|
||||
String curUser = mCursor.getString(1);
|
||||
|
||||
String curFprint = Apg.getSmallFingerPrint(curMkey);
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "current user: " + curUser + " (" + curFprint + ")");
|
||||
if (pSearchKeys.contains(curFprint) || pSearchKeys.contains(curUser)) {
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "master key found for: " + curFprint);
|
||||
masterKeys.add(curMkey);
|
||||
pSearchKeys.remove(curFprint);
|
||||
} else {
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "Installed key " + curFprint
|
||||
+ " is not in the list of public keys to encrypt with");
|
||||
}
|
||||
}
|
||||
mCursor.close();
|
||||
|
||||
long[] masterKeyLongs = new long[masterKeys.size()];
|
||||
int i = 0;
|
||||
for (Long key : masterKeys) {
|
||||
masterKeyLongs[i++] = key;
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
Log.w(TAG, "Found not one public key");
|
||||
pReturn.getStringArrayList(ret.WARNINGS.name()).add(
|
||||
"Searched for public key(s) but found not one");
|
||||
}
|
||||
|
||||
for (String key : pSearchKeys) {
|
||||
Log.w(TAG, "Searched for key " + key + " but cannot find it in APG");
|
||||
pReturn.getStringArrayList(ret.WARNINGS.name()).add(
|
||||
"Searched for key " + key + " but cannot find it in APG");
|
||||
}
|
||||
|
||||
return masterKeyLongs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default arguments if missing
|
||||
*
|
||||
* @param args
|
||||
* the bundle to add default parameters to if missing
|
||||
*/
|
||||
private void addDefaultArguments(String pCall, Bundle pArgs) {
|
||||
// check whether there are optional elements defined for that call
|
||||
if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pCall)) {
|
||||
Preferences preferences = Preferences.getPreferences(getBaseContext(), true);
|
||||
|
||||
Iterator<arg> iter = FUNCTIONS_DEFAULTS.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
arg currentArg = iter.next();
|
||||
String currentKey = currentArg.name();
|
||||
if (!pArgs.containsKey(currentKey)
|
||||
&& FUNCTIONS_OPTIONAL_ARGS.get(pCall).contains(currentArg)) {
|
||||
String currentFunctionName = FUNCTIONS_DEFAULTS.get(currentArg);
|
||||
try {
|
||||
Class<?> returnType = FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName)
|
||||
.getReturnType();
|
||||
if (returnType == String.class) {
|
||||
pArgs.putString(currentKey,
|
||||
(String) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName)
|
||||
.invoke(preferences));
|
||||
} else if (returnType == boolean.class) {
|
||||
pArgs.putBoolean(currentKey,
|
||||
(Boolean) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName)
|
||||
.invoke(preferences));
|
||||
} else if (returnType == int.class) {
|
||||
pArgs.putInt(currentKey,
|
||||
(Integer) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName)
|
||||
.invoke(preferences));
|
||||
} else {
|
||||
Log.e(TAG, "Unknown return type " + returnType.toString()
|
||||
+ " for default option");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception in add_default_arguments " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* updates a Bundle with default return values
|
||||
*
|
||||
* @param pReturn
|
||||
* the Bundle to update
|
||||
*/
|
||||
private void addDefaultReturns(Bundle pReturn) {
|
||||
ArrayList<String> errors = new ArrayList<String>();
|
||||
ArrayList<String> warnings = new ArrayList<String>();
|
||||
|
||||
pReturn.putStringArrayList(ret.ERRORS.name(), errors);
|
||||
pReturn.putStringArrayList(ret.WARNINGS.name(), warnings);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks for required arguments and adds them to the error if missing
|
||||
*
|
||||
* @param function
|
||||
* the functions required arguments to check for
|
||||
* @param pArgs
|
||||
* the Bundle of arguments to check
|
||||
* @param pReturn
|
||||
* the bundle to write errors to
|
||||
*/
|
||||
private void checkForRequiredArgs(String pFunction, Bundle pArgs, Bundle pReturn) {
|
||||
if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) {
|
||||
Iterator<arg> iter = FUNCTIONS_REQUIRED_ARGS.get(pFunction).iterator();
|
||||
while (iter.hasNext()) {
|
||||
String curArg = iter.next().name();
|
||||
if (!pArgs.containsKey(curArg)) {
|
||||
pReturn.getStringArrayList(ret.ERRORS.name())
|
||||
.add("Argument missing: " + curArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pFunction.equals(call.encrypt_with_passphrase.name())
|
||||
|| pFunction.equals(call.encrypt_with_public_key.name())
|
||||
|| pFunction.equals(call.decrypt.name())) {
|
||||
// check that either MESSAGE or BLOB are there
|
||||
if (!pArgs.containsKey(arg.MESSAGE.name()) && !pArgs.containsKey(arg.BLOB.name())) {
|
||||
pReturn.getStringArrayList(ret.ERRORS.name()).add(
|
||||
"Arguments missing: Neither MESSAGE nor BLOG found");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks for unknown arguments and add them to warning if found
|
||||
*
|
||||
* @param function
|
||||
* the functions name to check against
|
||||
* @param pArgs
|
||||
* the Bundle of arguments to check
|
||||
* @param pReturn
|
||||
* the bundle to write warnings to
|
||||
*/
|
||||
private void checkForUnknownArgs(String pFunction, Bundle pArgs, Bundle pReturn) {
|
||||
|
||||
HashSet<arg> allArgs = new HashSet<arg>();
|
||||
if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) {
|
||||
allArgs.addAll(FUNCTIONS_REQUIRED_ARGS.get(pFunction));
|
||||
}
|
||||
if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pFunction)) {
|
||||
allArgs.addAll(FUNCTIONS_OPTIONAL_ARGS.get(pFunction));
|
||||
}
|
||||
|
||||
ArrayList<String> unknownArgs = new ArrayList<String>();
|
||||
Iterator<String> iter = pArgs.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
String curKey = iter.next();
|
||||
try {
|
||||
arg curArg = arg.valueOf(curKey);
|
||||
if (!allArgs.contains(curArg)) {
|
||||
pReturn.getStringArrayList(ret.WARNINGS.name()).add(
|
||||
"Unknown argument: " + curKey);
|
||||
unknownArgs.add(curKey);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
pReturn.getStringArrayList(ret.WARNINGS.name()).add("Unknown argument: " + curKey);
|
||||
unknownArgs.add(curKey);
|
||||
}
|
||||
}
|
||||
|
||||
// remove unknown arguments so our bundle has just what we need
|
||||
for (String arg : unknownArgs) {
|
||||
pArgs.remove(arg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean prepareArgs(String pCall, Bundle pArgs, Bundle pReturn) {
|
||||
Apg.initialize(getBaseContext());
|
||||
|
||||
/* add default return values for all functions */
|
||||
addDefaultReturns(pReturn);
|
||||
|
||||
/* add default arguments if missing */
|
||||
addDefaultArguments(pCall, pArgs);
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "add_default_arguments");
|
||||
|
||||
/* check for required arguments */
|
||||
checkForRequiredArgs(pCall, pArgs, pReturn);
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "check_required_args");
|
||||
|
||||
/* check for unknown arguments and add to warning if found */
|
||||
checkForUnknownArgs(pCall, pArgs, pReturn);
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "check_unknown_args");
|
||||
|
||||
/* return if errors happened */
|
||||
if (pReturn.getStringArrayList(ret.ERRORS.name()).size() != 0) {
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "Errors after preparing, not executing " + pCall);
|
||||
pReturn.putInt(ret.ERROR.name(), error.ARGUMENTS_MISSING.shiftedOrdinal());
|
||||
return false;
|
||||
}
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "error return");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean encrypt(Bundle pArgs, Bundle pReturn) {
|
||||
boolean isBlob = pArgs.containsKey(arg.BLOB.name());
|
||||
|
||||
long pubMasterKeys[] = {};
|
||||
if (pArgs.containsKey(arg.PUBLIC_KEYS.name())) {
|
||||
ArrayList<String> list = pArgs.getStringArrayList(arg.PUBLIC_KEYS.name());
|
||||
ArrayList<String> pubKeys = new ArrayList<String>();
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "Long size: " + list.size());
|
||||
Iterator<String> iter = list.iterator();
|
||||
while (iter.hasNext()) {
|
||||
pubKeys.add(iter.next());
|
||||
}
|
||||
pubMasterKeys = getMasterKey(pubKeys, pReturn);
|
||||
}
|
||||
|
||||
InputStream inStream = null;
|
||||
if (isBlob) {
|
||||
ContentResolver cr = getContentResolver();
|
||||
try {
|
||||
inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "... exception on opening blob", e);
|
||||
}
|
||||
} else {
|
||||
inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
|
||||
}
|
||||
InputData in = new InputData(inStream, 0); // XXX Size second param?
|
||||
|
||||
OutputStream out = new ByteArrayOutputStream();
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "About to encrypt");
|
||||
try {
|
||||
Apg.encrypt(getBaseContext(), // context
|
||||
in, // input stream
|
||||
out, // output stream
|
||||
pArgs.getBoolean(arg.ARMORED_OUTPUT.name()), // ARMORED_OUTPUT
|
||||
pubMasterKeys, // encryption keys
|
||||
getMasterKey(pArgs.getString(arg.SIGNATURE_KEY.name()), pReturn), // signature
|
||||
// key
|
||||
pArgs.getString(arg.PRIVATE_KEY_PASSPHRASE.name()), // signature passphrase
|
||||
null, // progress
|
||||
pArgs.getInt(arg.ENCRYPTION_ALGORYTHM.name()), // encryption
|
||||
pArgs.getInt(arg.HASH_ALGORYTHM.name()), // hash
|
||||
pArgs.getInt(arg.COMPRESSION.name()), // compression
|
||||
pArgs.getBoolean(arg.FORCE_V3_SIGNATURE.name()), // mPreferences.getForceV3Signatures(),
|
||||
pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) // passPhrase
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception in encrypt");
|
||||
String msg = e.getMessage();
|
||||
if (msg.equals(getBaseContext().getString(R.string.error_noSignaturePassPhrase))) {
|
||||
pReturn.getStringArrayList(ret.ERRORS.name()).add(
|
||||
"Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() + " missing): "
|
||||
+ msg);
|
||||
pReturn.putInt(ret.ERROR.name(),
|
||||
error.PRIVATE_KEY_PASSPHRASE_MISSING.shiftedOrdinal());
|
||||
} else if (msg.equals(getBaseContext().getString(
|
||||
R.string.error_couldNotExtractPrivateKey))) {
|
||||
pReturn.getStringArrayList(ret.ERRORS.name()).add(
|
||||
"Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name()
|
||||
+ " probably wrong): " + msg);
|
||||
pReturn.putInt(ret.ERROR.name(),
|
||||
error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal());
|
||||
} else {
|
||||
pReturn.getStringArrayList(ret.ERRORS.name()).add(
|
||||
"Internal failure (" + e.getClass() + ") in APG when encrypting: "
|
||||
+ e.getMessage());
|
||||
pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "Encrypted");
|
||||
if (isBlob) {
|
||||
ContentResolver cr = getContentResolver();
|
||||
try {
|
||||
OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB
|
||||
.name())));
|
||||
writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), outStream);
|
||||
outStream.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "... exception on writing blob", e);
|
||||
}
|
||||
} else {
|
||||
pReturn.putString(ret.RESULT.name(), out.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private final IApgService.Stub mBinder = new IApgService.Stub() {
|
||||
|
||||
public boolean getKeys(Bundle pArgs, Bundle pReturn) {
|
||||
|
||||
prepareArgs("get_keys", pArgs, pReturn);
|
||||
|
||||
HashMap<String, Object> qParams = new HashMap<String, Object>();
|
||||
qParams.put("columns", new String[] {
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0
|
||||
UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1
|
||||
});
|
||||
|
||||
qParams.put("key_type", pArgs.getInt(arg.KEY_TYPE.name()));
|
||||
|
||||
Cursor cursor = getKeyEntries(qParams);
|
||||
ArrayList<String> fPrints = new ArrayList<String>();
|
||||
ArrayList<String> ids = new ArrayList<String>();
|
||||
while (cursor.moveToNext()) {
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "adding key " + Apg.getSmallFingerPrint(cursor.getLong(0)));
|
||||
fPrints.add(Apg.getSmallFingerPrint(cursor.getLong(0)));
|
||||
ids.add(cursor.getString(1));
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
pReturn.putStringArrayList(ret.FINGERPRINTS.name(), fPrints);
|
||||
pReturn.putStringArrayList(ret.USER_IDS.name(), ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean encryptWithPublicKey(Bundle pArgs, Bundle pReturn) {
|
||||
if (!prepareArgs("encrypt_with_public_key", pArgs, pReturn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return encrypt(pArgs, pReturn);
|
||||
}
|
||||
|
||||
public boolean encryptWithPassphrase(Bundle pArgs, Bundle pReturn) {
|
||||
if (!prepareArgs("encrypt_with_passphrase", pArgs, pReturn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return encrypt(pArgs, pReturn);
|
||||
|
||||
}
|
||||
|
||||
public boolean decrypt(Bundle pArgs, Bundle pReturn) {
|
||||
if (!prepareArgs("decrypt", pArgs, pReturn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isBlob = pArgs.containsKey(arg.BLOB.name());
|
||||
|
||||
String passphrase = pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null ? pArgs
|
||||
.getString(arg.SYMMETRIC_PASSPHRASE.name()) : pArgs
|
||||
.getString(arg.PRIVATE_KEY_PASSPHRASE.name());
|
||||
|
||||
InputStream inStream = null;
|
||||
if (isBlob) {
|
||||
ContentResolver cr = getContentResolver();
|
||||
try {
|
||||
inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "... exception on opening blob", e);
|
||||
}
|
||||
} else {
|
||||
inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
|
||||
}
|
||||
|
||||
InputData in = new InputData(inStream, 0); // XXX what size in second parameter?
|
||||
OutputStream out = new ByteArrayOutputStream();
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "About to decrypt");
|
||||
try {
|
||||
Apg.decrypt(getBaseContext(), in, out, passphrase, null, // progress
|
||||
pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null // symmetric
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception in decrypt");
|
||||
String msg = e.getMessage();
|
||||
if (msg.equals(getBaseContext().getString(R.string.error_noSecretKeyFound))) {
|
||||
pReturn.getStringArrayList(ret.ERRORS.name()).add("Cannot decrypt: " + msg);
|
||||
pReturn.putInt(ret.ERROR.name(), error.NO_MATCHING_SECRET_KEY.shiftedOrdinal());
|
||||
} else if (msg.equals(getBaseContext().getString(R.string.error_wrongPassPhrase))) {
|
||||
pReturn.getStringArrayList(ret.ERRORS.name()).add(
|
||||
"Cannot decrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name()
|
||||
+ " wrong/missing): " + msg);
|
||||
pReturn.putInt(ret.ERROR.name(),
|
||||
error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal());
|
||||
} else {
|
||||
pReturn.getStringArrayList(ret.ERRORS.name()).add(
|
||||
"Internal failure (" + e.getClass() + ") in APG when decrypting: "
|
||||
+ msg);
|
||||
pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (LOCAL_LOGV)
|
||||
Log.v(TAG, "... decrypted");
|
||||
|
||||
if (isBlob) {
|
||||
ContentResolver cr = getContentResolver();
|
||||
try {
|
||||
OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB
|
||||
.name())));
|
||||
writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()),
|
||||
outStream);
|
||||
outStream.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "... exception on writing blob", e);
|
||||
}
|
||||
} else {
|
||||
pReturn.putString(ret.RESULT.name(), out.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class AskForSecretKeyPassPhrase {
|
||||
public static interface PassPhraseCallbackInterface {
|
||||
void passPhraseCallback(long keyId, String passPhrase);
|
||||
}
|
||||
|
||||
public static Dialog createDialog(Activity context, long secretKeyId,
|
||||
PassPhraseCallbackInterface callback) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(context);
|
||||
|
||||
alert.setTitle(R.string.title_authentication);
|
||||
|
||||
final PGPSecretKey secretKey;
|
||||
final Activity activity = context;
|
||||
|
||||
if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) {
|
||||
secretKey = null;
|
||||
alert.setMessage(context.getString(R.string.passPhraseForSymmetricEncryption));
|
||||
} else {
|
||||
secretKey = Apg.getMasterKey(Apg.getSecretKeyRing(secretKeyId));
|
||||
if (secretKey == null) {
|
||||
alert.setTitle(R.string.title_keyNotFound);
|
||||
alert.setMessage(context.getString(R.string.keyNotFound, secretKeyId));
|
||||
alert.setPositiveButton(android.R.string.ok, new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
activity.removeDialog(Id.dialog.pass_phrase);
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
return alert.create();
|
||||
}
|
||||
String userId = Apg.getMainUserIdSafe(context, secretKey);
|
||||
alert.setMessage(context.getString(R.string.passPhraseFor, userId));
|
||||
}
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.passphrase, null);
|
||||
final EditText input = (EditText) view.findViewById(R.id.passphrase_passphrase);
|
||||
|
||||
final TextView labelNotUsed = (TextView) view
|
||||
.findViewById(R.id.passphrase_label_passphrase_again);
|
||||
labelNotUsed.setVisibility(View.GONE);
|
||||
final EditText inputNotUsed = (EditText) view
|
||||
.findViewById(R.id.passphrase_passphrase_again);
|
||||
inputNotUsed.setVisibility(View.GONE);
|
||||
|
||||
alert.setView(view);
|
||||
|
||||
final PassPhraseCallbackInterface cb = callback;
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
activity.removeDialog(Id.dialog.pass_phrase);
|
||||
String passPhrase = input.getText().toString();
|
||||
long keyId;
|
||||
if (secretKey != null) {
|
||||
try {
|
||||
PGPPrivateKey testKey = secretKey.extractPrivateKey(
|
||||
passPhrase.toCharArray(), new BouncyCastleProvider());
|
||||
if (testKey == null) {
|
||||
Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Toast.makeText(activity, R.string.wrongPassPhrase, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
keyId = secretKey.getKeyID();
|
||||
} else {
|
||||
keyId = Id.key.symmetric;
|
||||
}
|
||||
cb.passPhraseCallback(keyId, passPhrase);
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
activity.removeDialog(Id.dialog.pass_phrase);
|
||||
}
|
||||
});
|
||||
|
||||
// check if the key has no passphrase
|
||||
if (secretKey != null) {
|
||||
try {
|
||||
Log.d("APG", "check if key has no passphrase...");
|
||||
PGPPrivateKey testKey = secretKey.extractPrivateKey("".toCharArray(),
|
||||
new BouncyCastleProvider());
|
||||
if (testKey != null) {
|
||||
Log.d("APG", "Key has no passphrase!");
|
||||
|
||||
cb.passPhraseCallback(secretKey.getKeyID(), null);
|
||||
|
||||
return null;
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
|
||||
}
|
||||
}
|
||||
return alert.create();
|
||||
}
|
||||
}
|
||||
62
org_apg/src/org/thialfihar/android/apg/CachedPassPhrase.java
Normal file
62
org_apg/src/org/thialfihar/android/apg/CachedPassPhrase.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class CachedPassPhrase {
|
||||
public final long timestamp;
|
||||
public final String passPhrase;
|
||||
|
||||
public CachedPassPhrase(long timestamp, String passPhrase) {
|
||||
super();
|
||||
this.timestamp = timestamp;
|
||||
this.passPhrase = passPhrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hc1 = (int) (this.timestamp & 0xffffffff);
|
||||
int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode());
|
||||
return (hc1 + hc2) * hc2 + hc1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof CachedPassPhrase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CachedPassPhrase o = (CachedPassPhrase) other;
|
||||
if (timestamp != o.timestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (passPhrase != o.passPhrase) {
|
||||
if (passPhrase == null || o.passPhrase == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!passPhrase.equals(o.passPhrase)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + timestamp + ", *******)";
|
||||
}
|
||||
}
|
||||
54
org_apg/src/org/thialfihar/android/apg/Constants.java
Normal file
54
org_apg/src/org/thialfihar/android/apg/Constants.java
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 android.os.Environment;
|
||||
|
||||
public final class Constants {
|
||||
|
||||
public static final String TAG = "APG";
|
||||
|
||||
public static final class path {
|
||||
public static final String APP_DIR = Environment.getExternalStorageDirectory() + "/APG";
|
||||
}
|
||||
|
||||
public static final class pref {
|
||||
public static final String HAS_SEEN_HELP = "seenHelp";
|
||||
public static final String HAS_SEEN_CHANGE_LOG = "seenChangeLogDialog";
|
||||
public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm";
|
||||
public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm";
|
||||
public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour";
|
||||
public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression";
|
||||
public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression";
|
||||
public static final String PASS_PHRASE_CACHE_TTL = "passPhraseCacheTtl";
|
||||
public static final String LANGUAGE = "language";
|
||||
public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
|
||||
public static final String KEY_SERVERS = "keyServers";
|
||||
}
|
||||
|
||||
public static final class defaults {
|
||||
public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu";
|
||||
}
|
||||
|
||||
public static final class extras {
|
||||
public static final String PROGRESS = "progress";
|
||||
public static final String PROGRESS_MAX = "max";
|
||||
public static final String STATUS = "status";
|
||||
public static final String MESSAGE = "message";
|
||||
public static final String KEY_ID = "keyId";
|
||||
}
|
||||
}
|
||||
95
org_apg/src/org/thialfihar/android/apg/DataDestination.java
Normal file
95
org_apg/src/org/thialfihar/android/apg/DataDestination.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg.GeneralException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
public class DataDestination {
|
||||
private String mStreamFilename;
|
||||
private String mFilename;
|
||||
private int mMode = Id.mode.undefined;
|
||||
|
||||
public DataDestination() {
|
||||
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
mMode = mode;
|
||||
}
|
||||
|
||||
public void setFilename(String filename) {
|
||||
mFilename = filename;
|
||||
}
|
||||
|
||||
public String getStreamFilename() {
|
||||
return mStreamFilename;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream(Context context) throws Apg.GeneralException,
|
||||
FileNotFoundException, IOException {
|
||||
OutputStream out = null;
|
||||
mStreamFilename = null;
|
||||
|
||||
switch (mMode) {
|
||||
case Id.mode.stream: {
|
||||
try {
|
||||
while (true) {
|
||||
mStreamFilename = Apg.generateRandomString(32);
|
||||
if (mStreamFilename == null) {
|
||||
throw new Apg.GeneralException("couldn't generate random file name");
|
||||
}
|
||||
context.openFileInput(mStreamFilename).close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// found a name that isn't used yet
|
||||
}
|
||||
out = context.openFileOutput(mStreamFilename, Context.MODE_PRIVATE);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.mode.byte_array: {
|
||||
out = new ByteArrayOutputStream();
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.mode.file: {
|
||||
if (mFilename.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
|
||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
throw new GeneralException(
|
||||
context.getString(R.string.error_externalStorageNotReady));
|
||||
}
|
||||
}
|
||||
out = new FileOutputStream(mFilename);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
118
org_apg/src/org/thialfihar/android/apg/DataSource.java
Normal file
118
org_apg/src/org/thialfihar/android/apg/DataSource.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg.GeneralException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
|
||||
public class DataSource {
|
||||
private Uri mContentUri = null;
|
||||
private String mText = null;
|
||||
private byte[] mData = null;
|
||||
|
||||
public DataSource() {
|
||||
|
||||
}
|
||||
|
||||
public void setUri(Uri uri) {
|
||||
mContentUri = uri;
|
||||
mText = null;
|
||||
mData = null;
|
||||
}
|
||||
|
||||
public void setUri(String uri) {
|
||||
if (uri.startsWith("/")) {
|
||||
setUri(Uri.parse("file://" + uri));
|
||||
} else {
|
||||
setUri(Uri.parse(uri));
|
||||
}
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
mText = text;
|
||||
mData = null;
|
||||
mContentUri = null;
|
||||
}
|
||||
|
||||
public void setData(byte[] data) {
|
||||
mData = data;
|
||||
mText = null;
|
||||
mContentUri = null;
|
||||
}
|
||||
|
||||
public boolean isText() {
|
||||
return mText != null;
|
||||
}
|
||||
|
||||
public boolean isBinary() {
|
||||
return mData != null || mContentUri != null;
|
||||
}
|
||||
|
||||
public InputData getInputData(Context context, boolean withSize) throws GeneralException,
|
||||
FileNotFoundException, IOException {
|
||||
InputStream in = null;
|
||||
long size = 0;
|
||||
|
||||
if (mContentUri != null) {
|
||||
if (mContentUri.getScheme().equals("file")) {
|
||||
// get the rest after "file://"
|
||||
String path = Uri.decode(mContentUri.toString().substring(7));
|
||||
if (path.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
|
||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
throw new GeneralException(
|
||||
context.getString(R.string.error_externalStorageNotReady));
|
||||
}
|
||||
}
|
||||
in = new FileInputStream(path);
|
||||
File file = new File(path);
|
||||
if (withSize) {
|
||||
size = file.length();
|
||||
}
|
||||
} else {
|
||||
in = context.getContentResolver().openInputStream(mContentUri);
|
||||
if (withSize) {
|
||||
InputStream tmp = context.getContentResolver().openInputStream(mContentUri);
|
||||
size = Apg.getLengthOfStream(tmp);
|
||||
tmp.close();
|
||||
}
|
||||
}
|
||||
} else if (mText != null || mData != null) {
|
||||
byte[] bytes = null;
|
||||
if (mData != null) {
|
||||
bytes = mData;
|
||||
} else {
|
||||
bytes = mText.getBytes();
|
||||
}
|
||||
in = new ByteArrayInputStream(bytes);
|
||||
if (withSize) {
|
||||
size = bytes.length;
|
||||
}
|
||||
}
|
||||
|
||||
return new InputData(in, size);
|
||||
}
|
||||
|
||||
}
|
||||
129
org_apg/src/org/thialfihar/android/apg/FileDialog.java
Normal file
129
org_apg/src/org/thialfihar/android/apg/FileDialog.java
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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 org.thialfihar.android.apg.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class FileDialog {
|
||||
private static EditText mFilename;
|
||||
private static ImageButton mBrowse;
|
||||
private static CheckBox mCheckBox;
|
||||
private static Activity mActivity;
|
||||
private static int mRequestCode;
|
||||
|
||||
public static interface OnClickListener {
|
||||
public void onCancelClick();
|
||||
|
||||
public void onOkClick(String filename, boolean checkbox);
|
||||
}
|
||||
|
||||
public static AlertDialog build(Activity activity, String title, String message,
|
||||
String defaultFile, OnClickListener onClickListener, String fileManagerTitle,
|
||||
String fileManagerButton, String checkboxText, int requestCode) {
|
||||
// TODO: fileManagerTitle and fileManagerButton are deprecated, no use for them right now,
|
||||
// but maybe the Intent now used will someday support them again, so leaving them in
|
||||
LayoutInflater inflater = (LayoutInflater) activity
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setTitle(title);
|
||||
alert.setMessage(message);
|
||||
|
||||
View view = inflater.inflate(R.layout.file_dialog, null);
|
||||
|
||||
mActivity = activity;
|
||||
mFilename = (EditText) view.findViewById(R.id.input);
|
||||
mFilename.setText(defaultFile);
|
||||
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
openFile();
|
||||
}
|
||||
});
|
||||
mRequestCode = requestCode;
|
||||
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
|
||||
if (checkboxText == null) {
|
||||
mCheckBox.setEnabled(false);
|
||||
mCheckBox.setVisibility(View.GONE);
|
||||
} else {
|
||||
mCheckBox.setEnabled(true);
|
||||
mCheckBox.setVisibility(View.VISIBLE);
|
||||
mCheckBox.setText(checkboxText);
|
||||
}
|
||||
|
||||
alert.setView(view);
|
||||
|
||||
final OnClickListener clickListener = onClickListener;
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
boolean checked = false;
|
||||
if (mCheckBox.isEnabled()) {
|
||||
checked = mCheckBox.isChecked();
|
||||
}
|
||||
clickListener.onOkClick(mFilename.getText().toString(), checked);
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
clickListener.onCancelClick();
|
||||
}
|
||||
});
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
public static void setFilename(String filename) {
|
||||
if (mFilename != null) {
|
||||
mFilename.setText(filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the file manager to select a file to open.
|
||||
*/
|
||||
private static void openFile() {
|
||||
String filename = mFilename.getText().toString();
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
|
||||
intent.setData(Uri.parse("file://" + filename));
|
||||
intent.setType("text/plain"); // only .asc or .gpg files
|
||||
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, mRequestCode);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// No compatible file manager was found.
|
||||
Toast.makeText(mActivity, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
256
org_apg/src/org/thialfihar/android/apg/HkpKeyServer.java
Normal file
256
org_apg/src/org/thialfihar/android/apg/HkpKeyServer.java
Normal file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import android.text.Html;
|
||||
|
||||
public class HkpKeyServer extends KeyServer {
|
||||
private static class HttpError extends Exception {
|
||||
private static final long serialVersionUID = 1718783705229428893L;
|
||||
private int mCode;
|
||||
private String mData;
|
||||
|
||||
public HttpError(int code, String data) {
|
||||
super("" + code + ": " + data);
|
||||
mCode = code;
|
||||
mData = data;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return mCode;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return mData;
|
||||
}
|
||||
}
|
||||
|
||||
private String mHost;
|
||||
private short mPort = 11371;
|
||||
|
||||
// example:
|
||||
// pub 2048R/<a href="/pks/lookup?op=get&search=0x887DF4BE9F5C9090">9F5C9090</a> 2009-08-17 <a
|
||||
// href="/pks/lookup?op=vindex&search=0x887DF4BE9F5C9090">Jörg Runge
|
||||
// <joerg@joergrunge.de></a>
|
||||
public static Pattern PUB_KEY_LINE = Pattern
|
||||
.compile(
|
||||
"pub +([0-9]+)([a-z]+)/.*?0x([0-9a-z]+).*? +([0-9-]+) +(.+)[\n\r]+((?: +.+[\n\r]+)*)",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static Pattern USER_ID_LINE = Pattern.compile("^ +(.+)$", Pattern.MULTILINE
|
||||
| Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public HkpKeyServer(String host) {
|
||||
mHost = host;
|
||||
}
|
||||
|
||||
public HkpKeyServer(String host, short port) {
|
||||
mHost = host;
|
||||
mPort = port;
|
||||
}
|
||||
|
||||
static private String readAll(InputStream in, String encoding) throws IOException {
|
||||
ByteArrayOutputStream raw = new ByteArrayOutputStream();
|
||||
|
||||
byte buffer[] = new byte[1 << 16];
|
||||
int n = 0;
|
||||
while ((n = in.read(buffer)) != -1) {
|
||||
raw.write(buffer, 0, n);
|
||||
}
|
||||
|
||||
if (encoding == null) {
|
||||
encoding = "utf8";
|
||||
}
|
||||
return raw.toString(encoding);
|
||||
}
|
||||
|
||||
// TODO: replace this with httpclient
|
||||
private String query(String request) throws QueryException, HttpError {
|
||||
InetAddress ips[];
|
||||
try {
|
||||
ips = InetAddress.getAllByName(mHost);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new QueryException(e.toString());
|
||||
}
|
||||
for (int i = 0; i < ips.length; ++i) {
|
||||
try {
|
||||
String url = "http://" + ips[i].getHostAddress() + ":" + mPort + request;
|
||||
URL realUrl = new URL(url);
|
||||
HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(25000);
|
||||
conn.connect();
|
||||
int response = conn.getResponseCode();
|
||||
if (response >= 200 && response < 300) {
|
||||
return readAll(conn.getInputStream(), conn.getContentEncoding());
|
||||
} else {
|
||||
String data = readAll(conn.getErrorStream(), conn.getContentEncoding());
|
||||
throw new HttpError(response, data);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
// nothing to do, try next IP
|
||||
} catch (IOException e) {
|
||||
// nothing to do, try next IP
|
||||
}
|
||||
}
|
||||
|
||||
throw new QueryException("querying server(s) for '" + mHost + "' failed");
|
||||
}
|
||||
|
||||
// TODO: replace this with httpclient
|
||||
@Override
|
||||
public List<KeyInfo> search(String query) throws QueryException, TooManyResponses,
|
||||
InsufficientQuery {
|
||||
Vector<KeyInfo> results = new Vector<KeyInfo>();
|
||||
|
||||
if (query.length() < 3) {
|
||||
throw new InsufficientQuery();
|
||||
}
|
||||
|
||||
String encodedQuery;
|
||||
try {
|
||||
encodedQuery = URLEncoder.encode(query, "utf8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
String request = "/pks/lookup?op=index&search=" + encodedQuery;
|
||||
|
||||
String data = null;
|
||||
try {
|
||||
data = query(request);
|
||||
} catch (HttpError e) {
|
||||
if (e.getCode() == 404) {
|
||||
return results;
|
||||
} else {
|
||||
if (e.getData().toLowerCase().contains("no keys found")) {
|
||||
return results;
|
||||
} else if (e.getData().toLowerCase().contains("too many")) {
|
||||
throw new TooManyResponses();
|
||||
} else if (e.getData().toLowerCase().contains("insufficient")) {
|
||||
throw new InsufficientQuery();
|
||||
}
|
||||
}
|
||||
throw new QueryException("querying server(s) for '" + mHost + "' failed");
|
||||
}
|
||||
|
||||
Matcher matcher = PUB_KEY_LINE.matcher(data);
|
||||
while (matcher.find()) {
|
||||
KeyInfo info = new KeyInfo();
|
||||
info.size = Integer.parseInt(matcher.group(1));
|
||||
info.algorithm = matcher.group(2);
|
||||
info.keyId = Apg.keyFromHex(matcher.group(3));
|
||||
info.fingerPrint = Apg.getSmallFingerPrint(info.keyId);
|
||||
String chunks[] = matcher.group(4).split("-");
|
||||
info.date = new GregorianCalendar(Integer.parseInt(chunks[0]),
|
||||
Integer.parseInt(chunks[1]), Integer.parseInt(chunks[2])).getTime();
|
||||
info.userIds = new Vector<String>();
|
||||
if (matcher.group(5).startsWith("*** KEY")) {
|
||||
info.revoked = matcher.group(5);
|
||||
} else {
|
||||
String tmp = matcher.group(5).replaceAll("<.*?>", "");
|
||||
tmp = Html.fromHtml(tmp).toString();
|
||||
info.userIds.add(tmp);
|
||||
}
|
||||
if (matcher.group(6).length() > 0) {
|
||||
Matcher matcher2 = USER_ID_LINE.matcher(matcher.group(6));
|
||||
while (matcher2.find()) {
|
||||
String tmp = matcher2.group(1).replaceAll("<.*?>", "");
|
||||
tmp = Html.fromHtml(tmp).toString();
|
||||
info.userIds.add(tmp);
|
||||
}
|
||||
}
|
||||
results.add(info);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(long keyId) throws QueryException {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
try {
|
||||
HttpGet get = new HttpGet("http://" + mHost + ":" + mPort
|
||||
+ "/pks/lookup?op=get&search=0x" + Apg.keyToHex(keyId));
|
||||
|
||||
HttpResponse response = client.execute(get);
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
|
||||
throw new QueryException("not found");
|
||||
}
|
||||
|
||||
HttpEntity entity = response.getEntity();
|
||||
InputStream is = entity.getContent();
|
||||
String data = readAll(is, EntityUtils.getContentCharSet(entity));
|
||||
Matcher matcher = Apg.PGP_PUBLIC_KEY.matcher(data);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// nothing to do, better luck on the next keyserver
|
||||
} finally {
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void add(String armouredText) throws AddKeyException {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
try {
|
||||
HttpPost post = new HttpPost("http://" + mHost + ":" + mPort + "/pks/add");
|
||||
|
||||
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
|
||||
nameValuePairs.add(new BasicNameValuePair("keytext", armouredText));
|
||||
post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
|
||||
|
||||
HttpResponse response = client.execute(post);
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
|
||||
throw new AddKeyException();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// nothing to do, better luck on the next keyserver
|
||||
} finally {
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
125
org_apg/src/org/thialfihar/android/apg/IApgService.aidl
Normal file
125
org_apg/src/org/thialfihar/android/apg/IApgService.aidl
Normal file
@@ -0,0 +1,125 @@
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
interface IApgService {
|
||||
|
||||
/* All functions fill the returnVals Bundle with the following keys:
|
||||
*
|
||||
* ArrayList<String> "WARNINGS" = Warnings, if any
|
||||
* ArrayList<String> "ERRORS" = Human readable error descriptions, if any
|
||||
* int "ERROR" = Numeric representation of error, if any
|
||||
* starting with 100:
|
||||
* 100: Required argument missing
|
||||
* 101: Generic failure of APG
|
||||
* 102: No matching private key found
|
||||
* 103: Private key's passphrase wrong
|
||||
* 104: Private key's passphrase missing
|
||||
*/
|
||||
|
||||
/* ********************************************************
|
||||
* Encryption
|
||||
* ********************************************************/
|
||||
|
||||
/* All encryption function's arguments
|
||||
*
|
||||
* Bundle params' keys:
|
||||
* (optional/required)
|
||||
* TYPE "STRING KEY" = EXPLANATION / VALUES
|
||||
*
|
||||
* (required)
|
||||
* String "MESSAGE" = Message to encrypt
|
||||
* OR
|
||||
* String "BLOB" = ContentUri to a file handle
|
||||
* with binary data to encrypt
|
||||
* (Attention: file will be overwritten
|
||||
* with encrypted content!)
|
||||
*
|
||||
* (optional)
|
||||
* int "ENCRYPTION_ALGORYTHM" = Encryption Algorithm
|
||||
* 7: AES-128, 8: AES-192, 9: AES-256,
|
||||
* 4: Blowfish, 10: Twofish, 3: CAST5,
|
||||
* 6: DES, 2: Triple DES, 1: IDEA
|
||||
* (optional)
|
||||
* int "HASH_ALGORYTHM" = Hash Algorithm
|
||||
* 1: MD5, 3: RIPEMD-160, 2: SHA-1,
|
||||
* 11: SHA-224, 8: SHA-256, 9: SHA-384,
|
||||
* 10: SHA-512
|
||||
* (optional)
|
||||
* Boolean "ARMORED_OUTPUT" = Armor output
|
||||
*
|
||||
* (optional)
|
||||
* Boolean "FORCE_V3_SIGNATURE" = Force V3 Signatures
|
||||
*
|
||||
* (optional)
|
||||
* int "COMPRESSION" = Compression to use
|
||||
* 0x21070001: none, 1: Zip, 2: Zlib,
|
||||
* 3: BZip2
|
||||
* (optional)
|
||||
* String "SIGNATURE_KEY" = Key to sign with
|
||||
*
|
||||
* (optional)
|
||||
* String "PRIVATE_KEY_PASSPHRASE" = Passphrase for signing key
|
||||
*
|
||||
* Bundle returnVals (in addition to the ERRORS/WARNINGS above):
|
||||
* If "MESSAGE" was set:
|
||||
* String "RESULT" = Encrypted message
|
||||
*/
|
||||
|
||||
/* Additional argument for function below:
|
||||
* (required)
|
||||
* String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase to use
|
||||
*/
|
||||
boolean encryptWithPassphrase(in Bundle params, out Bundle returnVals);
|
||||
|
||||
/* Additional argument:
|
||||
* (required)
|
||||
* ArrayList<String> "PUBLIC_KEYS" = Public keys (8char fingerprint "123ABC12" OR
|
||||
* complete id "Alice Meyer <ab@email.com>")
|
||||
*/
|
||||
boolean encryptWithPublicKey(in Bundle params, out Bundle returnVals);
|
||||
|
||||
/* ********************************************************
|
||||
* Decryption
|
||||
* ********************************************************/
|
||||
|
||||
/* Bundle params:
|
||||
* (required)
|
||||
* String "MESSAGE" = Message to dencrypt
|
||||
* OR
|
||||
* String "BLOB" = ContentUri to a file handle
|
||||
* with binary data to dencrypt
|
||||
* (Attention: file will be overwritten
|
||||
* with dencrypted content!)
|
||||
*
|
||||
* (optional)
|
||||
* String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase for decryption
|
||||
*
|
||||
* (optional)
|
||||
* String "PRIVATE_KEY_PASSPHRASE" = Private keys's passphrase on asymmetric encryption
|
||||
*
|
||||
* Bundle return_vals:
|
||||
* If "MESSAGE" was set:
|
||||
* String "RESULT" = Decrypted message
|
||||
*/
|
||||
boolean decrypt(in Bundle params, out Bundle returnVals);
|
||||
|
||||
/* ********************************************************
|
||||
* Get key information
|
||||
* ********************************************************/
|
||||
|
||||
/* Get info about all available keys
|
||||
*
|
||||
* Bundle params:
|
||||
* (required)
|
||||
* int "KEY_TYPE" = info about what type of keys to return
|
||||
* 0: public keys
|
||||
* 1: private keys
|
||||
*
|
||||
* Returns:
|
||||
* StringArrayList "FINGERPRINTS" = Short fingerprints of keys
|
||||
*
|
||||
* StringArrayList "USER_IDS" = User ids of corresponding fingerprints
|
||||
* (order is the same as in FINGERPRINTS)
|
||||
*/
|
||||
boolean getKeys(in Bundle params, out Bundle returnVals);
|
||||
|
||||
}
|
||||
193
org_apg/src/org/thialfihar/android/apg/Id.java
Normal file
193
org_apg/src/org/thialfihar/android/apg/Id.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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 org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
|
||||
public final class Id {
|
||||
|
||||
public static final String TAG = "APG";
|
||||
|
||||
public static final class menu {
|
||||
public static final int export = 0x21070001;
|
||||
public static final int delete = 0x21070002;
|
||||
public static final int edit = 0x21070003;
|
||||
public static final int update = 0x21070004;
|
||||
public static final int exportToServer = 0x21070005;
|
||||
public static final int share = 0x21070006;
|
||||
public static final int signKey = 0x21070007;
|
||||
|
||||
public static final class option {
|
||||
public static final int new_pass_phrase = 0x21070001;
|
||||
public static final int create = 0x21070002;
|
||||
public static final int about = 0x21070003;
|
||||
public static final int manage_public_keys = 0x21070004;
|
||||
public static final int manage_secret_keys = 0x21070005;
|
||||
public static final int import_keys = 0x21070006;
|
||||
public static final int export_keys = 0x21070007;
|
||||
public static final int preferences = 0x21070008;
|
||||
public static final int search = 0x21070009;
|
||||
public static final int help = 0x21070010;
|
||||
public static final int key_server = 0x21070011;
|
||||
public static final int scanQRCode = 0x21070012;
|
||||
public static final int encrypt = 0x21070013;
|
||||
public static final int encrypt_to_clipboard = 0x21070014;
|
||||
public static final int decrypt = 0x21070015;
|
||||
public static final int reply = 0x21070016;
|
||||
public static final int cancel = 0x21070017;
|
||||
public static final int save = 0x21070018;
|
||||
public static final int okay = 0x21070019;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static final class message {
|
||||
public static final int progress_update = 0x21070001;
|
||||
public static final int done = 0x21070002;
|
||||
public static final int import_keys = 0x21070003;
|
||||
public static final int export_keys = 0x21070004;
|
||||
public static final int import_done = 0x21070005;
|
||||
public static final int export_done = 0x21070006;
|
||||
public static final int create_key = 0x21070007;
|
||||
public static final int edit_key = 0x21070008;
|
||||
public static final int delete_done = 0x21070009;
|
||||
public static final int query_done = 0x21070010;
|
||||
public static final int unknown_signature_key = 0x21070011;
|
||||
}
|
||||
|
||||
public static final class request {
|
||||
public static final int public_keys = 0x21070001;
|
||||
public static final int secret_keys = 0x21070002;
|
||||
public static final int filename = 0x21070003;
|
||||
public static final int output_filename = 0x21070004;
|
||||
public static final int key_server_preference = 0x21070005;
|
||||
public static final int look_up_key_id = 0x21070006;
|
||||
public static final int export_to_server = 0x21070007;
|
||||
public static final int import_from_qr_code = 0x21070008;
|
||||
public static final int sign_key = 0x21070009;
|
||||
}
|
||||
|
||||
public static final class dialog {
|
||||
public static final int pass_phrase = 0x21070001;
|
||||
public static final int encrypting = 0x21070002;
|
||||
public static final int decrypting = 0x21070003;
|
||||
public static final int new_pass_phrase = 0x21070004;
|
||||
public static final int pass_phrases_do_not_match = 0x21070005;
|
||||
public static final int no_pass_phrase = 0x21070006;
|
||||
public static final int saving = 0x21070007;
|
||||
public static final int delete_key = 0x21070008;
|
||||
public static final int import_keys = 0x21070009;
|
||||
public static final int importing = 0x2107000a;
|
||||
public static final int export_key = 0x2107000b;
|
||||
public static final int export_keys = 0x2107000c;
|
||||
public static final int exporting = 0x2107000d;
|
||||
public static final int new_account = 0x2107000e;
|
||||
// public static final int about = 0x2107000f;
|
||||
public static final int change_log = 0x21070010;
|
||||
public static final int output_filename = 0x21070011;
|
||||
public static final int delete_file = 0x21070012;
|
||||
public static final int deleting = 0x21070013;
|
||||
public static final int help = 0x21070014;
|
||||
public static final int querying = 0x21070015;
|
||||
public static final int lookup_unknown_key = 0x21070016;
|
||||
public static final int signing = 0x21070017;
|
||||
}
|
||||
|
||||
public static final class task {
|
||||
public static final int import_keys = 0x21070001;
|
||||
public static final int export_keys = 0x21070002;
|
||||
}
|
||||
|
||||
public static final class database {
|
||||
public static final int type_public = 0;
|
||||
public static final int type_secret = 1;
|
||||
}
|
||||
|
||||
public static final class type {
|
||||
public static final int public_key = 0x21070001;
|
||||
public static final int secret_key = 0x21070002;
|
||||
public static final int user_id = 0x21070003;
|
||||
public static final int key = 0x21070004;
|
||||
}
|
||||
|
||||
public static final class choice {
|
||||
public static final class algorithm {
|
||||
public static final int dsa = 0x21070001;
|
||||
public static final int elgamal = 0x21070002;
|
||||
public static final int rsa = 0x21070003;
|
||||
}
|
||||
|
||||
public static final class compression {
|
||||
public static final int none = 0x21070001;
|
||||
public static final int zlib = CompressionAlgorithmTags.ZLIB;
|
||||
public static final int bzip2 = CompressionAlgorithmTags.BZIP2;
|
||||
public static final int zip = CompressionAlgorithmTags.ZIP;
|
||||
}
|
||||
|
||||
public static final class usage {
|
||||
public static final int sign_only = 0x21070001;
|
||||
public static final int encrypt_only = 0x21070002;
|
||||
public static final int sign_and_encrypt = 0x21070003;
|
||||
}
|
||||
|
||||
public static final class action {
|
||||
public static final int encrypt = 0x21070001;
|
||||
public static final int decrypt = 0x21070002;
|
||||
public static final int import_public = 0x21070003;
|
||||
public static final int import_secret = 0x21070004;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class return_value {
|
||||
public static final int ok = 0;
|
||||
public static final int error = -1;
|
||||
public static final int no_master_key = -2;
|
||||
public static final int updated = 1;
|
||||
public static final int bad = -3;
|
||||
}
|
||||
|
||||
public static final class target {
|
||||
public static final int clipboard = 0x21070001;
|
||||
public static final int email = 0x21070002;
|
||||
public static final int file = 0x21070003;
|
||||
public static final int message = 0x21070004;
|
||||
}
|
||||
|
||||
public static final class mode {
|
||||
public static final int undefined = 0x21070001;
|
||||
public static final int byte_array = 0x21070002;
|
||||
public static final int file = 0x21070003;
|
||||
public static final int stream = 0x21070004;
|
||||
}
|
||||
|
||||
public static final class key {
|
||||
public static final int none = 0;
|
||||
public static final int symmetric = -1;
|
||||
}
|
||||
|
||||
public static final class content {
|
||||
public static final int unknown = 0;
|
||||
public static final int encrypted_data = 1;
|
||||
public static final int keys = 2;
|
||||
}
|
||||
|
||||
public static final class keyserver {
|
||||
public static final int search = 0x21070001;
|
||||
public static final int get = 0x21070002;
|
||||
public static final int add = 0x21070003;
|
||||
}
|
||||
}
|
||||
39
org_apg/src/org/thialfihar/android/apg/InputData.java
Normal file
39
org_apg/src/org/thialfihar/android/apg/InputData.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.InputStream;
|
||||
|
||||
public class InputData {
|
||||
private PositionAwareInputStream mInputStream;
|
||||
private long mSize;
|
||||
|
||||
public InputData(InputStream inputStream, long size) {
|
||||
mInputStream = new PositionAwareInputStream(inputStream);
|
||||
mSize = size;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return mInputStream;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
public long getStreamPosition() {
|
||||
return mInputStream.position();
|
||||
}
|
||||
}
|
||||
60
org_apg/src/org/thialfihar/android/apg/KeyServer.java
Normal file
60
org_apg/src/org/thialfihar/android/apg/KeyServer.java
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
public abstract class KeyServer {
|
||||
static public class QueryException extends Exception {
|
||||
private static final long serialVersionUID = 2703768928624654512L;
|
||||
|
||||
public QueryException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
static public class TooManyResponses extends Exception {
|
||||
private static final long serialVersionUID = 2703768928624654513L;
|
||||
}
|
||||
|
||||
static public class InsufficientQuery extends Exception {
|
||||
private static final long serialVersionUID = 2703768928624654514L;
|
||||
}
|
||||
|
||||
static public class AddKeyException extends Exception {
|
||||
private static final long serialVersionUID = -507574859137295530L;
|
||||
}
|
||||
|
||||
static public class KeyInfo implements Serializable {
|
||||
private static final long serialVersionUID = -7797972113284992662L;
|
||||
public Vector<String> userIds;
|
||||
public String revoked;
|
||||
public Date date;
|
||||
public String fingerPrint;
|
||||
public long keyId;
|
||||
public int size;
|
||||
public String algorithm;
|
||||
}
|
||||
|
||||
abstract List<KeyInfo> search(String query) throws QueryException, TooManyResponses,
|
||||
InsufficientQuery;
|
||||
|
||||
abstract String get(long keyId) throws QueryException;
|
||||
|
||||
abstract void add(String armouredText) throws AddKeyException;
|
||||
}
|
||||
49
org_apg/src/org/thialfihar/android/apg/PausableThread.java
Normal file
49
org_apg/src/org/thialfihar/android/apg/PausableThread.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class PausableThread extends Thread {
|
||||
private boolean mPaused = false;
|
||||
|
||||
public PausableThread(Runnable runnable) {
|
||||
super(runnable);
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
synchronized (this) {
|
||||
mPaused = true;
|
||||
while (mPaused) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unpause() {
|
||||
synchronized (this) {
|
||||
mPaused = false;
|
||||
notify();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
synchronized (this) {
|
||||
return mPaused;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class PositionAwareInputStream extends InputStream {
|
||||
private InputStream mStream;
|
||||
private long mPosition;
|
||||
|
||||
public PositionAwareInputStream(InputStream in) {
|
||||
mStream = in;
|
||||
mPosition = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int ch = mStream.read();
|
||||
++mPosition;
|
||||
return ch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return mStream.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mStream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
int result = mStream.read(b);
|
||||
mPosition += result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int offset, int length) throws IOException {
|
||||
int result = mStream.read(b, offset, length);
|
||||
mPosition += result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
mStream.reset();
|
||||
mPosition = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
long result = mStream.skip(n);
|
||||
mPosition += result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public long position() {
|
||||
return mPosition;
|
||||
}
|
||||
}
|
||||
184
org_apg/src/org/thialfihar/android/apg/Preferences.java
Normal file
184
org_apg/src/org/thialfihar/android/apg/Preferences.java
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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 org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
public class Preferences {
|
||||
private static Preferences mPreferences;
|
||||
private SharedPreferences mSharedPreferences;
|
||||
|
||||
public static synchronized Preferences getPreferences(Context context) {
|
||||
return getPreferences(context, false);
|
||||
}
|
||||
|
||||
public static synchronized Preferences getPreferences(Context context, boolean force_new) {
|
||||
if (mPreferences == null || force_new) {
|
||||
mPreferences = new Preferences(context);
|
||||
}
|
||||
return mPreferences;
|
||||
}
|
||||
|
||||
private Preferences(Context context) {
|
||||
mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return mSharedPreferences.getString(Constants.pref.LANGUAGE, "");
|
||||
}
|
||||
|
||||
public void setLanguage(String value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putString(Constants.pref.LANGUAGE, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public int getPassPhraseCacheTtl() {
|
||||
int ttl = mSharedPreferences.getInt(Constants.pref.PASS_PHRASE_CACHE_TTL, 180);
|
||||
// fix the value if it was set to "never" in previous versions, which currently is not
|
||||
// supported
|
||||
if (ttl == 0) {
|
||||
ttl = 180;
|
||||
}
|
||||
return ttl;
|
||||
}
|
||||
|
||||
public void setPassPhraseCacheTtl(int value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putInt(Constants.pref.PASS_PHRASE_CACHE_TTL, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public int getDefaultEncryptionAlgorithm() {
|
||||
return mSharedPreferences.getInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM,
|
||||
PGPEncryptedData.AES_256);
|
||||
}
|
||||
|
||||
public void setDefaultEncryptionAlgorithm(int value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public int getDefaultHashAlgorithm() {
|
||||
return mSharedPreferences.getInt(Constants.pref.DEFAULT_HASH_ALGORITHM,
|
||||
HashAlgorithmTags.SHA256);
|
||||
}
|
||||
|
||||
public void setDefaultHashAlgorithm(int value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putInt(Constants.pref.DEFAULT_HASH_ALGORITHM, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public int getDefaultMessageCompression() {
|
||||
return mSharedPreferences.getInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION,
|
||||
Id.choice.compression.zlib);
|
||||
}
|
||||
|
||||
public void setDefaultMessageCompression(int value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public int getDefaultFileCompression() {
|
||||
return mSharedPreferences.getInt(Constants.pref.DEFAULT_FILE_COMPRESSION,
|
||||
Id.choice.compression.none);
|
||||
}
|
||||
|
||||
public void setDefaultFileCompression(int value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putInt(Constants.pref.DEFAULT_FILE_COMPRESSION, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getDefaultAsciiArmour() {
|
||||
return mSharedPreferences.getBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, false);
|
||||
}
|
||||
|
||||
public void setDefaultAsciiArmour(boolean value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getForceV3Signatures() {
|
||||
return mSharedPreferences.getBoolean(Constants.pref.FORCE_V3_SIGNATURES, false);
|
||||
}
|
||||
|
||||
public void setForceV3Signatures(boolean value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Constants.pref.FORCE_V3_SIGNATURES, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean hasSeenChangeLog(String version) {
|
||||
return mSharedPreferences.getBoolean(Constants.pref.HAS_SEEN_CHANGE_LOG + version, false);
|
||||
}
|
||||
|
||||
public void setHasSeenChangeLog(String version, boolean value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Constants.pref.HAS_SEEN_CHANGE_LOG + version, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean hasSeenHelp() {
|
||||
return mSharedPreferences.getBoolean(Constants.pref.HAS_SEEN_HELP, false);
|
||||
}
|
||||
|
||||
public void setHasSeenHelp(boolean value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Constants.pref.HAS_SEEN_HELP, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public String[] getKeyServers() {
|
||||
String rawData = mSharedPreferences.getString(Constants.pref.KEY_SERVERS,
|
||||
Constants.defaults.KEY_SERVERS);
|
||||
Vector<String> servers = new Vector<String>();
|
||||
String chunks[] = rawData.split(",");
|
||||
for (int i = 0; i < chunks.length; ++i) {
|
||||
String tmp = chunks[i].trim();
|
||||
if (tmp.length() > 0) {
|
||||
servers.add(tmp);
|
||||
}
|
||||
}
|
||||
return servers.toArray(chunks);
|
||||
}
|
||||
|
||||
public void setKeyServers(String[] value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
String rawData = "";
|
||||
for (int i = 0; i < value.length; ++i) {
|
||||
String tmp = value[i].trim();
|
||||
if (tmp.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!"".equals(rawData)) {
|
||||
rawData += ",";
|
||||
}
|
||||
rawData += tmp;
|
||||
}
|
||||
editor.putString(Constants.pref.KEY_SERVERS, rawData);
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
185
org_apg/src/org/thialfihar/android/apg/Primes.java
Normal file
185
org_apg/src/org/thialfihar/android/apg/Primes.java
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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.math.BigInteger;
|
||||
|
||||
public final class Primes {
|
||||
// taken from http://www.ietf.org/rfc/rfc3526.txt
|
||||
public static final String P1536 =
|
||||
"FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
|
||||
"29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
|
||||
"EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
|
||||
"E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
|
||||
"EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
|
||||
"C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
|
||||
"83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
|
||||
"670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF";
|
||||
|
||||
public static final String P2048 =
|
||||
"FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
|
||||
"29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
|
||||
"EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
|
||||
"E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
|
||||
"EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
|
||||
"C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
|
||||
"83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
|
||||
"670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
|
||||
"E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
|
||||
"DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
|
||||
"15728E5A 8AACAA68 FFFFFFFF FFFFFFFF";
|
||||
|
||||
public static final String P3072 =
|
||||
"FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
|
||||
"29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
|
||||
"EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
|
||||
"E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
|
||||
"EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
|
||||
"C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
|
||||
"83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
|
||||
"670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
|
||||
"E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
|
||||
"DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
|
||||
"15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" +
|
||||
"ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" +
|
||||
"ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" +
|
||||
"F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
|
||||
"BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" +
|
||||
"43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF";
|
||||
|
||||
public static final String P4096 =
|
||||
"FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
|
||||
"29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
|
||||
"EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
|
||||
"E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
|
||||
"EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
|
||||
"C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
|
||||
"83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
|
||||
"670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
|
||||
"E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
|
||||
"DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
|
||||
"15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" +
|
||||
"ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" +
|
||||
"ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" +
|
||||
"F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
|
||||
"BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" +
|
||||
"43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" +
|
||||
"88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" +
|
||||
"2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" +
|
||||
"287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" +
|
||||
"1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" +
|
||||
"93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199" +
|
||||
"FFFFFFFF FFFFFFFF";
|
||||
|
||||
public static final String P6144 =
|
||||
"FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
|
||||
"29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
|
||||
"EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
|
||||
"E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
|
||||
"EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
|
||||
"C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
|
||||
"83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
|
||||
"670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
|
||||
"E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
|
||||
"DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
|
||||
"15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" +
|
||||
"ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" +
|
||||
"ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" +
|
||||
"F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
|
||||
"BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" +
|
||||
"43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" +
|
||||
"88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" +
|
||||
"2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" +
|
||||
"287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" +
|
||||
"1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" +
|
||||
"93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" +
|
||||
"36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" +
|
||||
"F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" +
|
||||
"179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" +
|
||||
"DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" +
|
||||
"5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" +
|
||||
"D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" +
|
||||
"23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" +
|
||||
"CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" +
|
||||
"06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" +
|
||||
"DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" +
|
||||
"12BF2D5B 0B7474D6 E694F91E 6DCC4024 FFFFFFFF FFFFFFFF";
|
||||
|
||||
public static final String P8192 =
|
||||
"FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" +
|
||||
"29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" +
|
||||
"EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" +
|
||||
"E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" +
|
||||
"EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" +
|
||||
"C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" +
|
||||
"83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
|
||||
"670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" +
|
||||
"E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" +
|
||||
"DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" +
|
||||
"15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" +
|
||||
"ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" +
|
||||
"ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" +
|
||||
"F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
|
||||
"BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" +
|
||||
"43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" +
|
||||
"88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" +
|
||||
"2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" +
|
||||
"287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" +
|
||||
"1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" +
|
||||
"93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" +
|
||||
"36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" +
|
||||
"F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" +
|
||||
"179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" +
|
||||
"DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" +
|
||||
"5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" +
|
||||
"D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" +
|
||||
"23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" +
|
||||
"CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" +
|
||||
"06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" +
|
||||
"DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" +
|
||||
"12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4" +
|
||||
"38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300" +
|
||||
"741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568" +
|
||||
"3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9" +
|
||||
"22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B" +
|
||||
"4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A" +
|
||||
"062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36" +
|
||||
"4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1" +
|
||||
"B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92" +
|
||||
"4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47" +
|
||||
"9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71" +
|
||||
"60C980DD 98EDD3DF FFFFFFFF FFFFFFFF";
|
||||
|
||||
public static BigInteger getBestPrime(int keySize) {
|
||||
String primeString;
|
||||
if (keySize >= (8192 + 6144) / 2) {
|
||||
primeString = P8192;
|
||||
} else if (keySize >= (6144 + 4096) / 2) {
|
||||
primeString = P6144;
|
||||
} else if (keySize >= (4096 + 3072) / 2) {
|
||||
primeString = P4096;
|
||||
} else if (keySize >= (3072 + 2048) / 2) {
|
||||
primeString = P3072;
|
||||
} else if (keySize >= (2048 + 1536) / 2) {
|
||||
primeString = P2048;
|
||||
} else {
|
||||
primeString = P1536;
|
||||
}
|
||||
|
||||
return new BigInteger(primeString.replaceAll(" ", ""), 16);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public interface ProgressDialogUpdater {
|
||||
void setProgress(String message, int current, int total);
|
||||
|
||||
void setProgress(int resourceId, int current, int total);
|
||||
|
||||
void setProgress(int current, int total);
|
||||
}
|
||||
92
org_apg/src/org/thialfihar/android/apg/Service.java
Normal file
92
org_apg/src/org/thialfihar/android/apg/Service.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class Service extends android.app.Service {
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
|
||||
public static final String EXTRA_TTL = "ttl";
|
||||
|
||||
private int mPassPhraseCacheTtl = 15;
|
||||
private Handler mCacheHandler = new Handler();
|
||||
private Runnable mCacheTask = new Runnable() {
|
||||
public void run() {
|
||||
// check every ttl/2 seconds, which shouldn't be heavy on the device (even if ttl = 15),
|
||||
// and makes sure the longest a pass phrase survives in the cache is 1.5 * ttl
|
||||
int delay = mPassPhraseCacheTtl * 1000 / 2;
|
||||
// also make sure the delay is not longer than one minute
|
||||
if (delay > 60000) {
|
||||
delay = 60000;
|
||||
}
|
||||
|
||||
delay = Apg.cleanUpCache(mPassPhraseCacheTtl, delay);
|
||||
// don't check too often, even if we were close
|
||||
if (delay < 5000) {
|
||||
delay = 5000;
|
||||
}
|
||||
|
||||
mCacheHandler.postDelayed(this, delay);
|
||||
}
|
||||
};
|
||||
|
||||
static private boolean mIsRunning = false;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
mIsRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mIsRunning = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(Intent intent, int startId) {
|
||||
super.onStart(intent, startId);
|
||||
|
||||
if (intent != null) {
|
||||
mPassPhraseCacheTtl = intent.getIntExtra(EXTRA_TTL, 15);
|
||||
}
|
||||
if (mPassPhraseCacheTtl < 15) {
|
||||
mPassPhraseCacheTtl = 15;
|
||||
}
|
||||
mCacheHandler.removeCallbacks(mCacheTask);
|
||||
mCacheHandler.postDelayed(mCacheTask, 1000);
|
||||
}
|
||||
|
||||
static public boolean isRunning() {
|
||||
return mIsRunning;
|
||||
}
|
||||
|
||||
public class LocalBinder extends Binder {
|
||||
Service getService() {
|
||||
return Service.this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import org.thialfihar.android.apg.ApgService;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
public class ApgServiceBlobDatabase extends SQLiteOpenHelper {
|
||||
|
||||
private static final String TAG = "ApgServiceBlobDatabase";
|
||||
|
||||
private static final int VERSION = 1;
|
||||
private static final String NAME = "apg_service_blob_data";
|
||||
private static final String TABLE = "data";
|
||||
|
||||
public ApgServiceBlobDatabase(Context context) {
|
||||
super(context, NAME, null, VERSION);
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "constructor called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called");
|
||||
db.execSQL("create table " + TABLE + " ( _id integer primary key autoincrement," +
|
||||
"key text not null)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "onUpgrade() called");
|
||||
// no upgrade necessary yet
|
||||
}
|
||||
|
||||
public Uri insert(ContentValues vals) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called");
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
long newId = db.insert(TABLE, null, vals);
|
||||
return ContentUris.withAppendedId(ApgServiceBlobProvider.CONTENT_URI, newId);
|
||||
}
|
||||
|
||||
public Cursor query(String id, String key) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called");
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
return db.query(TABLE, new String[] {"_id"},
|
||||
"_id = ? and key = ?", new String[] {id, key},
|
||||
null, null, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import org.thialfihar.android.apg.ApgService;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ApgServiceBlobProvider extends ContentProvider {
|
||||
|
||||
private static final String TAG = "ApgServiceBlobProvider";
|
||||
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://org.thialfihar.android.apg.provider.apgserviceblobprovider");
|
||||
|
||||
private static final String COLUMN_KEY = "key";
|
||||
|
||||
private static final String STORE_PATH = Constants.path.APP_DIR+"/ApgServiceBlobs";
|
||||
|
||||
private ApgServiceBlobDatabase mDb = null;
|
||||
|
||||
public ApgServiceBlobProvider() {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor called");
|
||||
File dir = new File(STORE_PATH);
|
||||
dir.mkdirs();
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor finished");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri arg0, String arg1, String[] arg2) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "delete() called");
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri arg0) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "getType() called");
|
||||
// not needed for now
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues ignored) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called");
|
||||
// ContentValues are actually ignored, because we want to store a blob with no more information
|
||||
// but have to create an record with the password generated here first
|
||||
|
||||
ContentValues vals = new ContentValues();
|
||||
|
||||
// Insert a random key in the database. This has to provided by the caller when updating or
|
||||
// getting the blob
|
||||
String password = UUID.randomUUID().toString();
|
||||
vals.put(COLUMN_KEY, password);
|
||||
|
||||
Uri insertedUri = mDb.insert(vals);
|
||||
return Uri.withAppendedPath(insertedUri, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called");
|
||||
mDb = new ApgServiceBlobDatabase(getContext());
|
||||
// TODO Auto-generated method stub
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called");
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "update() called");
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException, FileNotFoundException {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "openFile() called");
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with uri: "+uri.toString());
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with mode: "+mode);
|
||||
|
||||
List<String> segments = uri.getPathSegments();
|
||||
if(segments.size() < 2) {
|
||||
throw new SecurityException("Password not found in URI");
|
||||
}
|
||||
String id = segments.get(0);
|
||||
String key = segments.get(1);
|
||||
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... got id: "+id);
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... and key: "+key);
|
||||
|
||||
// get the data
|
||||
Cursor result = mDb.query(id, key);
|
||||
|
||||
if(result.getCount() == 0) {
|
||||
// either the key is wrong or no id exists
|
||||
throw new FileNotFoundException("No file found with that ID and/or password");
|
||||
}
|
||||
|
||||
File targetFile = new File(STORE_PATH, id);
|
||||
if(mode.equals("w")) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file w");
|
||||
if( !targetFile.exists() ) {
|
||||
try {
|
||||
targetFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "... got IEOException on creating new file", e);
|
||||
throw new FileNotFoundException("Could not create file to write to");
|
||||
}
|
||||
}
|
||||
return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE );
|
||||
} else if(mode.equals("r")) {
|
||||
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file r");
|
||||
if( !targetFile.exists() ) {
|
||||
throw new FileNotFoundException("Error: Could not find the file requested");
|
||||
}
|
||||
return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.thialfihar.android.apg.Id;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
public class DataProvider extends ContentProvider {
|
||||
public static final String AUTHORITY = "org.thialfihar.android.apg.provider";
|
||||
|
||||
private static final int PUBLIC_KEY_RING = 101;
|
||||
private static final int PUBLIC_KEY_RING_ID = 102;
|
||||
private static final int PUBLIC_KEY_RING_BY_KEY_ID = 103;
|
||||
private static final int PUBLIC_KEY_RING_BY_EMAILS = 104;
|
||||
private static final int PUBLIC_KEY_RING_KEY = 111;
|
||||
private static final int PUBLIC_KEY_RING_KEY_RANK = 112;
|
||||
private static final int PUBLIC_KEY_RING_USER_ID = 121;
|
||||
private static final int PUBLIC_KEY_RING_USER_ID_RANK = 122;
|
||||
|
||||
private static final int SECRET_KEY_RING = 201;
|
||||
private static final int SECRET_KEY_RING_ID = 202;
|
||||
private static final int SECRET_KEY_RING_BY_KEY_ID = 203;
|
||||
private static final int SECRET_KEY_RING_BY_EMAILS = 204;
|
||||
private static final int SECRET_KEY_RING_KEY = 211;
|
||||
private static final int SECRET_KEY_RING_KEY_RANK = 212;
|
||||
private static final int SECRET_KEY_RING_USER_ID = 221;
|
||||
private static final int SECRET_KEY_RING_USER_ID_RANK = 222;
|
||||
|
||||
private static final int DATA_STREAM = 301;
|
||||
|
||||
private static final String PUBLIC_KEY_RING_CONTENT_DIR_TYPE =
|
||||
"vnd.android.cursor.dir/vnd.thialfihar.apg.public.key_ring";
|
||||
private static final String PUBLIC_KEY_RING_CONTENT_ITEM_TYPE =
|
||||
"vnd.android.cursor.item/vnd.thialfihar.apg.public.key_ring";
|
||||
|
||||
private static final String PUBLIC_KEY_CONTENT_DIR_TYPE =
|
||||
"vnd.android.cursor.dir/vnd.thialfihar.apg.public.key";
|
||||
private static final String PUBLIC_KEY_CONTENT_ITEM_TYPE =
|
||||
"vnd.android.cursor.item/vnd.thialfihar.apg.public.key";
|
||||
|
||||
private static final String SECRET_KEY_RING_CONTENT_DIR_TYPE =
|
||||
"vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key_ring";
|
||||
private static final String SECRET_KEY_RING_CONTENT_ITEM_TYPE =
|
||||
"vnd.android.cursor.item/vnd.thialfihar.apg.secret.key_ring";
|
||||
|
||||
private static final String SECRET_KEY_CONTENT_DIR_TYPE =
|
||||
"vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key";
|
||||
private static final String SECRET_KEY_CONTENT_ITEM_TYPE =
|
||||
"vnd.android.cursor.item/vnd.thialfihar.apg.secret.key";
|
||||
|
||||
private static final String USER_ID_CONTENT_DIR_TYPE =
|
||||
"vnd.android.cursor.dir/vnd.thialfihar.apg.user_id";
|
||||
private static final String USER_ID_CONTENT_ITEM_TYPE =
|
||||
"vnd.android.cursor.item/vnd.thialfihar.apg.user_id";
|
||||
|
||||
public static final String _ID = "_id";
|
||||
public static final String MASTER_KEY_ID = "master_key_id";
|
||||
public static final String KEY_ID = "key_id";
|
||||
public static final String USER_ID = "user_id";
|
||||
|
||||
private static final UriMatcher mUriMatcher;
|
||||
|
||||
private Database mDb;
|
||||
|
||||
static {
|
||||
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/public/key_id/*", PUBLIC_KEY_RING_BY_KEY_ID);
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/public/emails/*", PUBLIC_KEY_RING_BY_EMAILS);
|
||||
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys", PUBLIC_KEY_RING_KEY);
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys/#", PUBLIC_KEY_RING_KEY_RANK);
|
||||
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids", PUBLIC_KEY_RING_USER_ID);
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids/#", PUBLIC_KEY_RING_USER_ID_RANK);
|
||||
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/public", PUBLIC_KEY_RING);
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/public/*", PUBLIC_KEY_RING_ID);
|
||||
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/key_id/*", SECRET_KEY_RING_BY_KEY_ID);
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/emails/*", SECRET_KEY_RING_BY_EMAILS);
|
||||
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys", SECRET_KEY_RING_KEY);
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys/#", SECRET_KEY_RING_KEY_RANK);
|
||||
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids", SECRET_KEY_RING_USER_ID);
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids/#", SECRET_KEY_RING_USER_ID_RANK);
|
||||
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/secret", SECRET_KEY_RING);
|
||||
mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*", SECRET_KEY_RING_ID);
|
||||
|
||||
mUriMatcher.addURI(AUTHORITY, "data/*", DATA_STREAM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
mDb = new Database(getContext());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
// TODO: implement the others, then use them for the lists
|
||||
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||
|
||||
int match = mUriMatcher.match(uri);
|
||||
int type;
|
||||
switch (match) {
|
||||
case PUBLIC_KEY_RING:
|
||||
case PUBLIC_KEY_RING_ID:
|
||||
case PUBLIC_KEY_RING_BY_KEY_ID:
|
||||
case PUBLIC_KEY_RING_BY_EMAILS:
|
||||
case PUBLIC_KEY_RING_KEY:
|
||||
case PUBLIC_KEY_RING_KEY_RANK:
|
||||
case PUBLIC_KEY_RING_USER_ID:
|
||||
case PUBLIC_KEY_RING_USER_ID_RANK:
|
||||
type = Id.database.type_public;
|
||||
break;
|
||||
|
||||
case SECRET_KEY_RING:
|
||||
case SECRET_KEY_RING_ID:
|
||||
case SECRET_KEY_RING_BY_KEY_ID:
|
||||
case SECRET_KEY_RING_BY_EMAILS:
|
||||
case SECRET_KEY_RING_KEY:
|
||||
case SECRET_KEY_RING_KEY_RANK:
|
||||
case SECRET_KEY_RING_USER_ID:
|
||||
case SECRET_KEY_RING_USER_ID_RANK:
|
||||
type = Id.database.type_secret;
|
||||
break;
|
||||
|
||||
default: {
|
||||
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
qb.appendWhere(KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = " + type);
|
||||
|
||||
switch (match) {
|
||||
case PUBLIC_KEY_RING_ID:
|
||||
case SECRET_KEY_RING_ID: {
|
||||
qb.appendWhere(" AND " +
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
|
||||
|
||||
// break omitted intentionally
|
||||
}
|
||||
|
||||
case PUBLIC_KEY_RING:
|
||||
case SECRET_KEY_RING: {
|
||||
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') ");
|
||||
|
||||
projectionMap.put(_ID,
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID);
|
||||
projectionMap.put(MASTER_KEY_ID,
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);
|
||||
projectionMap.put(USER_ID,
|
||||
UserIds.TABLE_NAME + "." + UserIds.USER_ID);
|
||||
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
sortOrder = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SECRET_KEY_RING_BY_KEY_ID:
|
||||
case PUBLIC_KEY_RING_BY_KEY_ID: {
|
||||
qb.setTables(Keys.TABLE_NAME + " AS tmp INNER JOIN " +
|
||||
KeyRings.TABLE_NAME + " ON (" +
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
|
||||
"tmp." + Keys.KEY_RING_ID + ")" +
|
||||
" 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') ");
|
||||
|
||||
projectionMap.put(_ID,
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID);
|
||||
projectionMap.put(MASTER_KEY_ID,
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);
|
||||
projectionMap.put(USER_ID,
|
||||
UserIds.TABLE_NAME + "." + UserIds.USER_ID);
|
||||
|
||||
qb.appendWhere(" AND tmp." + Keys.KEY_ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getPathSegments().get(3));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SECRET_KEY_RING_BY_EMAILS:
|
||||
case PUBLIC_KEY_RING_BY_EMAILS: {
|
||||
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') ");
|
||||
|
||||
projectionMap.put(_ID,
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID);
|
||||
projectionMap.put(MASTER_KEY_ID,
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);
|
||||
projectionMap.put(USER_ID,
|
||||
UserIds.TABLE_NAME + "." + UserIds.USER_ID);
|
||||
|
||||
String emails = uri.getPathSegments().get(3);
|
||||
String chunks[] = emails.split(" *, *");
|
||||
boolean gotCondition = false;
|
||||
String emailWhere = "";
|
||||
for (int i = 0; i < chunks.length; ++i) {
|
||||
if (chunks[i].length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (i != 0) {
|
||||
emailWhere += " OR ";
|
||||
}
|
||||
emailWhere += "tmp." + UserIds.USER_ID + " LIKE ";
|
||||
// match '*<email>', so it has to be at the *end* of the user id
|
||||
emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
|
||||
gotCondition = true;
|
||||
}
|
||||
|
||||
if (gotCondition) {
|
||||
qb.appendWhere(" AND EXISTS (SELECT tmp." + UserIds._ID +
|
||||
" FROM " + UserIds.TABLE_NAME +
|
||||
" AS tmp WHERE tmp." + UserIds.KEY_ID + " = " +
|
||||
Keys.TABLE_NAME + "." + Keys._ID +
|
||||
" AND (" + emailWhere + "))");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
qb.setProjectionMap(projectionMap);
|
||||
|
||||
// If no sort order is specified use the default
|
||||
String orderBy;
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
orderBy = null;
|
||||
} else {
|
||||
orderBy = sortOrder;
|
||||
}
|
||||
|
||||
//System.out.println(qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null).replace("WHERE", "WHERE\n"));
|
||||
Cursor c = qb.query(mDb.db(), projection, selection, selectionArgs, null, null, orderBy);
|
||||
|
||||
// Tell the cursor what uri to watch, so it knows when its source data changes
|
||||
c.setNotificationUri(getContext().getContentResolver(), uri);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
switch (mUriMatcher.match(uri)) {
|
||||
case PUBLIC_KEY_RING:
|
||||
case PUBLIC_KEY_RING_BY_EMAILS:
|
||||
return PUBLIC_KEY_RING_CONTENT_DIR_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_ID:
|
||||
return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_BY_KEY_ID:
|
||||
return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_KEY:
|
||||
return PUBLIC_KEY_CONTENT_DIR_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_KEY_RANK:
|
||||
return PUBLIC_KEY_CONTENT_ITEM_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_USER_ID:
|
||||
return USER_ID_CONTENT_DIR_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_USER_ID_RANK:
|
||||
return USER_ID_CONTENT_ITEM_TYPE;
|
||||
|
||||
case SECRET_KEY_RING:
|
||||
case SECRET_KEY_RING_BY_EMAILS:
|
||||
return SECRET_KEY_RING_CONTENT_DIR_TYPE;
|
||||
|
||||
case SECRET_KEY_RING_ID:
|
||||
return SECRET_KEY_RING_CONTENT_ITEM_TYPE;
|
||||
|
||||
case SECRET_KEY_RING_BY_KEY_ID:
|
||||
return SECRET_KEY_RING_CONTENT_ITEM_TYPE;
|
||||
|
||||
case SECRET_KEY_RING_KEY:
|
||||
return SECRET_KEY_CONTENT_DIR_TYPE;
|
||||
|
||||
case SECRET_KEY_RING_KEY_RANK:
|
||||
return SECRET_KEY_CONTENT_ITEM_TYPE;
|
||||
|
||||
case SECRET_KEY_RING_USER_ID:
|
||||
return USER_ID_CONTENT_DIR_TYPE;
|
||||
|
||||
case SECRET_KEY_RING_USER_ID_RANK:
|
||||
return USER_ID_CONTENT_ITEM_TYPE;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues initialValues) {
|
||||
// not supported
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String where, String[] whereArgs) {
|
||||
// not supported
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
|
||||
// not supported
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
int match = mUriMatcher.match(uri);
|
||||
if (match != DATA_STREAM) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
String fileName = uri.getPathSegments().get(1);
|
||||
File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName);
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
}
|
||||
616
org_apg/src/org/thialfihar/android/apg/provider/Database.java
Normal file
616
org_apg/src/org/thialfihar/android/apg/provider/Database.java
Normal file
@@ -0,0 +1,616 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.util.IterableIterator;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
public class Database extends SQLiteOpenHelper {
|
||||
public static class GeneralException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773343L;
|
||||
|
||||
public GeneralException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String DATABASE_NAME = "apg";
|
||||
private static final int DATABASE_VERSION = 2;
|
||||
|
||||
public static final String AUTHORITY = "org.thialfihar.android.apg.database";
|
||||
|
||||
public static HashMap<String, String> sKeyRingsProjection;
|
||||
public static HashMap<String, String> sKeysProjection;
|
||||
public static HashMap<String, String> sUserIdsProjection;
|
||||
|
||||
private SQLiteDatabase mDb = null;
|
||||
private int mStatus = 0;
|
||||
|
||||
static {
|
||||
sKeyRingsProjection = new HashMap<String, String>();
|
||||
sKeyRingsProjection.put(KeyRings._ID, KeyRings._ID);
|
||||
sKeyRingsProjection.put(KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID);
|
||||
sKeyRingsProjection.put(KeyRings.TYPE, KeyRings.TYPE);
|
||||
sKeyRingsProjection.put(KeyRings.WHO_ID, KeyRings.WHO_ID);
|
||||
sKeyRingsProjection.put(KeyRings.KEY_RING_DATA, KeyRings.KEY_RING_DATA);
|
||||
|
||||
sKeysProjection = new HashMap<String, String>();
|
||||
sKeysProjection.put(Keys._ID, Keys._ID);
|
||||
sKeysProjection.put(Keys.KEY_ID, Keys.KEY_ID);
|
||||
sKeysProjection.put(Keys.TYPE, Keys.TYPE);
|
||||
sKeysProjection.put(Keys.IS_MASTER_KEY, Keys.IS_MASTER_KEY);
|
||||
sKeysProjection.put(Keys.ALGORITHM, Keys.ALGORITHM);
|
||||
sKeysProjection.put(Keys.KEY_SIZE, Keys.KEY_SIZE);
|
||||
sKeysProjection.put(Keys.CAN_SIGN, Keys.CAN_SIGN);
|
||||
sKeysProjection.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT);
|
||||
sKeysProjection.put(Keys.IS_REVOKED, Keys.IS_REVOKED);
|
||||
sKeysProjection.put(Keys.CREATION, Keys.CREATION);
|
||||
sKeysProjection.put(Keys.EXPIRY, Keys.EXPIRY);
|
||||
sKeysProjection.put(Keys.KEY_DATA, Keys.KEY_DATA);
|
||||
sKeysProjection.put(Keys.RANK, Keys.RANK);
|
||||
|
||||
sUserIdsProjection = new HashMap<String, String>();
|
||||
sUserIdsProjection.put(UserIds._ID, UserIds._ID);
|
||||
sUserIdsProjection.put(UserIds.KEY_ID, UserIds.KEY_ID);
|
||||
sUserIdsProjection.put(UserIds.USER_ID, UserIds.USER_ID);
|
||||
sUserIdsProjection.put(UserIds.RANK, UserIds.RANK);
|
||||
}
|
||||
|
||||
public Database(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
// force upgrade to test things
|
||||
//onUpgrade(getWritableDatabase(), 1, 2);
|
||||
mDb = getWritableDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
mDb.close();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" +
|
||||
KeyRings._ID + " " + KeyRings._ID_type + "," +
|
||||
KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " +
|
||||
KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " +
|
||||
KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " +
|
||||
KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");");
|
||||
|
||||
db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" +
|
||||
Keys._ID + " " + Keys._ID_type + "," +
|
||||
Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " +
|
||||
Keys.TYPE + " " + Keys.TYPE_type + ", " +
|
||||
Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " +
|
||||
Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " +
|
||||
Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " +
|
||||
Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " +
|
||||
Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " +
|
||||
Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " +
|
||||
Keys.CREATION + " " + Keys.CREATION_type + ", " +
|
||||
Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " +
|
||||
Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " +
|
||||
Keys.KEY_DATA + " " + Keys.KEY_DATA_type +
|
||||
Keys.RANK + " " + Keys.RANK_type + ");");
|
||||
|
||||
db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" +
|
||||
UserIds._ID + " " + UserIds._ID_type + "," +
|
||||
UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," +
|
||||
UserIds.USER_ID + " " + UserIds.USER_ID_type + "," +
|
||||
UserIds.RANK + " " + UserIds.RANK_type + ");");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
mDb = db;
|
||||
for (int version = oldVersion; version < newVersion; ++version) {
|
||||
switch (version) {
|
||||
case 1: { // upgrade 1 to 2
|
||||
db.execSQL("DROP TABLE IF EXISTS " + KeyRings.TABLE_NAME + ";");
|
||||
db.execSQL("DROP TABLE IF EXISTS " + Keys.TABLE_NAME + ";");
|
||||
db.execSQL("DROP TABLE IF EXISTS " + UserIds.TABLE_NAME + ";");
|
||||
|
||||
db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" +
|
||||
KeyRings._ID + " " + KeyRings._ID_type + "," +
|
||||
KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " +
|
||||
KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " +
|
||||
KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " +
|
||||
KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");");
|
||||
|
||||
db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" +
|
||||
Keys._ID + " " + Keys._ID_type + "," +
|
||||
Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " +
|
||||
Keys.TYPE + " " + Keys.TYPE_type + ", " +
|
||||
Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " +
|
||||
Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " +
|
||||
Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " +
|
||||
Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " +
|
||||
Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " +
|
||||
Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " +
|
||||
Keys.CREATION + " " + Keys.CREATION_type + ", " +
|
||||
Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " +
|
||||
Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " +
|
||||
Keys.KEY_DATA + " " + Keys.KEY_DATA_type +
|
||||
Keys.RANK + " " + Keys.RANK_type + ");");
|
||||
|
||||
db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" +
|
||||
UserIds._ID + " " + UserIds._ID_type + "," +
|
||||
UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," +
|
||||
UserIds.USER_ID + " " + UserIds.USER_ID_type + "," +
|
||||
UserIds.RANK + " " + UserIds.RANK_type + ");");
|
||||
|
||||
Cursor cursor = db.query("public_keys", new String[] { "c_key_data" },
|
||||
null, null, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
do {
|
||||
byte[] data = cursor.getBlob(0);
|
||||
try {
|
||||
PGPPublicKeyRing keyRing = new PGPPublicKeyRing(data);
|
||||
saveKeyRing(keyRing);
|
||||
} catch (IOException e) {
|
||||
Log.e("apg.db.upgrade", "key import failed: " + e);
|
||||
} catch (GeneralException e) {
|
||||
Log.e("apg.db.upgrade", "key import failed: " + e);
|
||||
}
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
cursor = db.query("secret_keys", new String[]{ "c_key_data" },
|
||||
null, null, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
do {
|
||||
byte[] data = cursor.getBlob(0);
|
||||
try {
|
||||
PGPSecretKeyRing keyRing = new PGPSecretKeyRing(data);
|
||||
saveKeyRing(keyRing);
|
||||
} catch (IOException e) {
|
||||
Log.e("apg.db.upgrade", "key import failed: " + e);
|
||||
} catch (PGPException e) {
|
||||
Log.e("apg.db.upgrade", "key import failed: " + e);
|
||||
} catch (GeneralException e) {
|
||||
Log.e("apg.db.upgrade", "key import failed: " + e);
|
||||
}
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
db.execSQL("DROP TABLE IF EXISTS public_keys;");
|
||||
db.execSQL("DROP TABLE IF EXISTS secret_keys;");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mDb = null;
|
||||
}
|
||||
|
||||
public int saveKeyRing(PGPPublicKeyRing keyRing) throws IOException, GeneralException {
|
||||
mDb.beginTransaction();
|
||||
ContentValues values = new ContentValues();
|
||||
PGPPublicKey masterKey = keyRing.getPublicKey();
|
||||
long masterKeyId = masterKey.getKeyID();
|
||||
|
||||
values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
|
||||
values.put(KeyRings.TYPE, Id.database.type_public);
|
||||
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
|
||||
|
||||
long rowId = insertOrUpdateKeyRing(values);
|
||||
int returnValue = mStatus;
|
||||
|
||||
if (rowId == -1) {
|
||||
throw new GeneralException("saving public key ring " + masterKeyId + " failed");
|
||||
}
|
||||
|
||||
Vector<Integer> seenIds = new Vector<Integer>();
|
||||
int rank = 0;
|
||||
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
|
||||
seenIds.add(saveKey(rowId, key, rank));
|
||||
++rank;
|
||||
}
|
||||
|
||||
String seenIdsStr = "";
|
||||
for (Integer id : seenIds) {
|
||||
if (seenIdsStr.length() > 0) {
|
||||
seenIdsStr += ",";
|
||||
}
|
||||
seenIdsStr += id;
|
||||
}
|
||||
mDb.delete(Keys.TABLE_NAME,
|
||||
Keys.KEY_RING_ID + " = ? AND " +
|
||||
Keys._ID + " NOT IN (" + seenIdsStr + ")",
|
||||
new String[] { "" + rowId });
|
||||
|
||||
mDb.setTransactionSuccessful();
|
||||
mDb.endTransaction();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public int saveKeyRing(PGPSecretKeyRing keyRing) throws IOException, GeneralException {
|
||||
mDb.beginTransaction();
|
||||
ContentValues values = new ContentValues();
|
||||
PGPSecretKey masterKey = keyRing.getSecretKey();
|
||||
long masterKeyId = masterKey.getKeyID();
|
||||
|
||||
values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
|
||||
values.put(KeyRings.TYPE, Id.database.type_secret);
|
||||
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
|
||||
|
||||
long rowId = insertOrUpdateKeyRing(values);
|
||||
int returnValue = mStatus;
|
||||
|
||||
if (rowId == -1) {
|
||||
throw new GeneralException("saving secret key ring " + masterKeyId + " failed");
|
||||
}
|
||||
|
||||
Vector<Integer> seenIds = new Vector<Integer>();
|
||||
int rank = 0;
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||
seenIds.add(saveKey(rowId, key, rank));
|
||||
++rank;
|
||||
}
|
||||
|
||||
String seenIdsStr = "";
|
||||
for (Integer id : seenIds) {
|
||||
if (seenIdsStr.length() > 0) {
|
||||
seenIdsStr += ",";
|
||||
}
|
||||
seenIdsStr += id;
|
||||
}
|
||||
mDb.delete(Keys.TABLE_NAME,
|
||||
Keys.KEY_RING_ID + " = ? AND " +
|
||||
Keys._ID + " NOT IN (" + seenIdsStr + ")",
|
||||
new String[] { "" + rowId });
|
||||
|
||||
mDb.setTransactionSuccessful();
|
||||
mDb.endTransaction();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private int saveKey(long keyRingId, PGPPublicKey key, int rank)
|
||||
throws IOException, GeneralException {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(Keys.KEY_ID, key.getKeyID());
|
||||
values.put(Keys.TYPE, Id.database.type_public);
|
||||
values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
|
||||
values.put(Keys.ALGORITHM, key.getAlgorithm());
|
||||
values.put(Keys.KEY_SIZE, key.getBitStrength());
|
||||
values.put(Keys.CAN_SIGN, Apg.isSigningKey(key));
|
||||
values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key));
|
||||
values.put(Keys.IS_REVOKED, key.isRevoked());
|
||||
values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000);
|
||||
Date expiryDate = Apg.getExpiryDate(key);
|
||||
if (expiryDate != null) {
|
||||
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
|
||||
}
|
||||
values.put(Keys.KEY_RING_ID, keyRingId);
|
||||
values.put(Keys.KEY_DATA, key.getEncoded());
|
||||
values.put(Keys.RANK, rank);
|
||||
|
||||
long rowId = insertOrUpdateKey(values);
|
||||
|
||||
if (rowId == -1) {
|
||||
throw new GeneralException("saving public key " + key.getKeyID() + " failed");
|
||||
}
|
||||
|
||||
Vector<Integer> seenIds = new Vector<Integer>();
|
||||
int userIdRank = 0;
|
||||
for (String userId : new IterableIterator<String>(key.getUserIDs())) {
|
||||
seenIds.add(saveUserId(rowId, userId, userIdRank));
|
||||
++userIdRank;
|
||||
}
|
||||
|
||||
String seenIdsStr = "";
|
||||
for (Integer id : seenIds) {
|
||||
if (seenIdsStr.length() > 0) {
|
||||
seenIdsStr += ",";
|
||||
}
|
||||
seenIdsStr += id;
|
||||
}
|
||||
mDb.delete(UserIds.TABLE_NAME,
|
||||
UserIds.KEY_ID + " = ? AND " +
|
||||
UserIds._ID + " NOT IN (" + seenIdsStr + ")",
|
||||
new String[] { "" + rowId });
|
||||
|
||||
return (int)rowId;
|
||||
}
|
||||
|
||||
private int saveKey(long keyRingId, PGPSecretKey key, int rank)
|
||||
throws IOException, GeneralException {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(Keys.KEY_ID, key.getPublicKey().getKeyID());
|
||||
values.put(Keys.TYPE, Id.database.type_secret);
|
||||
values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
|
||||
values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm());
|
||||
values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength());
|
||||
values.put(Keys.CAN_SIGN, Apg.isSigningKey(key));
|
||||
values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key));
|
||||
values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked());
|
||||
values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000);
|
||||
Date expiryDate = Apg.getExpiryDate(key);
|
||||
if (expiryDate != null) {
|
||||
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
|
||||
}
|
||||
values.put(Keys.KEY_RING_ID, keyRingId);
|
||||
values.put(Keys.KEY_DATA, key.getEncoded());
|
||||
values.put(Keys.RANK, rank);
|
||||
|
||||
long rowId = insertOrUpdateKey(values);
|
||||
|
||||
if (rowId == -1) {
|
||||
throw new GeneralException("saving secret key " + key.getPublicKey().getKeyID() + " failed");
|
||||
}
|
||||
|
||||
Vector<Integer> seenIds = new Vector<Integer>();
|
||||
int userIdRank = 0;
|
||||
for (String userId : new IterableIterator<String>(key.getUserIDs())) {
|
||||
seenIds.add(saveUserId(rowId, userId, userIdRank));
|
||||
++userIdRank;
|
||||
}
|
||||
|
||||
String seenIdsStr = "";
|
||||
for (Integer id : seenIds) {
|
||||
if (seenIdsStr.length() > 0) {
|
||||
seenIdsStr += ",";
|
||||
}
|
||||
seenIdsStr += id;
|
||||
}
|
||||
mDb.delete(UserIds.TABLE_NAME,
|
||||
UserIds.KEY_ID + " = ? AND " +
|
||||
UserIds._ID + " NOT IN (" + seenIdsStr + ")",
|
||||
new String[] { "" + rowId });
|
||||
|
||||
return (int)rowId;
|
||||
}
|
||||
|
||||
private int saveUserId(long keyId, String userId, int rank) throws GeneralException {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(UserIds.KEY_ID, keyId);
|
||||
values.put(UserIds.USER_ID, userId);
|
||||
values.put(UserIds.RANK, rank);
|
||||
|
||||
long rowId = insertOrUpdateUserId(values);
|
||||
|
||||
if (rowId == -1) {
|
||||
throw new GeneralException("saving user id " + userId + " failed");
|
||||
}
|
||||
|
||||
return (int)rowId;
|
||||
}
|
||||
|
||||
private long insertOrUpdateKeyRing(ContentValues values) {
|
||||
Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID },
|
||||
KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?",
|
||||
new String[] {
|
||||
values.getAsString(KeyRings.MASTER_KEY_ID),
|
||||
values.getAsString(KeyRings.TYPE),
|
||||
},
|
||||
null, null, null);
|
||||
long rowId = -1;
|
||||
if (c != null && c.moveToFirst()) {
|
||||
rowId = c.getLong(0);
|
||||
mDb.update(KeyRings.TABLE_NAME, values,
|
||||
KeyRings._ID + " = ?", new String[] { "" + rowId });
|
||||
mStatus = Id.return_value.updated;
|
||||
} else {
|
||||
rowId = mDb.insert(KeyRings.TABLE_NAME, KeyRings.WHO_ID, values);
|
||||
mStatus = Id.return_value.ok;
|
||||
}
|
||||
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
|
||||
return rowId;
|
||||
}
|
||||
|
||||
private long insertOrUpdateKey(ContentValues values) {
|
||||
Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID },
|
||||
Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?",
|
||||
new String[] {
|
||||
values.getAsString(Keys.KEY_ID),
|
||||
values.getAsString(Keys.TYPE),
|
||||
},
|
||||
null, null, null);
|
||||
long rowId = -1;
|
||||
if (c != null && c.moveToFirst()) {
|
||||
rowId = c.getLong(0);
|
||||
mDb.update(Keys.TABLE_NAME, values,
|
||||
Keys._ID + " = ?", new String[] { "" + rowId });
|
||||
} else {
|
||||
rowId = mDb.insert(Keys.TABLE_NAME, Keys.KEY_DATA, values);
|
||||
}
|
||||
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
|
||||
return rowId;
|
||||
}
|
||||
|
||||
private long insertOrUpdateUserId(ContentValues values) {
|
||||
Cursor c = mDb.query(UserIds.TABLE_NAME, new String[] { UserIds._ID },
|
||||
UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?",
|
||||
new String[] {
|
||||
values.getAsString(UserIds.KEY_ID),
|
||||
values.getAsString(UserIds.USER_ID),
|
||||
},
|
||||
null, null, null);
|
||||
long rowId = -1;
|
||||
if (c != null && c.moveToFirst()) {
|
||||
rowId = c.getLong(0);
|
||||
mDb.update(UserIds.TABLE_NAME, values,
|
||||
UserIds._ID + " = ?", new String[] { "" + rowId });
|
||||
} else {
|
||||
rowId = mDb.insert(UserIds.TABLE_NAME, UserIds.USER_ID, values);
|
||||
}
|
||||
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
|
||||
return rowId;
|
||||
}
|
||||
|
||||
public Object getKeyRing(int keyRingId) {
|
||||
Cursor c = mDb.query(KeyRings.TABLE_NAME,
|
||||
new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE },
|
||||
KeyRings._ID + " = ?",
|
||||
new String[] {
|
||||
"" + keyRingId,
|
||||
},
|
||||
null, null, null);
|
||||
byte[] data = null;
|
||||
Object keyRing = null;
|
||||
if (c != null && c.moveToFirst()) {
|
||||
data = c.getBlob(0);
|
||||
if (data != null) {
|
||||
try {
|
||||
if (c.getInt(1) == Id.database.type_public) {
|
||||
keyRing = new PGPPublicKeyRing(data);
|
||||
} else {
|
||||
keyRing = new PGPSecretKeyRing(data);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// can't load it, then
|
||||
} catch (PGPException e) {
|
||||
// can't load it, then
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
|
||||
return keyRing;
|
||||
}
|
||||
|
||||
public byte[] getKeyRingDataFromKeyId(int type, long keyId) {
|
||||
Cursor c = mDb.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" +
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
|
||||
Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")",
|
||||
new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA },
|
||||
Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " +
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?",
|
||||
new String[] {
|
||||
"" + keyId,
|
||||
"" + type,
|
||||
},
|
||||
null, null, null);
|
||||
|
||||
byte[] data = null;
|
||||
if (c != null && c.moveToFirst()) {
|
||||
data = c.getBlob(0);
|
||||
}
|
||||
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public byte[] getKeyDataFromKeyId(int type, long keyId) {
|
||||
Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys.KEY_DATA },
|
||||
Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?",
|
||||
new String[] {
|
||||
"" + keyId,
|
||||
"" + type,
|
||||
},
|
||||
null, null, null);
|
||||
byte[] data = null;
|
||||
if (c != null && c.moveToFirst()) {
|
||||
data = c.getBlob(0);
|
||||
}
|
||||
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void deleteKeyRing(int keyRingId) {
|
||||
mDb.beginTransaction();
|
||||
mDb.delete(KeyRings.TABLE_NAME,
|
||||
KeyRings._ID + " = ?", new String[] { "" + keyRingId });
|
||||
|
||||
Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID },
|
||||
Keys.KEY_RING_ID + " = ?",
|
||||
new String[] {
|
||||
"" + keyRingId,
|
||||
},
|
||||
null, null, null);
|
||||
if (c != null && c.moveToFirst()) {
|
||||
do {
|
||||
int keyId = c.getInt(0);
|
||||
deleteKey(keyId);
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
|
||||
mDb.setTransactionSuccessful();
|
||||
mDb.endTransaction();
|
||||
}
|
||||
|
||||
private void deleteKey(int keyId) {
|
||||
mDb.delete(Keys.TABLE_NAME,
|
||||
Keys._ID + " = ?", new String[] { "" + keyId });
|
||||
|
||||
mDb.delete(UserIds.TABLE_NAME,
|
||||
UserIds.KEY_ID + " = ?", new String[] { "" + keyId });
|
||||
}
|
||||
|
||||
public SQLiteDatabase db() {
|
||||
return mDb;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
public class KeyRings implements BaseColumns {
|
||||
public static final String TABLE_NAME = "key_rings";
|
||||
|
||||
public static final String _ID_type = "INTEGER PRIMARY KEY";
|
||||
public static final String MASTER_KEY_ID = "c_master_key_id";
|
||||
public static final String MASTER_KEY_ID_type = "INT64";
|
||||
public static final String TYPE = "c_type";
|
||||
public static final String TYPE_type = "INTEGER";
|
||||
public static final String WHO_ID = "c_who_id";
|
||||
public static final String WHO_ID_type = "INTEGER";
|
||||
public static final String KEY_RING_DATA = "c_key_ring_data";
|
||||
public static final String KEY_RING_DATA_type = "BLOB";
|
||||
}
|
||||
51
org_apg/src/org/thialfihar/android/apg/provider/Keys.java
Normal file
51
org_apg/src/org/thialfihar/android/apg/provider/Keys.java
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
public class Keys implements BaseColumns {
|
||||
public static final String TABLE_NAME = "keys";
|
||||
|
||||
public static final String _ID_type = "INTEGER PRIMARY KEY";
|
||||
public static final String KEY_ID = "c_key_id";
|
||||
public static final String KEY_ID_type = "INT64";
|
||||
public static final String TYPE = "c_type";
|
||||
public static final String TYPE_type = "INTEGER";
|
||||
public static final String IS_MASTER_KEY = "c_is_master_key";
|
||||
public static final String IS_MASTER_KEY_type = "INTEGER";
|
||||
public static final String ALGORITHM = "c_algorithm";
|
||||
public static final String ALGORITHM_type = "INTEGER";
|
||||
public static final String KEY_SIZE = "c_key_size";
|
||||
public static final String KEY_SIZE_type = "INTEGER";
|
||||
public static final String CAN_SIGN = "c_can_sign";
|
||||
public static final String CAN_SIGN_type = "INTEGER";
|
||||
public static final String CAN_ENCRYPT = "c_can_encrypt";
|
||||
public static final String CAN_ENCRYPT_type = "INTEGER";
|
||||
public static final String IS_REVOKED = "c_is_revoked";
|
||||
public static final String IS_REVOKED_type = "INTEGER";
|
||||
public static final String CREATION = "c_creation";
|
||||
public static final String CREATION_type = "INTEGER";
|
||||
public static final String EXPIRY = "c_expiry";
|
||||
public static final String EXPIRY_type = "INTEGER";
|
||||
public static final String KEY_RING_ID = "c_key_ring_id";
|
||||
public static final String KEY_RING_ID_type = "INTEGER";
|
||||
public static final String KEY_DATA = "c_key_data";
|
||||
public static final String KEY_DATA_type = "BLOB";
|
||||
public static final String RANK = "c_key_data";
|
||||
public static final String RANK_type = "INTEGER";
|
||||
}
|
||||
31
org_apg/src/org/thialfihar/android/apg/provider/UserIds.java
Normal file
31
org_apg/src/org/thialfihar/android/apg/provider/UserIds.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
public class UserIds implements BaseColumns {
|
||||
public static final String TABLE_NAME = "user_ids";
|
||||
|
||||
public static final String _ID_type = "INTEGER PRIMARY KEY";
|
||||
public static final String KEY_ID = "c_key_id";
|
||||
public static final String KEY_ID_type = "INTEGER";
|
||||
public static final String USER_ID = "c_user_id";
|
||||
public static final String USER_ID_type = "TEXT";
|
||||
public static final String RANK = "c_rank";
|
||||
public static final String RANK_type = "INTEGER";
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Reimplement all the threads in the activitys as intents in this intentService
|
||||
* - This IntentService stopps itself after an action is executed
|
||||
*/
|
||||
|
||||
package org.thialfihar.android.apg.service;
|
||||
|
||||
public class ApgService {
|
||||
|
||||
}
|
||||
65
org_apg/src/org/thialfihar/android/apg/ui/AboutActivity.java
Normal file
65
org_apg/src/org/thialfihar/android/apg/ui/AboutActivity.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class AboutActivity extends Activity {
|
||||
Activity mActivity;
|
||||
|
||||
/**
|
||||
* Instantiate View for this Activity
|
||||
*/
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.about_activity);
|
||||
|
||||
mActivity = this;
|
||||
|
||||
TextView versionText = (TextView) findViewById(R.id.about_version);
|
||||
versionText.setText(getString(R.string.about_version) + " " + getVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current package version.
|
||||
*
|
||||
* @return The current version.
|
||||
*/
|
||||
private String getVersion() {
|
||||
String result = "";
|
||||
try {
|
||||
PackageManager manager = mActivity.getPackageManager();
|
||||
PackageInfo info = manager.getPackageInfo(mActivity.getPackageName(), 0);
|
||||
|
||||
result = String.format("%s (%s)", info.versionName, info.versionCode);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(Constants.TAG, "Unable to get application version: " + e.getMessage());
|
||||
result = "Unable to get application version.";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
408
org_apg/src/org/thialfihar/android/apg/ui/BaseActivity.java
Normal file
408
org_apg/src/org/thialfihar/android/apg/ui/BaseActivity.java
Normal file
@@ -0,0 +1,408 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.AskForSecretKeyPassPhrase;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.PausableThread;
|
||||
import org.thialfihar.android.apg.Preferences;
|
||||
import org.thialfihar.android.apg.ProgressDialogUpdater;
|
||||
import org.thialfihar.android.apg.Service;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockActivity;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class BaseActivity extends SherlockActivity implements Runnable, ProgressDialogUpdater,
|
||||
AskForSecretKeyPassPhrase.PassPhraseCallbackInterface {
|
||||
|
||||
private ProgressDialog mProgressDialog = null;
|
||||
private PausableThread mRunningThread = null;
|
||||
private Thread mDeletingThread = null;
|
||||
|
||||
private long mSecretKeyId = 0;
|
||||
private String mDeleteFile = null;
|
||||
|
||||
protected Preferences mPreferences;
|
||||
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
handlerCallback(msg);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mPreferences = Preferences.getPreferences(this);
|
||||
|
||||
Apg.initialize(this);
|
||||
|
||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
File dir = new File(Constants.path.APP_DIR);
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
// ignore this for now, it's not crucial
|
||||
// that the directory doesn't exist at this point
|
||||
}
|
||||
}
|
||||
|
||||
startCacheService(this, mPreferences);
|
||||
}
|
||||
|
||||
public static void startCacheService(Activity activity, Preferences preferences) {
|
||||
Intent intent = new Intent(activity, Service.class);
|
||||
intent.putExtra(Service.EXTRA_TTL, preferences.getPassPhraseCacheTtl());
|
||||
activity.startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
|
||||
// TODO: needed?:
|
||||
case Id.menu.option.search:
|
||||
startSearch("", false, null, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
// in case it is a progress dialog
|
||||
mProgressDialog = new ProgressDialog(this);
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
mProgressDialog.setCancelable(false);
|
||||
switch (id) {
|
||||
case Id.dialog.encrypting: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_initializing));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.decrypting: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_initializing));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.saving: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_saving));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.importing: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_importing));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.exporting: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_exporting));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.deleting: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_initializing));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.querying: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_querying));
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
mProgressDialog.setCancelable(false);
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.signing: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_signing));
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
mProgressDialog.setCancelable(false);
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mProgressDialog = null;
|
||||
|
||||
switch (id) {
|
||||
|
||||
case Id.dialog.pass_phrase: {
|
||||
return AskForSecretKeyPassPhrase.createDialog(this, getSecretKeyId(), this);
|
||||
}
|
||||
|
||||
case Id.dialog.pass_phrases_do_not_match: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.error);
|
||||
alert.setMessage(R.string.passPhrasesDoNotMatch);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.pass_phrases_do_not_match);
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
case Id.dialog.no_pass_phrase: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.error);
|
||||
alert.setMessage(R.string.passPhraseMustNotBeEmpty);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.no_pass_phrase);
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
case Id.dialog.delete_file: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(this.getString(R.string.fileDeleteConfirmation, getDeleteFile()));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.delete_file);
|
||||
final File file = new File(getDeleteFile());
|
||||
showDialog(Id.dialog.deleting);
|
||||
mDeletingThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
Bundle data = new Bundle();
|
||||
data.putInt(Constants.extras.STATUS, Id.message.delete_done);
|
||||
try {
|
||||
Apg.deleteFileSecurely(BaseActivity.this, file, BaseActivity.this);
|
||||
} catch (FileNotFoundException e) {
|
||||
data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString(
|
||||
R.string.error_fileNotFound, file));
|
||||
} catch (IOException e) {
|
||||
data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString(
|
||||
R.string.error_fileDeleteFailed, file));
|
||||
}
|
||||
Message msg = new Message();
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
});
|
||||
mDeletingThread.start();
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.delete_file);
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.secret_keys: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
Bundle bundle = data.getExtras();
|
||||
setSecretKeyId(bundle.getLong(Apg.EXTRA_KEY_ID));
|
||||
} else {
|
||||
setSecretKeyId(Id.key.none);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
public void setProgress(int resourceId, int progress, int max) {
|
||||
setProgress(getString(resourceId), progress, max);
|
||||
}
|
||||
|
||||
public void setProgress(int progress, int max) {
|
||||
Message msg = new Message();
|
||||
Bundle data = new Bundle();
|
||||
data.putInt(Constants.extras.STATUS, Id.message.progress_update);
|
||||
data.putInt(Constants.extras.PROGRESS, progress);
|
||||
data.putInt(Constants.extras.PROGRESS_MAX, max);
|
||||
msg.setData(data);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
Message msg = new Message();
|
||||
Bundle data = new Bundle();
|
||||
data.putInt(Constants.extras.STATUS, Id.message.progress_update);
|
||||
data.putString(Constants.extras.MESSAGE, message);
|
||||
data.putInt(Constants.extras.PROGRESS, progress);
|
||||
data.putInt(Constants.extras.PROGRESS_MAX, max);
|
||||
msg.setData(data);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
public void handlerCallback(Message msg) {
|
||||
Bundle data = msg.getData();
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int type = data.getInt(Constants.extras.STATUS);
|
||||
switch (type) {
|
||||
case Id.message.progress_update: {
|
||||
String message = data.getString(Constants.extras.MESSAGE);
|
||||
if (mProgressDialog != null) {
|
||||
if (message != null) {
|
||||
mProgressDialog.setMessage(message);
|
||||
}
|
||||
mProgressDialog.setMax(data.getInt(Constants.extras.PROGRESS_MAX));
|
||||
mProgressDialog.setProgress(data.getInt(Constants.extras.PROGRESS));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.message.delete_done: {
|
||||
mProgressDialog = null;
|
||||
deleteDoneCallback(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.message.import_done: // intentionally no break
|
||||
case Id.message.export_done: // intentionally no break
|
||||
case Id.message.query_done: // intentionally no break
|
||||
case Id.message.done: {
|
||||
mProgressDialog = null;
|
||||
doneCallback(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void doneCallback(Message msg) {
|
||||
|
||||
}
|
||||
|
||||
public void deleteDoneCallback(Message msg) {
|
||||
removeDialog(Id.dialog.deleting);
|
||||
mDeletingThread = null;
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
String message;
|
||||
if (error != null) {
|
||||
message = getString(R.string.errorMessage, error);
|
||||
} else {
|
||||
message = getString(R.string.fileDeleteSuccessful);
|
||||
}
|
||||
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public void passPhraseCallback(long keyId, String passPhrase) {
|
||||
Apg.setCachedPassPhrase(keyId, passPhrase);
|
||||
}
|
||||
|
||||
public void sendMessage(Message msg) {
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
public PausableThread getRunningThread() {
|
||||
return mRunningThread;
|
||||
}
|
||||
|
||||
public void startThread() {
|
||||
mRunningThread = new PausableThread(this);
|
||||
mRunningThread.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
||||
}
|
||||
|
||||
public void setSecretKeyId(long id) {
|
||||
mSecretKeyId = id;
|
||||
}
|
||||
|
||||
public long getSecretKeyId() {
|
||||
return mSecretKeyId;
|
||||
}
|
||||
|
||||
protected void setDeleteFile(String deleteFile) {
|
||||
mDeleteFile = deleteFile;
|
||||
}
|
||||
|
||||
protected String getDeleteFile() {
|
||||
return mDeleteFile;
|
||||
}
|
||||
}
|
||||
864
org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java
Normal file
864
org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java
Normal file
@@ -0,0 +1,864 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.DataDestination;
|
||||
import org.thialfihar.android.apg.DataSource;
|
||||
import org.thialfihar.android.apg.FileDialog;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.InputData;
|
||||
import org.thialfihar.android.apg.PausableThread;
|
||||
import org.thialfihar.android.apg.provider.DataProvider;
|
||||
import org.thialfihar.android.apg.util.Compatibility;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Security;
|
||||
import java.security.SignatureException;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class DecryptActivity extends BaseActivity {
|
||||
private long mSignatureKeyId = 0;
|
||||
|
||||
private Intent mIntent;
|
||||
|
||||
private boolean mReturnResult = false;
|
||||
private String mReplyTo = null;
|
||||
private String mSubject = null;
|
||||
private boolean mSignedOnly = false;
|
||||
private boolean mAssumeSymmetricEncryption = false;
|
||||
|
||||
private EditText mMessage = null;
|
||||
private LinearLayout mSignatureLayout = null;
|
||||
private ImageView mSignatureStatusImage = null;
|
||||
private TextView mUserId = null;
|
||||
private TextView mUserIdRest = null;
|
||||
|
||||
private ViewFlipper mSource = null;
|
||||
private TextView mSourceLabel = null;
|
||||
private ImageView mSourcePrevious = null;
|
||||
private ImageView mSourceNext = null;
|
||||
|
||||
// private Button mDecryptButton = null;
|
||||
// private Button mReplyButton = null;
|
||||
|
||||
private boolean mDecryptEnabled = true;
|
||||
private String mDecryptString = "";
|
||||
private boolean mReplyEnabled = true;
|
||||
private String mReplyString = "";
|
||||
|
||||
private int mDecryptTarget;
|
||||
|
||||
private EditText mFilename = null;
|
||||
private CheckBox mDeleteAfter = null;
|
||||
private ImageButton mBrowse = null;
|
||||
|
||||
private String mInputFilename = null;
|
||||
private String mOutputFilename = null;
|
||||
|
||||
private Uri mContentUri = null;
|
||||
private byte[] mData = null;
|
||||
private boolean mReturnBinary = false;
|
||||
|
||||
private DataSource mDataSource = null;
|
||||
private DataDestination mDataDestination = null;
|
||||
|
||||
private long mUnknownSignatureKeyId = 0;
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
|
||||
if (mDecryptEnabled) {
|
||||
menu.add(1, Id.menu.option.decrypt, 0, mDecryptString).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
}
|
||||
if (mReplyEnabled) {
|
||||
menu.add(1, Id.menu.option.reply, 1, mReplyString).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.decrypt: {
|
||||
decryptClicked();
|
||||
|
||||
return true;
|
||||
}
|
||||
case Id.menu.option.reply: {
|
||||
replyClicked();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.decrypt);
|
||||
|
||||
mSource = (ViewFlipper) findViewById(R.id.source);
|
||||
mSourceLabel = (TextView) findViewById(R.id.sourceLabel);
|
||||
mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious);
|
||||
mSourceNext = (ImageView) findViewById(R.id.sourceNext);
|
||||
|
||||
mSourcePrevious.setClickable(true);
|
||||
mSourcePrevious.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_right_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_right_out));
|
||||
mSource.showPrevious();
|
||||
updateSource();
|
||||
}
|
||||
});
|
||||
|
||||
mSourceNext.setClickable(true);
|
||||
OnClickListener nextSourceClickListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_left_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_left_out));
|
||||
mSource.showNext();
|
||||
updateSource();
|
||||
}
|
||||
};
|
||||
mSourceNext.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mSourceLabel.setClickable(true);
|
||||
mSourceLabel.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mMessage = (EditText) findViewById(R.id.message);
|
||||
mSignatureLayout = (LinearLayout) findViewById(R.id.signature);
|
||||
mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status);
|
||||
mUserId = (TextView) findViewById(R.id.mainUserId);
|
||||
mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
|
||||
|
||||
// measure the height of the source_file view and set the message view's min height to that,
|
||||
// so it fills mSource fully... bit of a hack.
|
||||
View tmp = findViewById(R.id.sourceFile);
|
||||
tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||
int height = tmp.getMeasuredHeight();
|
||||
mMessage.setMinimumHeight(height);
|
||||
|
||||
mFilename = (EditText) findViewById(R.id.filename);
|
||||
mBrowse = (ImageButton) findViewById(R.id.btn_browse);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
openFile();
|
||||
}
|
||||
});
|
||||
|
||||
mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption);
|
||||
|
||||
// default: message source
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
|
||||
mSource.showNext();
|
||||
}
|
||||
|
||||
mIntent = getIntent();
|
||||
if (Intent.ACTION_VIEW.equals(mIntent.getAction())) {
|
||||
Uri uri = mIntent.getData();
|
||||
try {
|
||||
InputStream attachment = getContentResolver().openInputStream(uri);
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
byte bytes[] = new byte[1 << 16];
|
||||
int length;
|
||||
while ((length = attachment.read(bytes)) > 0) {
|
||||
byteOut.write(bytes, 0, length);
|
||||
}
|
||||
byteOut.close();
|
||||
String data = new String(byteOut.toByteArray());
|
||||
mMessage.setText(data);
|
||||
} catch (FileNotFoundException e) {
|
||||
// ignore, then
|
||||
} catch (IOException e) {
|
||||
// ignore, then
|
||||
}
|
||||
} else if (Apg.Intent.DECRYPT.equals(mIntent.getAction())) {
|
||||
Log.d(Constants.TAG, "Apg Intent DECRYPT startet");
|
||||
Bundle extras = mIntent.getExtras();
|
||||
if (extras == null) {
|
||||
Log.d(Constants.TAG, "extra bundle was null");
|
||||
extras = new Bundle();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "got extras");
|
||||
}
|
||||
|
||||
mData = extras.getByteArray(Apg.EXTRA_DATA);
|
||||
String textData = null;
|
||||
if (mData == null) {
|
||||
Log.d(Constants.TAG, "EXTRA_DATA was null");
|
||||
textData = extras.getString(Apg.EXTRA_TEXT);
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Got data from EXTRA_DATA");
|
||||
}
|
||||
if (textData != null) {
|
||||
Log.d(Constants.TAG, "textData null, matching text ...");
|
||||
Matcher matcher = Apg.PGP_MESSAGE.matcher(textData);
|
||||
if (matcher.matches()) {
|
||||
Log.d(Constants.TAG, "PGP_MESSAGE matched");
|
||||
textData = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
textData = textData.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(textData);
|
||||
} else {
|
||||
matcher = Apg.PGP_SIGNED_MESSAGE.matcher(textData);
|
||||
if (matcher.matches()) {
|
||||
Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
|
||||
textData = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
textData = textData.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(textData);
|
||||
|
||||
mDecryptString = getString(R.string.btn_verify);
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Nothing matched!");
|
||||
}
|
||||
}
|
||||
}
|
||||
mReplyTo = extras.getString(Apg.EXTRA_REPLY_TO);
|
||||
mSubject = extras.getString(Apg.EXTRA_SUBJECT);
|
||||
} else if (Apg.Intent.DECRYPT_FILE.equals(mIntent.getAction())) {
|
||||
mInputFilename = mIntent.getDataString();
|
||||
if ("file".equals(mIntent.getScheme())) {
|
||||
mInputFilename = Uri.decode(mInputFilename.substring(7));
|
||||
}
|
||||
mFilename.setText(mInputFilename);
|
||||
guessOutputFilename();
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceFile) {
|
||||
mSource.showNext();
|
||||
}
|
||||
} else if (Apg.Intent.DECRYPT_AND_RETURN.equals(mIntent.getAction())) {
|
||||
mContentUri = mIntent.getData();
|
||||
Bundle extras = mIntent.getExtras();
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
// disable home button on actionbar because this activity is run from another app
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
mReturnBinary = extras.getBoolean(Apg.EXTRA_BINARY, false);
|
||||
|
||||
if (mContentUri == null) {
|
||||
mData = extras.getByteArray(Apg.EXTRA_DATA);
|
||||
String data = extras.getString(Apg.EXTRA_TEXT);
|
||||
if (data != null) {
|
||||
Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
data = data.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(data);
|
||||
} else {
|
||||
matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data);
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
data = data.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(data);
|
||||
mDecryptString = getString(R.string.btn_verify);
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mReturnResult = true;
|
||||
}
|
||||
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceMessage
|
||||
&& mMessage.getText().length() == 0) {
|
||||
|
||||
CharSequence clipboardText = Compatibility.getClipboardText(this);
|
||||
|
||||
String data = "";
|
||||
if (clipboardText != null) {
|
||||
Matcher matcher = Apg.PGP_MESSAGE.matcher(clipboardText);
|
||||
if (!matcher.matches()) {
|
||||
matcher = Apg.PGP_SIGNED_MESSAGE.matcher(clipboardText);
|
||||
}
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
mMessage.setText(data);
|
||||
Toast.makeText(this, R.string.usingClipboardContent, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mSignatureLayout.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (mSignatureKeyId == 0) {
|
||||
return;
|
||||
}
|
||||
PGPPublicKeyRing key = Apg.getPublicKeyRing(mSignatureKeyId);
|
||||
if (key != null) {
|
||||
Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class);
|
||||
intent.setAction(Apg.Intent.LOOK_UP_KEY_ID);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, mSignatureKeyId);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mReplyEnabled = false;
|
||||
|
||||
// build new actionbar
|
||||
invalidateOptionsMenu();
|
||||
|
||||
if (mReturnResult) {
|
||||
mSourcePrevious.setClickable(false);
|
||||
mSourcePrevious.setEnabled(false);
|
||||
mSourcePrevious.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceNext.setClickable(false);
|
||||
mSourceNext.setEnabled(false);
|
||||
mSourceNext.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceLabel.setClickable(false);
|
||||
mSourceLabel.setEnabled(false);
|
||||
}
|
||||
|
||||
updateSource();
|
||||
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceMessage
|
||||
&& (mMessage.getText().length() > 0 || mData != null || mContentUri != null)) {
|
||||
decryptClicked();
|
||||
}
|
||||
}
|
||||
|
||||
private void openFile() {
|
||||
String filename = mFilename.getText().toString();
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
|
||||
intent.setData(Uri.parse("file://" + filename));
|
||||
intent.setType("*/*");
|
||||
|
||||
try {
|
||||
startActivityForResult(intent, Id.request.filename);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// No compatible file manager was found.
|
||||
Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void guessOutputFilename() {
|
||||
mInputFilename = mFilename.getText().toString();
|
||||
File file = new File(mInputFilename);
|
||||
String filename = file.getName();
|
||||
if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) {
|
||||
filename = filename.substring(0, filename.length() - 4);
|
||||
}
|
||||
mOutputFilename = Constants.path.APP_DIR + "/" + filename;
|
||||
}
|
||||
|
||||
private void updateSource() {
|
||||
switch (mSource.getCurrentView().getId()) {
|
||||
case R.id.sourceFile: {
|
||||
mSourceLabel.setText(R.string.label_file);
|
||||
mDecryptString = getString(R.string.btn_decrypt);
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
mDecryptString = getString(R.string.btn_decrypt);
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptClicked() {
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceFile) {
|
||||
mDecryptTarget = Id.target.file;
|
||||
} else {
|
||||
mDecryptTarget = Id.target.message;
|
||||
}
|
||||
initiateDecryption();
|
||||
}
|
||||
|
||||
private void initiateDecryption() {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
String currentFilename = mFilename.getText().toString();
|
||||
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
|
||||
guessOutputFilename();
|
||||
}
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
Toast.makeText(this, R.string.noFileSelected, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInputFilename.startsWith("file")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.errorMessage, getString(R.string.error_fileNotFound)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mDecryptTarget == Id.target.message) {
|
||||
String messageData = mMessage.getText().toString();
|
||||
Matcher matcher = Apg.PGP_SIGNED_MESSAGE.matcher(messageData);
|
||||
if (matcher.matches()) {
|
||||
mSignedOnly = true;
|
||||
decryptStart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// else treat it as an decrypted message/file
|
||||
mSignedOnly = false;
|
||||
String error = null;
|
||||
fillDataSource();
|
||||
try {
|
||||
InputData in = mDataSource.getInputData(this, false);
|
||||
try {
|
||||
setSecretKeyId(Apg.getDecryptionKeyId(this, in));
|
||||
if (getSecretKeyId() == Id.key.none) {
|
||||
throw new Apg.GeneralException(getString(R.string.error_noSecretKeyFound));
|
||||
}
|
||||
mAssumeSymmetricEncryption = false;
|
||||
} catch (Apg.NoAsymmetricEncryptionException e) {
|
||||
setSecretKeyId(Id.key.symmetric);
|
||||
in = mDataSource.getInputData(this, false);
|
||||
if (!Apg.hasSymmetricEncryption(this, in)) {
|
||||
throw new Apg.GeneralException(getString(R.string.error_noKnownEncryptionFound));
|
||||
}
|
||||
mAssumeSymmetricEncryption = true;
|
||||
}
|
||||
|
||||
if (getSecretKeyId() == Id.key.symmetric
|
||||
|| Apg.getCachedPassPhrase(getSecretKeyId()) == null) {
|
||||
showDialog(Id.dialog.pass_phrase);
|
||||
} else {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
error = getString(R.string.error_fileNotFound);
|
||||
} catch (IOException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void replyClicked() {
|
||||
Intent intent = new Intent(this, EncryptActivity.class);
|
||||
intent.setAction(Apg.Intent.ENCRYPT);
|
||||
String data = mMessage.getText().toString();
|
||||
data = data.replaceAll("(?m)^", "> ");
|
||||
data = "\n\n" + data;
|
||||
intent.putExtra(Apg.EXTRA_TEXT, data);
|
||||
intent.putExtra(Apg.EXTRA_SUBJECT, "Re: " + mSubject);
|
||||
intent.putExtra(Apg.EXTRA_SEND_TO, mReplyTo);
|
||||
intent.putExtra(Apg.EXTRA_SIGNATURE_KEY_ID, getSecretKeyId());
|
||||
intent.putExtra(Apg.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId });
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void askForOutputFilename() {
|
||||
showDialog(Id.dialog.output_filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passPhraseCallback(long keyId, String passPhrase) {
|
||||
super.passPhraseCallback(keyId, passPhrase);
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptStart() {
|
||||
showDialog(Id.dialog.decrypting);
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
fillDataSource();
|
||||
fillDataDestination();
|
||||
try {
|
||||
InputData in = mDataSource.getInputData(this, true);
|
||||
OutputStream out = mDataDestination.getOutputStream(this);
|
||||
|
||||
if (mSignedOnly) {
|
||||
data = Apg.verifyText(this, in, out, this);
|
||||
} else {
|
||||
data = Apg.decrypt(this, in, out, Apg.getCachedPassPhrase(getSecretKeyId()), this,
|
||||
mAssumeSymmetricEncryption);
|
||||
}
|
||||
|
||||
out.close();
|
||||
|
||||
if (mDataDestination.getStreamFilename() != null) {
|
||||
data.putString(Apg.EXTRA_RESULT_URI, "content://" + DataProvider.AUTHORITY
|
||||
+ "/data/" + mDataDestination.getStreamFilename());
|
||||
} else if (mDecryptTarget == Id.target.message) {
|
||||
if (mReturnBinary) {
|
||||
data.putByteArray(Apg.EXTRA_DECRYPTED_DATA,
|
||||
((ByteArrayOutputStream) out).toByteArray());
|
||||
} else {
|
||||
data.putString(Apg.EXTRA_DECRYPTED_MESSAGE, new String(
|
||||
((ByteArrayOutputStream) out).toByteArray()));
|
||||
}
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
error = "" + e;
|
||||
} catch (IOException e) {
|
||||
error = "" + e;
|
||||
} catch (SignatureException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
|
||||
data.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerCallback(Message msg) {
|
||||
Bundle data = msg.getData();
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.getInt(Constants.extras.STATUS) == Id.message.unknown_signature_key) {
|
||||
mUnknownSignatureKeyId = data.getLong(Constants.extras.KEY_ID);
|
||||
showDialog(Id.dialog.lookup_unknown_key);
|
||||
return;
|
||||
}
|
||||
|
||||
super.handlerCallback(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
removeDialog(Id.dialog.decrypting);
|
||||
mSignatureKeyId = 0;
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mReplyEnabled = false;
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(this, R.string.decryptionSuccessful, Toast.LENGTH_SHORT).show();
|
||||
if (mReturnResult) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtras(data);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mDecryptTarget) {
|
||||
case Id.target.message: {
|
||||
String decryptedMessage = data.getString(Apg.EXTRA_DECRYPTED_MESSAGE);
|
||||
mMessage.setText(decryptedMessage);
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
mReplyEnabled = false;
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.target.file: {
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
setDeleteFile(mInputFilename);
|
||||
showDialog(Id.dialog.delete_file);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// shouldn't happen
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.getBoolean(Apg.EXTRA_SIGNATURE)) {
|
||||
String userId = data.getString(Apg.EXTRA_SIGNATURE_USER_ID);
|
||||
mSignatureKeyId = data.getLong(Apg.EXTRA_SIGNATURE_KEY_ID);
|
||||
mUserIdRest.setText("id: " + Apg.getSmallFingerPrint(mSignatureKeyId));
|
||||
if (userId == null) {
|
||||
userId = getResources().getString(R.string.unknownUserId);
|
||||
}
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mUserId.setText(userId);
|
||||
|
||||
if (data.getBoolean(Apg.EXTRA_SIGNATURE_SUCCESS)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
} else if (data.getBoolean(Apg.EXTRA_SIGNATURE_UNKNOWN)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
Toast.makeText(this, R.string.unknownSignatureKeyTouchToLookUp, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} else {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
}
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
mFilename.setText(filename);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case Id.request.output_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;
|
||||
}
|
||||
|
||||
case Id.request.look_up_key_id: {
|
||||
PausableThread thread = getRunningThread();
|
||||
if (thread != null && thread.isPaused()) {
|
||||
thread.unpause();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case Id.dialog.output_filename: {
|
||||
return FileDialog.build(this, getString(R.string.title_decryptToFile),
|
||||
getString(R.string.specifyFileToDecryptTo), mOutputFilename,
|
||||
new FileDialog.OnClickListener() {
|
||||
public void onOkClick(String filename, boolean checked) {
|
||||
removeDialog(Id.dialog.output_filename);
|
||||
mOutputFilename = filename;
|
||||
decryptStart();
|
||||
}
|
||||
|
||||
public void onCancelClick() {
|
||||
removeDialog(Id.dialog.output_filename);
|
||||
}
|
||||
}, getString(R.string.filemanager_titleSave),
|
||||
getString(R.string.filemanager_btnSave), null, Id.request.output_filename);
|
||||
}
|
||||
|
||||
case Id.dialog.lookup_unknown_key: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.title_unknownSignatureKey);
|
||||
alert.setMessage(getString(R.string.lookupUnknownKey,
|
||||
Apg.getSmallFingerPrint(mUnknownSignatureKeyId)));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.lookup_unknown_key);
|
||||
Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class);
|
||||
intent.setAction(Apg.Intent.LOOK_UP_KEY_ID);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, mUnknownSignatureKeyId);
|
||||
startActivityForResult(intent, Id.request.look_up_key_id);
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.lookup_unknown_key);
|
||||
PausableThread thread = getRunningThread();
|
||||
if (thread != null && thread.isPaused()) {
|
||||
thread.unpause();
|
||||
}
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
|
||||
protected void fillDataSource() {
|
||||
mDataSource = new DataSource();
|
||||
if (mContentUri != null) {
|
||||
mDataSource.setUri(mContentUri);
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
mDataSource.setUri(mInputFilename);
|
||||
} else {
|
||||
if (mData != null) {
|
||||
mDataSource.setData(mData);
|
||||
} else {
|
||||
mDataSource.setText(mMessage.getText().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void fillDataDestination() {
|
||||
mDataDestination = new DataDestination();
|
||||
if (mContentUri != null) {
|
||||
mDataDestination.setMode(Id.mode.stream);
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
mDataDestination.setFilename(mOutputFilename);
|
||||
mDataDestination.setMode(Id.mode.file);
|
||||
} else {
|
||||
mDataDestination.setMode(Id.mode.byte_array);
|
||||
}
|
||||
}
|
||||
}
|
||||
412
org_apg/src/org/thialfihar/android/apg/ui/EditKeyActivity.java
Normal file
412
org_apg/src/org/thialfihar/android/apg/ui/EditKeyActivity.java
Normal file
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 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.ui;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.provider.Database;
|
||||
import org.thialfihar.android.apg.ui.widget.KeyEditor;
|
||||
import org.thialfihar.android.apg.ui.widget.SectionView;
|
||||
import org.thialfihar.android.apg.util.IterableIterator;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EditKeyActivity extends BaseActivity {
|
||||
private Intent mIntent = null;
|
||||
private ActionBar mActionBar;
|
||||
|
||||
private PGPSecretKeyRing mKeyRing = null;
|
||||
|
||||
private SectionView mUserIds;
|
||||
private SectionView mKeys;
|
||||
|
||||
private String mCurrentPassPhrase = null;
|
||||
private String mNewPassPhrase = null;
|
||||
|
||||
private Button mChangePassPhrase;
|
||||
|
||||
private CheckBox mNoPassphrase;
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(1, Id.menu.option.cancel, 0, R.string.btn_doNotSave).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
menu.add(1, Id.menu.option.save, 1, R.string.btn_save).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
startActivity(new Intent(this, SecretKeyListActivity.class));
|
||||
return true;
|
||||
|
||||
case Id.menu.option.save:
|
||||
saveClicked();
|
||||
return true;
|
||||
|
||||
case Id.menu.option.cancel:
|
||||
finish();
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.edit_key);
|
||||
|
||||
mActionBar = getSupportActionBar();
|
||||
|
||||
// find views
|
||||
mChangePassPhrase = (Button) findViewById(R.id.edit_key_btn_change_pass_phrase);
|
||||
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
|
||||
|
||||
Vector<String> userIds = new Vector<String>();
|
||||
Vector<PGPSecretKey> keys = new Vector<PGPSecretKey>();
|
||||
Vector<Integer> keysUsages = new Vector<Integer>();
|
||||
|
||||
// Catch Intents opened from other apps
|
||||
mIntent = getIntent();
|
||||
Bundle extras = mIntent.getExtras();
|
||||
if (Apg.Intent.CREATE_KEY.equals(mIntent.getAction())) {
|
||||
|
||||
mActionBar.setTitle(R.string.title_createKey);
|
||||
|
||||
mCurrentPassPhrase = "";
|
||||
|
||||
if (extras != null) {
|
||||
|
||||
// disable home button on actionbar because this activity is run from another app
|
||||
mActionBar.setDisplayShowTitleEnabled(true);
|
||||
mActionBar.setDisplayHomeAsUpEnabled(false);
|
||||
mActionBar.setHomeButtonEnabled(false);
|
||||
|
||||
// if userId is given, prefill the fields
|
||||
if (extras.containsKey(Apg.EXTRA_USER_IDS)) {
|
||||
Log.d(Constants.TAG, "UserIds are given!");
|
||||
userIds.add(extras.getString(Apg.EXTRA_USER_IDS));
|
||||
}
|
||||
|
||||
// if no passphrase is given
|
||||
if (extras.containsKey(Apg.EXTRA_NO_PASSPHRASE)) {
|
||||
boolean noPassphrase = extras.getBoolean(Apg.EXTRA_NO_PASSPHRASE);
|
||||
if (noPassphrase) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassPhrase.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// generate key
|
||||
if (extras.containsKey(Apg.EXTRA_GENERATE_DEFAULT_KEYS)) {
|
||||
boolean generateDefaultKeys = extras
|
||||
.getBoolean(Apg.EXTRA_GENERATE_DEFAULT_KEYS);
|
||||
if (generateDefaultKeys) {
|
||||
|
||||
// generate a RSA 2048 key for encryption and signing!
|
||||
try {
|
||||
PGPSecretKey masterKey = Apg.createKey(this, Id.choice.algorithm.rsa,
|
||||
2048, mCurrentPassPhrase, null);
|
||||
|
||||
// add new masterKey to keys array, which is then added to view
|
||||
keys.add(masterKey);
|
||||
keysUsages.add(Id.choice.usage.sign_only);
|
||||
|
||||
PGPSecretKey subKey = Apg.createKey(this, Id.choice.algorithm.rsa,
|
||||
2048, mCurrentPassPhrase, masterKey);
|
||||
|
||||
keys.add(subKey);
|
||||
keysUsages.add(Id.choice.usage.encrypt_only);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Creating initial key failed: +" + e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else if (Apg.Intent.EDIT_KEY.equals(mIntent.getAction())) {
|
||||
|
||||
mActionBar.setTitle(R.string.title_editKey);
|
||||
|
||||
mCurrentPassPhrase = Apg.getEditPassPhrase();
|
||||
if (mCurrentPassPhrase == null) {
|
||||
mCurrentPassPhrase = "";
|
||||
}
|
||||
|
||||
if (mCurrentPassPhrase.equals("")) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassPhrase.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (extras != null) {
|
||||
|
||||
if (extras.containsKey(Apg.EXTRA_KEY_ID)) {
|
||||
long keyId = mIntent.getExtras().getLong(Apg.EXTRA_KEY_ID);
|
||||
|
||||
if (keyId != 0) {
|
||||
PGPSecretKey masterKey = null;
|
||||
mKeyRing = Apg.getSecretKeyRing(keyId);
|
||||
if (mKeyRing != null) {
|
||||
masterKey = Apg.getMasterKey(mKeyRing);
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(
|
||||
mKeyRing.getSecretKeys())) {
|
||||
keys.add(key);
|
||||
keysUsages.add(-1); // get usage when view is created
|
||||
}
|
||||
}
|
||||
if (masterKey != null) {
|
||||
for (String userId : new IterableIterator<String>(
|
||||
masterKey.getUserIDs())) {
|
||||
userIds.add(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mChangePassPhrase.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
showDialog(Id.dialog.new_pass_phrase);
|
||||
}
|
||||
});
|
||||
|
||||
// disable passphrase when no passphrase checkobox is checked!
|
||||
mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
// remove passphrase
|
||||
mNewPassPhrase = null;
|
||||
|
||||
mChangePassPhrase.setVisibility(View.GONE);
|
||||
} else {
|
||||
mChangePassPhrase.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Build layout based on given userIds and keys
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container);
|
||||
mUserIds = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mUserIds.setType(Id.type.user_id);
|
||||
mUserIds.setUserIds(userIds);
|
||||
container.addView(mUserIds);
|
||||
mKeys = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mKeys.setType(Id.type.key);
|
||||
mKeys.setKeys(keys, keysUsages);
|
||||
container.addView(mKeys);
|
||||
|
||||
updatePassPhraseButtonText();
|
||||
}
|
||||
|
||||
private long getMasterKeyId() {
|
||||
if (mKeys.getEditors().getChildCount() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((KeyEditor) mKeys.getEditors().getChildAt(0)).getValue().getKeyID();
|
||||
}
|
||||
|
||||
public boolean isPassphraseSet() {
|
||||
if (mNoPassphrase.isChecked()) {
|
||||
return true;
|
||||
} else if ((!mCurrentPassPhrase.equals(""))
|
||||
|| (mNewPassPhrase != null && !mNewPassPhrase.equals(""))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case Id.dialog.new_pass_phrase: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
if (isPassphraseSet()) {
|
||||
alert.setTitle(R.string.title_changePassPhrase);
|
||||
} else {
|
||||
alert.setTitle(R.string.title_setPassPhrase);
|
||||
}
|
||||
alert.setMessage(R.string.enterPassPhraseTwice);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.passphrase, null);
|
||||
final EditText input1 = (EditText) view.findViewById(R.id.passphrase_passphrase);
|
||||
final EditText input2 = (EditText) view.findViewById(R.id.passphrase_passphrase_again);
|
||||
|
||||
alert.setView(view);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.new_pass_phrase);
|
||||
|
||||
String passPhrase1 = "" + input1.getText();
|
||||
String passPhrase2 = "" + input2.getText();
|
||||
if (!passPhrase1.equals(passPhrase2)) {
|
||||
showDialog(Id.dialog.pass_phrases_do_not_match);
|
||||
return;
|
||||
}
|
||||
|
||||
if (passPhrase1.equals("")) {
|
||||
showDialog(Id.dialog.no_pass_phrase);
|
||||
return;
|
||||
}
|
||||
|
||||
mNewPassPhrase = passPhrase1;
|
||||
updatePassPhraseButtonText();
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.new_pass_phrase);
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveClicked() {
|
||||
if (!isPassphraseSet()) {
|
||||
Toast.makeText(this, R.string.setAPassPhrase, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
showDialog(Id.dialog.saving);
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
try {
|
||||
String oldPassPhrase = mCurrentPassPhrase;
|
||||
String newPassPhrase = mNewPassPhrase;
|
||||
if (newPassPhrase == null) {
|
||||
newPassPhrase = oldPassPhrase;
|
||||
}
|
||||
Apg.buildSecretKey(this, mUserIds, mKeys, oldPassPhrase, newPassPhrase, this);
|
||||
Apg.setCachedPassPhrase(getMasterKeyId(), newPassPhrase);
|
||||
} catch (NoSuchProviderException e) {
|
||||
error = "" + e;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
error = "" + e;
|
||||
} catch (PGPException e) {
|
||||
error = "" + e;
|
||||
} catch (SignatureException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
} catch (Database.GeneralException e) {
|
||||
error = "" + e;
|
||||
} catch (IOException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
|
||||
data.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
removeDialog(Id.dialog.saving);
|
||||
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(EditKeyActivity.this, getString(R.string.errorMessage, error),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(EditKeyActivity.this, R.string.keySaved, Toast.LENGTH_SHORT).show();
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePassPhraseButtonText() {
|
||||
mChangePassPhrase.setText(isPassphraseSet() ? R.string.btn_changePassPhrase
|
||||
: R.string.btn_setPassPhrase);
|
||||
}
|
||||
}
|
||||
1042
org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java
Normal file
1042
org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java
Normal file
File diff suppressed because it is too large
Load Diff
191
org_apg/src/org/thialfihar/android/apg/ui/GeneralActivity.java
Normal file
191
org_apg/src/org/thialfihar/android/apg/ui/GeneralActivity.java
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.util.Choice;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class GeneralActivity extends BaseActivity {
|
||||
private Intent mIntent;
|
||||
private ArrayAdapter<Choice> mAdapter;
|
||||
private ListView mList;
|
||||
private Button mCancelButton;
|
||||
private String mDataString;
|
||||
private Uri mDataUri;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.general);
|
||||
|
||||
mIntent = getIntent();
|
||||
|
||||
InputStream inStream = null;
|
||||
{
|
||||
String data = mIntent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (data != null) {
|
||||
mDataString = data;
|
||||
inStream = new ByteArrayInputStream(data.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
if (inStream == null) {
|
||||
Uri data = mIntent.getData();
|
||||
if (data != null) {
|
||||
mDataUri = data;
|
||||
try {
|
||||
inStream = getContentResolver().openInputStream(data);
|
||||
} catch (FileNotFoundException e) {
|
||||
// didn't work
|
||||
Toast.makeText(this, "failed to open stream", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inStream == null) {
|
||||
Toast.makeText(this, "no data found", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
int contentType = Id.content.unknown;
|
||||
try {
|
||||
contentType = Apg.getStreamContent(this, inStream);
|
||||
inStream.close();
|
||||
} catch (IOException e) {
|
||||
// just means that there's no PGP data in there
|
||||
}
|
||||
|
||||
mList = (ListView) findViewById(R.id.options);
|
||||
Vector<Choice> choices = new Vector<Choice>();
|
||||
|
||||
if (contentType == Id.content.keys) {
|
||||
choices.add(new Choice(Id.choice.action.import_public,
|
||||
getString(R.string.action_importPublic)));
|
||||
choices.add(new Choice(Id.choice.action.import_secret,
|
||||
getString(R.string.action_importSecret)));
|
||||
}
|
||||
|
||||
if (contentType == Id.content.encrypted_data) {
|
||||
choices.add(new Choice(Id.choice.action.decrypt, getString(R.string.action_decrypt)));
|
||||
}
|
||||
|
||||
if (contentType == Id.content.unknown) {
|
||||
choices.add(new Choice(Id.choice.action.encrypt, getString(R.string.action_encrypt)));
|
||||
}
|
||||
|
||||
mAdapter = new ArrayAdapter<Choice>(this, android.R.layout.simple_list_item_1, choices);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
mList.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
|
||||
clicked(mAdapter.getItem(arg2).getId());
|
||||
}
|
||||
});
|
||||
|
||||
mCancelButton = (Button) findViewById(R.id.btn_cancel);
|
||||
mCancelButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
GeneralActivity.this.finish();
|
||||
}
|
||||
});
|
||||
|
||||
if (choices.size() == 1) {
|
||||
clicked(choices.get(0).getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void clicked(int id) {
|
||||
Intent intent = new Intent();
|
||||
switch (id) {
|
||||
case Id.choice.action.encrypt: {
|
||||
intent.setClass(this, EncryptActivity.class);
|
||||
if (mDataString != null) {
|
||||
intent.setAction(Apg.Intent.ENCRYPT);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mDataString);
|
||||
} else if (mDataUri != null) {
|
||||
intent.setAction(Apg.Intent.ENCRYPT_FILE);
|
||||
intent.setDataAndType(mDataUri, mIntent.getType());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.choice.action.decrypt: {
|
||||
intent.setClass(this, DecryptActivity.class);
|
||||
if (mDataString != null) {
|
||||
intent.setAction(Apg.Intent.DECRYPT);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mDataString);
|
||||
} else if (mDataUri != null) {
|
||||
intent.setAction(Apg.Intent.DECRYPT_FILE);
|
||||
intent.setDataAndType(mDataUri, mIntent.getType());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.choice.action.import_public: {
|
||||
intent.setClass(this, PublicKeyListActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT);
|
||||
if (mDataString != null) {
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mDataString);
|
||||
} else if (mDataUri != null) {
|
||||
intent.setDataAndType(mDataUri, mIntent.getType());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.choice.action.import_secret: {
|
||||
intent.setClass(this, SecretKeyListActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT);
|
||||
if (mDataString != null) {
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mDataString);
|
||||
} else if (mDataUri != null) {
|
||||
intent.setDataAndType(mDataUri, mIntent.getType());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// shouldn't happen
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
79
org_apg/src/org/thialfihar/android/apg/ui/HelpActivity.java
Normal file
79
org_apg/src/org/thialfihar/android/apg/ui/HelpActivity.java
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.thialfihar.android.apg.ui;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.util.Utils;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockActivity;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class HelpActivity extends SherlockActivity {
|
||||
Activity mActivity;
|
||||
TextView mHelpText;
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate View for this Activity
|
||||
*/
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.help_activity);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mActivity = this;
|
||||
|
||||
mHelpText = (TextView) findViewById(R.id.help_text);
|
||||
|
||||
// load html from html file from /res/raw
|
||||
String helpText = Utils.readContentFromResource(mActivity, R.raw.help);
|
||||
|
||||
// set text from resources with html markup
|
||||
mHelpText.setText(Html.fromHtml(helpText));
|
||||
// make links work
|
||||
mHelpText.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.HkpKeyServer;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.KeyServer.QueryException;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
public class ImportFromQRCodeActivity extends BaseActivity {
|
||||
private static final String TAG = "ImportFromQRCodeActivity";
|
||||
|
||||
private final Bundle status = new Bundle();
|
||||
private final Message msg = new Message();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
new IntentIntegrator(this).initiateScan();
|
||||
}
|
||||
|
||||
private void importAndSign(final long keyId, final String expectedFingerprint) {
|
||||
if (expectedFingerprint != null && expectedFingerprint.length() > 0) {
|
||||
|
||||
Thread t = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// TODO: display some sort of spinner here while the user waits
|
||||
|
||||
HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]); // TODO: there should be only 1
|
||||
String encodedKey = server.get(keyId);
|
||||
|
||||
PGPKeyRing keyring = Apg.decodeKeyRing(new ByteArrayInputStream(encodedKey.getBytes()));
|
||||
if (keyring != null && keyring instanceof PGPPublicKeyRing) {
|
||||
PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring;
|
||||
|
||||
// make sure the fingerprints match before we cache this thing
|
||||
String actualFingerprint = Apg.convertToHex(publicKeyRing.getPublicKey().getFingerprint());
|
||||
if (expectedFingerprint.equals(actualFingerprint)) {
|
||||
// store the signed key in our local cache
|
||||
int retval = Apg.storeKeyRingInCache(publicKeyRing);
|
||||
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache");
|
||||
} else {
|
||||
Intent intent = new Intent(ImportFromQRCodeActivity.this, SignKeyActivity.class);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
|
||||
startActivityForResult(intent, Id.request.sign_key);
|
||||
}
|
||||
} else {
|
||||
status.putString(Apg.EXTRA_ERROR, "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key.");
|
||||
}
|
||||
}
|
||||
} catch (QueryException e) {
|
||||
Log.e(TAG, "Failed to query KeyServer", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to query KeyServer");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to query KeyServer", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to query KeyServer");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
t.setName("KeyExchange Download Thread");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case IntentIntegrator.REQUEST_CODE: {
|
||||
boolean debug = true; // TODO: remove this!!!
|
||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
|
||||
if (debug || (scanResult != null && scanResult.getFormatName() != null)) {
|
||||
String[] bits = debug ? new String[] { "5993515643896327656", "0816 F68A 6816 68FB 01BF 2CA5 532D 3EB9 1E2F EDE8" } : scanResult.getContents().split(",");
|
||||
if (bits.length != 2) {
|
||||
return; // dont know how to handle this. Not a valid code
|
||||
}
|
||||
|
||||
long keyId = Long.parseLong(bits[0]);
|
||||
String expectedFingerprint = bits[1];
|
||||
|
||||
importAndSign(keyId, expectedFingerprint);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case Id.request.sign_key: {
|
||||
// signals the end of processing. Signature was either applied, or it wasnt
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
msg.setData(status);
|
||||
sendMessage(msg);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show(); // TODO
|
||||
finish();
|
||||
}
|
||||
}
|
||||
769
org_apg/src/org/thialfihar/android/apg/ui/KeyListActivity.java
Normal file
769
org_apg/src/org/thialfihar/android/apg/ui/KeyListActivity.java
Normal file
@@ -0,0 +1,769 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.FileDialog;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.InputData;
|
||||
import org.thialfihar.android.apg.provider.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.Keys;
|
||||
import org.thialfihar.android.apg.provider.UserIds;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
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.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;
|
||||
|
||||
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;
|
||||
|
||||
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 boolean mDeleteAfterImport = false;
|
||||
|
||||
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 = findViewById(R.id.layout_filter);
|
||||
mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
|
||||
mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
handleIntent(new Intent());
|
||||
}
|
||||
});
|
||||
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
protected 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);
|
||||
|
||||
if (Apg.Intent.IMPORT.equals(intent.getAction())) {
|
||||
if ("file".equals(intent.getScheme()) && intent.getDataString() != null) {
|
||||
mImportFilename = Uri.decode(intent.getDataString().replace("file://", ""));
|
||||
} else {
|
||||
mImportData = intent.getStringExtra(Apg.EXTRA_TEXT);
|
||||
}
|
||||
importKeys();
|
||||
}
|
||||
}
|
||||
|
||||
@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(android.view.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() {
|
||||
public void onOkClick(String filename, boolean checked) {
|
||||
removeDialog(Id.dialog.import_keys);
|
||||
mDeleteAfterImport = checked;
|
||||
mImportFilename = filename;
|
||||
importKeys();
|
||||
}
|
||||
|
||||
public void onCancelClick() {
|
||||
removeDialog(Id.dialog.import_keys);
|
||||
}
|
||||
}, getString(R.string.filemanager_titleOpen),
|
||||
getString(R.string.filemanager_btnOpen),
|
||||
getString(R.string.label_deleteAfterImport), 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() {
|
||||
public void onOkClick(String filename, boolean checked) {
|
||||
removeDialog(thisDialogId);
|
||||
mExportFilename = filename;
|
||||
exportKeys();
|
||||
}
|
||||
|
||||
public void onCancelClick() {
|
||||
removeDialog(thisDialogId);
|
||||
}
|
||||
}, getString(R.string.filemanager_titleSave),
|
||||
getString(R.string.filemanager_btnSave), null, 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(Constants.extras.STATUS, Id.message.import_done);
|
||||
} else {
|
||||
data.putInt(Constants.extras.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(Constants.extras.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");
|
||||
int bad = data.getInt("bad");
|
||||
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();
|
||||
if (bad > 0) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(this.getString(R.string.badKeysEncountered, bad));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
alert.create().show();
|
||||
} else if (mDeleteAfterImport) {
|
||||
// everything went well, so now delete, if that was turned on
|
||||
setDeleteFile(mImportFilename);
|
||||
showDialog(Id.dialog.delete_file);
|
||||
}
|
||||
}
|
||||
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 static final int FINGER_PRINT = 2;
|
||||
|
||||
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 String fingerPrint;
|
||||
|
||||
public KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize,
|
||||
boolean canSign, boolean canEncrypt) {
|
||||
this.type = KEY;
|
||||
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 KeyChild(String fingerPrint, boolean isFingerPrint) {
|
||||
type = FINGER_PRINT;
|
||||
this.fingerPrint = fingerPrint;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
int masterKeyId = -1;
|
||||
long fingerPrintId = -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);
|
||||
fingerPrintId = c.getLong(1);
|
||||
}
|
||||
}
|
||||
c.close();
|
||||
|
||||
if (masterKeyId != -1) {
|
||||
children.insertElementAt(new KeyChild(Apg.getFingerPrint(fingerPrintId), true), 0);
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
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 = Apg.getSmallFingerPrint(child.keyId);
|
||||
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;
|
||||
}
|
||||
|
||||
case KeyChild.FINGER_PRINT: {
|
||||
view = mInflater.inflate(R.layout.key_list_child_item_user_id, null);
|
||||
TextView userId = (TextView) view.findViewById(R.id.userId);
|
||||
userId.setText(getString(R.string.fingerprint) + ":\n"
|
||||
+ child.fingerPrint.replace(" ", "\n"));
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.HkpKeyServer;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* gpg --send-key activity
|
||||
*
|
||||
* Sends the selected public key to a key server
|
||||
*/
|
||||
public class KeyServerExportActivity extends BaseActivity {
|
||||
|
||||
private Button export;
|
||||
private Spinner keyServer;
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
startActivity(new Intent(this, PublicKeyListActivity.class));
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.key_server_export_layout);
|
||||
|
||||
export = (Button) findViewById(R.id.btn_export_to_server);
|
||||
keyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, mPreferences.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
keyServer.setAdapter(adapter);
|
||||
if (adapter.getCount() > 0) {
|
||||
keyServer.setSelection(0);
|
||||
} else {
|
||||
export.setEnabled(false);
|
||||
}
|
||||
|
||||
export.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startThread();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem());
|
||||
|
||||
int keyRingId = getIntent().getIntExtra(Apg.EXTRA_KEY_ID, -1);
|
||||
|
||||
PGPKeyRing keyring = Apg.getKeyRing(keyRingId);
|
||||
if (keyring != null && keyring instanceof PGPPublicKeyRing) {
|
||||
boolean uploaded = Apg.uploadKeyRingToServer(server, (PGPPublicKeyRing) keyring);
|
||||
if (!uploaded) {
|
||||
error = "Unable to export key to selected server";
|
||||
}
|
||||
}
|
||||
|
||||
data.putInt(Constants.extras.STATUS, Id.message.export_done);
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(this, R.string.keySendSuccess, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.ui.widget.Editor;
|
||||
import org.thialfihar.android.apg.ui.widget.KeyServerEditor;
|
||||
import org.thialfihar.android.apg.ui.widget.Editor.EditorListener;
|
||||
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class KeyServerPreferenceActivity extends BaseActivity implements OnClickListener,
|
||||
EditorListener {
|
||||
private LayoutInflater mInflater;
|
||||
private ViewGroup mEditors;
|
||||
private View mAdd;
|
||||
private TextView mTitle;
|
||||
private TextView mSummary;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.key_server_preference);
|
||||
|
||||
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
mTitle = (TextView) findViewById(R.id.title);
|
||||
mSummary = (TextView) findViewById(R.id.summary);
|
||||
|
||||
mTitle.setText(R.string.label_keyServers);
|
||||
|
||||
mEditors = (ViewGroup) findViewById(R.id.editors);
|
||||
mAdd = findViewById(R.id.add);
|
||||
mAdd.setOnClickListener(this);
|
||||
|
||||
Intent intent = getIntent();
|
||||
String servers[] = intent.getStringArrayExtra(Apg.EXTRA_KEY_SERVERS);
|
||||
if (servers != null) {
|
||||
for (int i = 0; i < servers.length; ++i) {
|
||||
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(
|
||||
R.layout.key_server_editor, mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
view.setValue(servers[i]);
|
||||
mEditors.addView(view);
|
||||
}
|
||||
}
|
||||
|
||||
Button okButton = (Button) findViewById(R.id.btn_ok);
|
||||
okButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
okClicked();
|
||||
}
|
||||
});
|
||||
|
||||
Button cancelButton = (Button) findViewById(R.id.btn_cancel);
|
||||
cancelButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
cancelClicked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onDeleted(Editor editor) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
|
||||
mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
mEditors.addView(view);
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void okClicked() {
|
||||
Intent data = new Intent();
|
||||
Vector<String> servers = new Vector<String>();
|
||||
for (int i = 0; i < mEditors.getChildCount(); ++i) {
|
||||
KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i);
|
||||
String tmp = editor.getValue();
|
||||
if (tmp.length() > 0) {
|
||||
servers.add(tmp);
|
||||
}
|
||||
}
|
||||
String[] dummy = new String[0];
|
||||
data.putExtra(Apg.EXTRA_KEY_SERVERS, servers.toArray(dummy));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// override this, so no option menu is added (as would be in BaseActivity), since
|
||||
// we're still in preferences
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.HkpKeyServer;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.KeyServer.InsufficientQuery;
|
||||
import org.thialfihar.android.apg.KeyServer.KeyInfo;
|
||||
import org.thialfihar.android.apg.KeyServer.QueryException;
|
||||
import org.thialfihar.android.apg.KeyServer.TooManyResponses;
|
||||
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class KeyServerQueryActivity extends BaseActivity {
|
||||
private ListView mList;
|
||||
private EditText mQuery;
|
||||
private Button mSearch;
|
||||
private KeyInfoListAdapter mAdapter;
|
||||
private Spinner mKeyServer;
|
||||
|
||||
private int mQueryType;
|
||||
private String mQueryString;
|
||||
private long mQueryId;
|
||||
private volatile List<KeyInfo> mSearchResult;
|
||||
private volatile String mKeyData;
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
startActivity(new Intent(this, PublicKeyListActivity.class));
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.key_server_query_layout);
|
||||
|
||||
mQuery = (EditText) findViewById(R.id.query);
|
||||
mSearch = (Button) findViewById(R.id.btn_search);
|
||||
mList = (ListView) findViewById(R.id.list);
|
||||
mAdapter = new KeyInfoListAdapter(this);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
mKeyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, mPreferences.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mKeyServer.setAdapter(adapter);
|
||||
if (adapter.getCount() > 0) {
|
||||
mKeyServer.setSelection(0);
|
||||
} else {
|
||||
mSearch.setEnabled(false);
|
||||
}
|
||||
|
||||
mList.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> adapter, View view, int position, long keyId) {
|
||||
get(keyId);
|
||||
}
|
||||
});
|
||||
|
||||
mSearch.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
String query = mQuery.getText().toString();
|
||||
search(query);
|
||||
}
|
||||
});
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (Apg.Intent.LOOK_UP_KEY_ID.equals(intent.getAction())
|
||||
|| Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(intent.getAction())) {
|
||||
long keyId = intent.getLongExtra(Apg.EXTRA_KEY_ID, 0);
|
||||
if (keyId != 0) {
|
||||
String query = "0x" + Apg.keyToHex(keyId);
|
||||
mQuery.setText(query);
|
||||
search(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void search(String query) {
|
||||
showDialog(Id.dialog.querying);
|
||||
mQueryType = Id.keyserver.search;
|
||||
mQueryString = query;
|
||||
mAdapter.setKeys(new Vector<KeyInfo>());
|
||||
startThread();
|
||||
}
|
||||
|
||||
private void get(long keyId) {
|
||||
showDialog(Id.dialog.querying);
|
||||
mQueryType = Id.keyserver.get;
|
||||
mQueryId = keyId;
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
ProgressDialog progress = (ProgressDialog) super.onCreateDialog(id);
|
||||
progress.setMessage(this.getString(R.string.progress_queryingServer,
|
||||
(String) mKeyServer.getSelectedItem()));
|
||||
return progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
try {
|
||||
HkpKeyServer server = new HkpKeyServer((String) mKeyServer.getSelectedItem());
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
mSearchResult = server.search(mQueryString);
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
mKeyData = server.get(mQueryId);
|
||||
}
|
||||
} catch (QueryException e) {
|
||||
error = "" + e;
|
||||
} catch (InsufficientQuery e) {
|
||||
error = "Insufficient query.";
|
||||
} catch (TooManyResponses e) {
|
||||
error = "Too many responses.";
|
||||
}
|
||||
|
||||
data.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
removeDialog(Id.dialog.querying);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
if (mSearchResult != null) {
|
||||
Toast.makeText(this, getString(R.string.keysFound, mSearchResult.size()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
mAdapter.setKeys(mSearchResult);
|
||||
}
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
Intent orgIntent = getIntent();
|
||||
if (Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(orgIntent.getAction())) {
|
||||
if (mKeyData != null) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mKeyData);
|
||||
setResult(RESULT_OK, intent);
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
}
|
||||
finish();
|
||||
} else {
|
||||
if (mKeyData != null) {
|
||||
Intent intent = new Intent(this, PublicKeyListActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mKeyData);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class KeyInfoListAdapter extends BaseAdapter {
|
||||
protected LayoutInflater mInflater;
|
||||
protected Activity mActivity;
|
||||
protected List<KeyInfo> mKeys;
|
||||
|
||||
public KeyInfoListAdapter(Activity activity) {
|
||||
mActivity = activity;
|
||||
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mKeys = new Vector<KeyInfo>();
|
||||
}
|
||||
|
||||
public void setKeys(List<KeyInfo> keys) {
|
||||
mKeys = keys;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mKeys.size();
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
return mKeys.get(position);
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
return mKeys.get(position).keyId;
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
KeyInfo keyInfo = mKeys.get(position);
|
||||
|
||||
View view = mInflater.inflate(R.layout.key_server_query_result_item, null);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(R.string.noKey);
|
||||
TextView algorithm = (TextView) view.findViewById(R.id.algorithm);
|
||||
algorithm.setText("");
|
||||
TextView status = (TextView) view.findViewById(R.id.status);
|
||||
status.setText("");
|
||||
|
||||
String userId = keyInfo.userIds.get(0);
|
||||
if (userId != null) {
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mainUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mainUserId.setText(userId);
|
||||
}
|
||||
|
||||
keyId.setText(Apg.getSmallFingerPrint(keyInfo.keyId));
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
algorithm.setText("" + keyInfo.size + "/" + keyInfo.algorithm);
|
||||
|
||||
if (keyInfo.revoked != null) {
|
||||
status.setText("revoked");
|
||||
} else {
|
||||
status.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
LinearLayout ll = (LinearLayout) view.findViewById(R.id.list);
|
||||
if (keyInfo.userIds.size() == 1) {
|
||||
ll.setVisibility(View.GONE);
|
||||
} else {
|
||||
boolean first = true;
|
||||
boolean second = true;
|
||||
for (String uid : keyInfo.userIds) {
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (!second) {
|
||||
View sep = new View(mActivity);
|
||||
sep.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 1));
|
||||
sep.setBackgroundResource(android.R.drawable.divider_horizontal_dark);
|
||||
ll.addView(sep);
|
||||
}
|
||||
TextView uidView = (TextView) mInflater.inflate(
|
||||
R.layout.key_server_query_result_user_id, null);
|
||||
uidView.setText(uid);
|
||||
ll.addView(uidView);
|
||||
second = false;
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
221
org_apg/src/org/thialfihar/android/apg/ui/MailListActivity.java
Normal file
221
org_apg/src/org/thialfihar/android/apg/ui/MailListActivity.java
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Preferences;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class MailListActivity extends ListActivity {
|
||||
LayoutInflater mInflater = null;
|
||||
|
||||
public static final String EXTRA_ACCOUNT = "account";
|
||||
|
||||
private static class Conversation {
|
||||
public long id;
|
||||
public String subject;
|
||||
public Vector<Message> messages;
|
||||
|
||||
public Conversation(long id, String subject) {
|
||||
this.id = id;
|
||||
this.subject = subject;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Message {
|
||||
public Conversation parent;
|
||||
public long id;
|
||||
public String subject;
|
||||
public String fromAddress;
|
||||
public String data;
|
||||
public String replyTo;
|
||||
public boolean signedOnly;
|
||||
|
||||
public Message(Conversation parent, long id, String subject,
|
||||
String fromAddress, String replyTo,
|
||||
String data, boolean signedOnly) {
|
||||
this.parent = parent;
|
||||
this.id = id;
|
||||
this.subject = subject;
|
||||
this.fromAddress = fromAddress;
|
||||
this.replyTo = replyTo;
|
||||
this.data = data;
|
||||
if (this.replyTo == null || this.replyTo.equals("")) {
|
||||
this.replyTo = this.fromAddress;
|
||||
}
|
||||
this.signedOnly = signedOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector<Conversation> mConversations;
|
||||
private Vector<Message> mMessages;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
mConversations = new Vector<Conversation>();
|
||||
mMessages = new Vector<Message>();
|
||||
|
||||
String account = getIntent().getExtras().getString(EXTRA_ACCOUNT);
|
||||
// TODO: what if account is null?
|
||||
Uri uri = Uri.parse("content://gmail-ls/conversations/" + account);
|
||||
Cursor cursor =
|
||||
managedQuery(uri, new String[] { "conversation_id", "subject" }, null, null, null);
|
||||
for (int i = 0; i < cursor.getCount(); ++i) {
|
||||
cursor.moveToPosition(i);
|
||||
|
||||
int idIndex = cursor.getColumnIndex("conversation_id");
|
||||
int subjectIndex = cursor.getColumnIndex("subject");
|
||||
long conversationId = cursor.getLong(idIndex);
|
||||
Conversation conversation =
|
||||
new Conversation(conversationId, cursor.getString(subjectIndex));
|
||||
Uri messageUri = Uri.withAppendedPath(uri, "" + conversationId + "/messages");
|
||||
Cursor messageCursor =
|
||||
managedQuery(messageUri, new String[] {
|
||||
"messageId",
|
||||
"subject",
|
||||
"fromAddress",
|
||||
"replyToAddresses",
|
||||
"body" }, null, null, null);
|
||||
Vector<Message> messages = new Vector<Message>();
|
||||
for (int j = 0; j < messageCursor.getCount(); ++j) {
|
||||
messageCursor.moveToPosition(j);
|
||||
idIndex = messageCursor.getColumnIndex("messageId");
|
||||
subjectIndex = messageCursor.getColumnIndex("subject");
|
||||
int fromAddressIndex = messageCursor.getColumnIndex("fromAddress");
|
||||
int replyToIndex = messageCursor.getColumnIndex("replyToAddresses");
|
||||
int bodyIndex = messageCursor.getColumnIndex("body");
|
||||
String data = messageCursor.getString(bodyIndex);
|
||||
data = Html.fromHtml(data).toString();
|
||||
boolean signedOnly = false;
|
||||
Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
} else {
|
||||
matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data);
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
signedOnly = true;
|
||||
} else {
|
||||
data = null;
|
||||
}
|
||||
}
|
||||
Message message =
|
||||
new Message(conversation,
|
||||
messageCursor.getLong(idIndex),
|
||||
messageCursor.getString(subjectIndex),
|
||||
messageCursor.getString(fromAddressIndex),
|
||||
messageCursor.getString(replyToIndex),
|
||||
data, signedOnly);
|
||||
|
||||
messages.add(message);
|
||||
mMessages.add(message);
|
||||
}
|
||||
conversation.messages = messages;
|
||||
mConversations.add(conversation);
|
||||
}
|
||||
|
||||
setListAdapter(new MailboxAdapter());
|
||||
getListView().setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> arg0, View v, int position, long id) {
|
||||
Intent intent = new Intent(MailListActivity.this, DecryptActivity.class);
|
||||
intent.setAction(Apg.Intent.DECRYPT);
|
||||
Message message = (Message) ((MailboxAdapter) getListAdapter()).getItem(position);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, message.data);
|
||||
intent.putExtra(Apg.EXTRA_SUBJECT, message.subject);
|
||||
intent.putExtra(Apg.EXTRA_REPLY_TO, message.replyTo);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class MailboxAdapter extends BaseAdapter implements ListAdapter {
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
Message message = (Message) getItem(position);
|
||||
return message.data != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mMessages.size();
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
return mMessages.get(position);
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
return mMessages.get(position).id;
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = mInflater.inflate(R.layout.mailbox_message_item, null);
|
||||
|
||||
Message message = (Message) getItem(position);
|
||||
|
||||
TextView subject = (TextView) view.findViewById(R.id.subject);
|
||||
TextView email = (TextView) view.findViewById(R.id.emailAddress);
|
||||
ImageView status = (ImageView) view.findViewById(R.id.ic_status);
|
||||
|
||||
subject.setText(message.subject);
|
||||
email.setText(message.fromAddress);
|
||||
if (message.data != null) {
|
||||
if (message.signedOnly) {
|
||||
status.setImageResource(R.drawable.signed);
|
||||
} else {
|
||||
status.setImageResource(R.drawable.encrypted);
|
||||
}
|
||||
status.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
status.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
213
org_apg/src/org/thialfihar/android/apg/ui/MainActivity.java
Normal file
213
org_apg/src/org/thialfihar/android/apg/ui/MainActivity.java
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class MainActivity extends BaseActivity {
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public void manageKeysOnClick(View view) {
|
||||
startActivity(new Intent(this, PublicKeyListActivity.class));
|
||||
}
|
||||
|
||||
public void myKeysOnClick(View view) {
|
||||
startActivity(new Intent(this, SecretKeyListActivity.class));
|
||||
}
|
||||
|
||||
public void encryptOnClick(View view) {
|
||||
Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
|
||||
intent.setAction(Apg.Intent.ENCRYPT);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
public void decryptOnClick(View view) {
|
||||
Intent intent = new Intent(MainActivity.this, DecryptActivity.class);
|
||||
intent.setAction(Apg.Intent.DECRYPT);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
public void scanQrcodeOnClick(View view) {
|
||||
Intent intent = new Intent(this, ImportFromQRCodeActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT_FROM_QR_CODE);
|
||||
startActivityForResult(intent, Id.request.import_from_qr_code);
|
||||
}
|
||||
|
||||
public void helpOnClick(View view) {
|
||||
startActivity(new Intent(this, HelpActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
// if (!mPreferences.hasSeenHelp()) {
|
||||
// showDialog(Id.dialog.help);
|
||||
// }
|
||||
//
|
||||
// if (Apg.isReleaseVersion(this) && !mPreferences.hasSeenChangeLog(Apg.getVersion(this))) {
|
||||
// showDialog(Id.dialog.change_log);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
|
||||
// case Id.dialog.change_log: {
|
||||
// AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
//
|
||||
// alert.setTitle("Changes " + Apg.getFullVersion(this));
|
||||
// LayoutInflater inflater = (LayoutInflater) this
|
||||
// .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
// View layout = inflater.inflate(R.layout.info, null);
|
||||
// TextView message = (TextView) layout.findViewById(R.id.message);
|
||||
//
|
||||
// message.setText("Changes:\n" + "* \n" + "\n"
|
||||
// + "WARNING: be careful editing your existing keys, as they "
|
||||
// + "WILL be stripped of certificates right now.\n" + "\n"
|
||||
// + "Also: key cross-certification is NOT supported, so signing "
|
||||
// + "with those keys will get a warning when the signature is " + "checked.\n"
|
||||
// + "\n" + "I hope APG continues to be useful to you, please send "
|
||||
// + "bug reports, feature wishes, feedback.");
|
||||
// alert.setView(layout);
|
||||
//
|
||||
// alert.setCancelable(false);
|
||||
// alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
// public void onClick(DialogInterface dialog, int id) {
|
||||
// MainActivity.this.removeDialog(Id.dialog.change_log);
|
||||
// mPreferences.setHasSeenChangeLog(Apg.getVersion(MainActivity.this), true);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return alert.create();
|
||||
// }
|
||||
|
||||
// case Id.dialog.help: {
|
||||
// AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
//
|
||||
// alert.setTitle(R.string.title_help);
|
||||
//
|
||||
// LayoutInflater inflater = (LayoutInflater) this
|
||||
// .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
// View layout = inflater.inflate(R.layout.info, null);
|
||||
// TextView message = (TextView) layout.findViewById(R.id.message);
|
||||
// message.setText(R.string.text_help);
|
||||
//
|
||||
// TransformFilter packageNames = new TransformFilter() {
|
||||
// public final String transformUrl(final Matcher match, String url) {
|
||||
// String name = match.group(1).toLowerCase();
|
||||
// if (name.equals("astro")) {
|
||||
// return "com.metago.astro";
|
||||
// } else if (name.equals("k-9 mail")) {
|
||||
// return "com.fsck.k9";
|
||||
// } else {
|
||||
// return "org.openintents.filemanager";
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// Pattern pattern = Pattern.compile("(OI File Manager|ASTRO|K-9 Mail)");
|
||||
// String scheme = "market://search?q=pname:";
|
||||
// message.setAutoLinkMask(0);
|
||||
// Linkify.addLinks(message, pattern, scheme, null, packageNames);
|
||||
//
|
||||
// alert.setView(layout);
|
||||
//
|
||||
// alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
// public void onClick(DialogInterface dialog, int id) {
|
||||
// MainActivity.this.removeDialog(Id.dialog.help);
|
||||
// mPreferences.setHasSeenHelp(true);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return alert.create();
|
||||
// }
|
||||
|
||||
default: {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences)
|
||||
.setIcon(R.drawable.ic_menu_settings)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
menu.add(0, Id.menu.option.about, 1, R.string.menu_about).setIcon(R.drawable.ic_menu_about)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.about: {
|
||||
startActivity(new Intent(this, AboutActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.option.preferences: {
|
||||
startActivity(new Intent(this, PreferencesActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
TextView nameTextView = (TextView) v.findViewById(R.id.accountName);
|
||||
if (nameTextView != null) {
|
||||
menu.setHeaderTitle(nameTextView.getText());
|
||||
menu.add(0, Id.menu.delete, 0, R.string.menu_deleteAccount);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.Preferences;
|
||||
import org.thialfihar.android.apg.ui.widget.IntegerListPreference;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockPreferenceActivity;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceScreen;
|
||||
|
||||
|
||||
public class PreferencesActivity extends SherlockPreferenceActivity {
|
||||
private IntegerListPreference mPassPhraseCacheTtl = null;
|
||||
private IntegerListPreference mEncryptionAlgorithm = null;
|
||||
private IntegerListPreference mHashAlgorithm = null;
|
||||
private IntegerListPreference mMessageCompression = null;
|
||||
private IntegerListPreference mFileCompression = null;
|
||||
private CheckBoxPreference mAsciiArmour = null;
|
||||
private CheckBoxPreference mForceV3Signatures = null;
|
||||
private PreferenceScreen mKeyServerPreference = null;
|
||||
private Preferences mPreferences;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mPreferences = Preferences.getPreferences(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
addPreferencesFromResource(R.xml.apg_preferences);
|
||||
|
||||
mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL);
|
||||
mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl());
|
||||
mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry());
|
||||
mPassPhraseCacheTtl
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mPassPhraseCacheTtl.setValue(newValue.toString());
|
||||
mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry());
|
||||
mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString()));
|
||||
BaseActivity.startCacheService(PreferencesActivity.this, mPreferences);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM);
|
||||
int valueIds[] = { PGPEncryptedData.AES_128, PGPEncryptedData.AES_192,
|
||||
PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH,
|
||||
PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES,
|
||||
PGPEncryptedData.IDEA, };
|
||||
String entries[] = { "AES-128", "AES-192", "AES-256", "Blowfish", "Twofish", "CAST5",
|
||||
"DES", "Triple DES", "IDEA", };
|
||||
String values[] = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
mEncryptionAlgorithm.setEntries(entries);
|
||||
mEncryptionAlgorithm.setEntryValues(values);
|
||||
mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm());
|
||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||
mEncryptionAlgorithm
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mEncryptionAlgorithm.setValue(newValue.toString());
|
||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||
mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue
|
||||
.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM);
|
||||
valueIds = new int[] { HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160,
|
||||
HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256,
|
||||
HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, };
|
||||
entries = new String[] { "MD5", "RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384",
|
||||
"SHA-512", };
|
||||
values = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
mHashAlgorithm.setEntries(entries);
|
||||
mHashAlgorithm.setEntryValues(values);
|
||||
mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm());
|
||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||
mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mHashAlgorithm.setValue(newValue.toString());
|
||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||
mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mMessageCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION);
|
||||
valueIds = new int[] { Id.choice.compression.none, Id.choice.compression.zip,
|
||||
Id.choice.compression.zlib, Id.choice.compression.bzip2, };
|
||||
entries = new String[] {
|
||||
getString(R.string.choice_none) + " (" + getString(R.string.fast) + ")",
|
||||
"ZIP (" + getString(R.string.fast) + ")",
|
||||
"ZLIB (" + getString(R.string.fast) + ")",
|
||||
"BZIP2 (" + getString(R.string.very_slow) + ")", };
|
||||
values = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
mMessageCompression.setEntries(entries);
|
||||
mMessageCompression.setEntryValues(values);
|
||||
mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression());
|
||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||
mMessageCompression
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mMessageCompression.setValue(newValue.toString());
|
||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||
mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue
|
||||
.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mFileCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION);
|
||||
mFileCompression.setEntries(entries);
|
||||
mFileCompression.setEntryValues(values);
|
||||
mFileCompression.setValue("" + mPreferences.getDefaultFileCompression());
|
||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||
mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mFileCompression.setValue(newValue.toString());
|
||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||
mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR);
|
||||
mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
|
||||
mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mAsciiArmour.setChecked((Boolean) newValue);
|
||||
mPreferences.setDefaultAsciiArmour((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES);
|
||||
mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures());
|
||||
mForceV3Signatures
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mForceV3Signatures.setChecked((Boolean) newValue);
|
||||
mPreferences.setForceV3Signatures((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS);
|
||||
String servers[] = mPreferences.getKeyServers();
|
||||
mKeyServerPreference.setSummary(getResources().getString(R.string.nKeyServers,
|
||||
servers.length));
|
||||
mKeyServerPreference
|
||||
.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent intent = new Intent(PreferencesActivity.this,
|
||||
KeyServerPreferenceActivity.class);
|
||||
intent.putExtra(Apg.EXTRA_KEY_SERVERS, mPreferences.getKeyServers());
|
||||
startActivityForResult(intent, Id.request.key_server_preference);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.key_server_preference: {
|
||||
if (resultCode == RESULT_CANCELED || data == null) {
|
||||
return;
|
||||
}
|
||||
String servers[] = data.getStringArrayExtra(Apg.EXTRA_KEY_SERVERS);
|
||||
mPreferences.setKeyServers(servers);
|
||||
mKeyServerPreference.setSummary(getResources().getString(R.string.nKeyServers,
|
||||
servers.length));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.View;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||
|
||||
public class PublicKeyListActivity extends KeyListActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
mExportFilename = Constants.path.APP_DIR + "/pubexport.asc";
|
||||
mKeyType = Id.type.public_key;
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(1, Id.menu.option.search, 0, R.string.menu_search)
|
||||
.setIcon(R.drawable.ic_menu_search).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
menu.add(1, Id.menu.option.scanQRCode, 1, R.string.menu_scanQRCode)
|
||||
.setIcon(R.drawable.ic_menu_scan_qrcode)
|
||||
.setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
menu.add(1, Id.menu.option.key_server, 2, R.string.menu_keyServer)
|
||||
.setIcon(R.drawable.ic_menu_search_list)
|
||||
.setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
menu.add(0, Id.menu.option.import_keys, 3, R.string.menu_importKeys).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
menu.add(0, Id.menu.option.export_keys, 4, R.string.menu_exportKeys).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.key_server: {
|
||||
startActivity(new Intent(this, KeyServerQueryActivity.class));
|
||||
|
||||
return true;
|
||||
}
|
||||
case Id.menu.option.scanQRCode: {
|
||||
Intent intent = new Intent(this, ImportFromQRCodeActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT_FROM_QR_CODE);
|
||||
startActivityForResult(intent, Id.request.import_from_qr_code);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
|
||||
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||
|
||||
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||
// TODO: user id? menu.setHeaderTitle("Key");
|
||||
menu.add(0, Id.menu.export, 0, R.string.menu_exportKey);
|
||||
menu.add(0, Id.menu.delete, 1, R.string.menu_deleteKey);
|
||||
menu.add(0, Id.menu.update, 1, R.string.menu_updateKey);
|
||||
menu.add(0, Id.menu.exportToServer, 1, R.string.menu_exportKeyToServer);
|
||||
menu.add(0, Id.menu.signKey, 1, R.string.menu_signKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(android.view.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.update: {
|
||||
mSelectedItem = groupPosition;
|
||||
final int keyRingId = mListAdapter.getKeyRingId(groupPosition);
|
||||
long keyId = 0;
|
||||
Object keyRing = Apg.getKeyRing(keyRingId);
|
||||
if (keyRing != null && keyRing instanceof PGPPublicKeyRing) {
|
||||
keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID();
|
||||
}
|
||||
if (keyId == 0) {
|
||||
// this shouldn't happen
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, KeyServerQueryActivity.class);
|
||||
intent.setAction(Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
|
||||
startActivityForResult(intent, Id.request.look_up_key_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.exportToServer: {
|
||||
mSelectedItem = groupPosition;
|
||||
final int keyRingId = mListAdapter.getKeyRingId(groupPosition);
|
||||
|
||||
Intent intent = new Intent(this, KeyServerExportActivity.class);
|
||||
intent.setAction(Apg.Intent.EXPORT_KEY_TO_SERVER);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyRingId);
|
||||
startActivityForResult(intent, Id.request.export_to_server);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.signKey: {
|
||||
mSelectedItem = groupPosition;
|
||||
final int keyRingId = mListAdapter.getKeyRingId(groupPosition);
|
||||
long keyId = 0;
|
||||
Object keyRing = Apg.getKeyRing(keyRingId);
|
||||
if (keyRing != null && keyRing instanceof PGPPublicKeyRing) {
|
||||
keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID();
|
||||
}
|
||||
|
||||
if (keyId == 0) {
|
||||
// this shouldn't happen
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, SignKeyActivity.class);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
|
||||
startActivity(intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.look_up_key_id: {
|
||||
if (resultCode == RESULT_CANCELED || data == null
|
||||
|| data.getStringExtra(Apg.EXTRA_TEXT) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, PublicKeyListActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, data.getStringExtra(Apg.EXTRA_TEXT));
|
||||
handleIntent(intent);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.AskForSecretKeyPassPhrase;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.View;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||
import android.widget.ExpandableListView.OnChildClickListener;
|
||||
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
|
||||
public class SecretKeyListActivity extends KeyListActivity implements OnChildClickListener {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
mExportFilename = Constants.path.APP_DIR + "/secexport.asc";
|
||||
mKeyType = Id.type.secret_key;
|
||||
super.onCreate(savedInstanceState);
|
||||
mList.setOnChildClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(3, Id.menu.option.search, 0, R.string.menu_search)
|
||||
.setIcon(R.drawable.ic_menu_search).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
menu.add(1, Id.menu.option.create, 1, R.string.menu_createKey).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
menu.add(0, Id.menu.option.import_keys, 2, R.string.menu_importKeys).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
menu.add(0, Id.menu.option.export_keys, 3, R.string.menu_exportKeys).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.create: {
|
||||
createKey();
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
|
||||
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||
|
||||
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||
// TODO: user id? menu.setHeaderTitle("Key");
|
||||
menu.add(0, Id.menu.edit, 0, R.string.menu_editKey);
|
||||
menu.add(0, Id.menu.export, 1, R.string.menu_exportKey);
|
||||
menu.add(0, Id.menu.delete, 2, R.string.menu_deleteKey);
|
||||
menu.add(0, Id.menu.share, 2, R.string.menu_share);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(android.view.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.edit: {
|
||||
mSelectedItem = groupPosition;
|
||||
checkPassPhraseAndEdit();
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.share: {
|
||||
mSelectedItem = groupPosition;
|
||||
|
||||
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter())
|
||||
.getGroupId(mSelectedItem);
|
||||
String msg = keyId + "," + Apg.getFingerPrint(keyId);
|
||||
|
||||
new IntentIntegrator(this).shareText(msg);
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
|
||||
int childPosition, long id) {
|
||||
mSelectedItem = groupPosition;
|
||||
checkPassPhraseAndEdit();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case Id.dialog.pass_phrase: {
|
||||
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter())
|
||||
.getGroupId(mSelectedItem);
|
||||
return AskForSecretKeyPassPhrase.createDialog(this, keyId, this);
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void checkPassPhraseAndEdit() {
|
||||
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
|
||||
String passPhrase = Apg.getCachedPassPhrase(keyId);
|
||||
if (passPhrase == null) {
|
||||
showDialog(Id.dialog.pass_phrase);
|
||||
} else {
|
||||
Apg.setEditPassPhrase(passPhrase);
|
||||
editKey();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passPhraseCallback(long keyId, String passPhrase) {
|
||||
super.passPhraseCallback(keyId, passPhrase);
|
||||
Apg.setEditPassPhrase(passPhrase);
|
||||
editKey();
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Apg.setEditPassPhrase("");
|
||||
Intent intent = new Intent(Apg.Intent.CREATE_KEY);
|
||||
startActivityForResult(intent, Id.message.create_key);
|
||||
}
|
||||
|
||||
private void editKey() {
|
||||
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
|
||||
Intent intent = new Intent(Apg.Intent.EDIT_KEY);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
|
||||
startActivityForResult(intent, Id.message.edit_key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.message.create_key: // intentionally no break
|
||||
case Id.message.edit_key: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
refreshList();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.ui.widget.SelectPublicKeyListAdapter;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectPublicKeyListActivity extends BaseActivity {
|
||||
protected ListView mList;
|
||||
protected SelectPublicKeyListAdapter mListAdapter;
|
||||
protected View mFilterLayout;
|
||||
protected Button mClearFilterButton;
|
||||
protected TextView mFilterInfo;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
setContentView(R.layout.select_public_key);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
mList = (ListView) findViewById(R.id.list);
|
||||
// needed in Android 1.5, where the XML attribute gets ignored
|
||||
mList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
|
||||
mFilterLayout = findViewById(R.id.layout_filter);
|
||||
mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
|
||||
mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
handleIntent(new Intent());
|
||||
}
|
||||
});
|
||||
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
long selectedKeyIds[] = null;
|
||||
selectedKeyIds = intent.getLongArrayExtra(Apg.EXTRA_SELECTION);
|
||||
|
||||
if (selectedKeyIds == null) {
|
||||
Vector<Long> vector = new Vector<Long>();
|
||||
for (int i = 0; i < mList.getCount(); ++i) {
|
||||
if (mList.isItemChecked(i)) {
|
||||
vector.add(mList.getItemIdAtPosition(i));
|
||||
}
|
||||
}
|
||||
selectedKeyIds = new long[vector.size()];
|
||||
for (int i = 0; i < vector.size(); ++i) {
|
||||
selectedKeyIds[i] = vector.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
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 SelectPublicKeyListAdapter(this, mList, searchString, selectedKeyIds);
|
||||
mList.setAdapter(mListAdapter);
|
||||
|
||||
if (selectedKeyIds != null) {
|
||||
for (int i = 0; i < mListAdapter.getCount(); ++i) {
|
||||
long keyId = mListAdapter.getItemId(i);
|
||||
for (int j = 0; j < selectedKeyIds.length; ++j) {
|
||||
if (keyId == selectedKeyIds[j]) {
|
||||
mList.setItemChecked(i, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void okClicked() {
|
||||
Intent data = new Intent();
|
||||
Vector<Long> keys = new Vector<Long>();
|
||||
Vector<String> userIds = new Vector<String>();
|
||||
for (int i = 0; i < mList.getCount(); ++i) {
|
||||
if (mList.isItemChecked(i)) {
|
||||
keys.add(mList.getItemIdAtPosition(i));
|
||||
userIds.add((String) mList.getItemAtPosition(i));
|
||||
}
|
||||
}
|
||||
long selectedKeyIds[] = new long[keys.size()];
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
selectedKeyIds[i] = keys.get(i);
|
||||
}
|
||||
String userIdArray[] = new String[0];
|
||||
data.putExtra(Apg.EXTRA_SELECTION, selectedKeyIds);
|
||||
data.putExtra(Apg.EXTRA_USER_IDS, userIds.toArray(userIdArray));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon(
|
||||
android.R.drawable.ic_menu_search);
|
||||
menu.add(1, Id.menu.option.cancel, 0, R.string.btn_doNotSave).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
menu.add(1, Id.menu.option.okay, 1, R.string.btn_okay).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case Id.menu.option.okay:
|
||||
okClicked();
|
||||
return true;
|
||||
|
||||
case Id.menu.option.cancel:
|
||||
cancelClicked();
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.ui.widget.SelectSecretKeyListAdapter;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectSecretKeyListActivity extends BaseActivity {
|
||||
protected ListView mList;
|
||||
protected SelectSecretKeyListAdapter mListAdapter;
|
||||
protected View mFilterLayout;
|
||||
protected Button mClearFilterButton;
|
||||
protected TextView mFilterInfo;
|
||||
|
||||
protected long mSelectedKeyId = 0;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
setContentView(R.layout.select_secret_key);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
mList = (ListView) findViewById(R.id.list);
|
||||
|
||||
mList.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(Apg.EXTRA_KEY_ID, id);
|
||||
data.putExtra(Apg.EXTRA_USER_ID, (String) mList.getItemAtPosition(position));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
mFilterLayout = findViewById(R.id.layout_filter);
|
||||
mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
|
||||
mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
handleIntent(new Intent());
|
||||
}
|
||||
});
|
||||
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@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 SelectSecretKeyListAdapter(this, mList, searchString);
|
||||
mList.setAdapter(mListAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon(
|
||||
android.R.drawable.ic_menu_search);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
327
org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java
Normal file
327
org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java
Normal file
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.HkpKeyServer;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* gpg --sign-key
|
||||
*
|
||||
* signs the specified public key with the specified secret master key
|
||||
*/
|
||||
public class SignKeyActivity extends BaseActivity {
|
||||
private static final String TAG = "SignKeyActivity";
|
||||
|
||||
private long pubKeyId = 0;
|
||||
private long masterKeyId = 0;
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
startActivity(new Intent(this, PublicKeyListActivity.class));
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// check we havent already signed it
|
||||
setContentView(R.layout.sign_key_layout);
|
||||
|
||||
final Spinner keyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, mPreferences.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
keyServer.setAdapter(adapter);
|
||||
|
||||
final CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey);
|
||||
if (!sendKey.isChecked()) {
|
||||
keyServer.setEnabled(false);
|
||||
} else {
|
||||
keyServer.setEnabled(true);
|
||||
}
|
||||
|
||||
sendKey.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (!isChecked) {
|
||||
keyServer.setEnabled(false);
|
||||
} else {
|
||||
keyServer.setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Button sign = (Button) findViewById(R.id.sign);
|
||||
sign.setEnabled(false); // disabled until the user selects a key to sign with
|
||||
sign.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (pubKeyId != 0) {
|
||||
initiateSigning();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pubKeyId = getIntent().getLongExtra(Apg.EXTRA_KEY_ID, 0);
|
||||
if (pubKeyId == 0) {
|
||||
finish(); // nothing to do if we dont know what key to sign
|
||||
} else {
|
||||
// kick off the SecretKey selection activity so the user chooses which key to sign with
|
||||
// first
|
||||
Intent intent = new Intent(this, SelectSecretKeyListActivity.class);
|
||||
startActivityForResult(intent, Id.request.secret_keys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handles the UI bits of the signing process on the UI thread
|
||||
*/
|
||||
private void initiateSigning() {
|
||||
PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId);
|
||||
if (pubring != null) {
|
||||
// if we have already signed this key, dont bother doing it again
|
||||
boolean alreadySigned = false;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSignature> itr = pubring.getPublicKey(pubKeyId).getSignatures();
|
||||
while (itr.hasNext()) {
|
||||
PGPSignature sig = itr.next();
|
||||
if (sig.getKeyID() == masterKeyId) {
|
||||
alreadySigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadySigned) {
|
||||
/*
|
||||
* get the user's passphrase for this key (if required)
|
||||
*/
|
||||
String passphrase = Apg.getCachedPassPhrase(masterKeyId);
|
||||
if (passphrase == null) {
|
||||
showDialog(Id.dialog.pass_phrase);
|
||||
return; // bail out; need to wait until the user has entered the passphrase
|
||||
// before trying again
|
||||
} else {
|
||||
startSigning();
|
||||
}
|
||||
} else {
|
||||
final Bundle status = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
status.putString(Apg.EXTRA_ERROR, "Key has already been signed");
|
||||
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
msg.setData(status);
|
||||
sendMessage(msg);
|
||||
|
||||
setResult(Id.return_value.error);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSecretKeyId() {
|
||||
return masterKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passPhraseCallback(long keyId, String passPhrase) {
|
||||
super.passPhraseCallback(keyId, passPhrase);
|
||||
startSigning();
|
||||
}
|
||||
|
||||
/**
|
||||
* kicks off the actual signing process on a background thread
|
||||
*/
|
||||
private void startSigning() {
|
||||
showDialog(Id.dialog.signing);
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final Bundle status = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
try {
|
||||
String passphrase = Apg.getCachedPassPhrase(masterKeyId);
|
||||
if (passphrase == null || passphrase.length() <= 0) {
|
||||
status.putString(Apg.EXTRA_ERROR, "Unable to obtain passphrase");
|
||||
} else {
|
||||
PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId);
|
||||
|
||||
/*
|
||||
* sign the incoming key
|
||||
*/
|
||||
PGPSecretKey secretKey = Apg.getSecretKey(masterKeyId);
|
||||
PGPPrivateKey signingKey = secretKey.extractPrivateKey(passphrase.toCharArray(),
|
||||
BouncyCastleProvider.PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(secretKey.getPublicKey()
|
||||
.getAlgorithm(), PGPUtil.SHA256, BouncyCastleProvider.PROVIDER_NAME);
|
||||
sGen.initSign(PGPSignature.DIRECT_KEY, signingKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
sGen.setHashedSubpackets(packetVector);
|
||||
|
||||
PGPPublicKey signedKey = PGPPublicKey.addCertification(
|
||||
pubring.getPublicKey(pubKeyId), sGen.generate());
|
||||
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
|
||||
|
||||
// check if we need to send the key to the server or not
|
||||
CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey);
|
||||
if (sendKey.isChecked()) {
|
||||
Spinner keyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem());
|
||||
|
||||
/*
|
||||
* upload the newly signed key to the key server
|
||||
*/
|
||||
|
||||
Apg.uploadKeyRingToServer(server, pubring);
|
||||
}
|
||||
|
||||
// store the signed key in our local cache
|
||||
int retval = Apg.storeKeyRingInCache(pubring);
|
||||
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache");
|
||||
}
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Log.e(TAG, "Failed to sign key", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
return;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "Failed to sign key", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
return;
|
||||
} catch (NoSuchProviderException e) {
|
||||
Log.e(TAG, "Failed to sign key", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
return;
|
||||
} catch (SignatureException e) {
|
||||
Log.e(TAG, "Failed to sign key", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
return;
|
||||
}
|
||||
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
msg.setData(status);
|
||||
sendMessage(msg);
|
||||
|
||||
if (status.containsKey(Apg.EXTRA_ERROR)) {
|
||||
setResult(Id.return_value.error);
|
||||
} else {
|
||||
setResult(Id.return_value.ok);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.secret_keys: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
masterKeyId = data.getLongExtra(Apg.EXTRA_KEY_ID, 0);
|
||||
|
||||
// re-enable the sign button so the user can initiate the sign process
|
||||
Button sign = (Button) findViewById(R.id.sign);
|
||||
sign.setEnabled(true);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
removeDialog(Id.dialog.signing);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* 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.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Custom layout that arranges children in a grid-like manner, optimizing for even horizontal and
|
||||
* vertical whitespace.
|
||||
*/
|
||||
public class DashboardLayout extends ViewGroup {
|
||||
private static final int UNEVEN_GRID_PENALTY_MULTIPLIER = 10;
|
||||
|
||||
private int mMaxChildWidth = 0;
|
||||
private int mMaxChildHeight = 0;
|
||||
|
||||
public DashboardLayout(Context context) {
|
||||
super(context, null);
|
||||
}
|
||||
|
||||
public DashboardLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs, 0);
|
||||
}
|
||||
|
||||
public DashboardLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
mMaxChildWidth = 0;
|
||||
mMaxChildHeight = 0;
|
||||
|
||||
// Measure once to find the maximum child size.
|
||||
|
||||
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
|
||||
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);
|
||||
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
|
||||
MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST);
|
||||
|
||||
final int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
||||
|
||||
mMaxChildWidth = Math.max(mMaxChildWidth, child.getMeasuredWidth());
|
||||
mMaxChildHeight = Math.max(mMaxChildHeight, child.getMeasuredHeight());
|
||||
}
|
||||
|
||||
// Measure again for each child to be exactly the same size.
|
||||
|
||||
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildWidth, MeasureSpec.EXACTLY);
|
||||
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildHeight, MeasureSpec.EXACTLY);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
||||
}
|
||||
|
||||
setMeasuredDimension(resolveSize(mMaxChildWidth, widthMeasureSpec),
|
||||
resolveSize(mMaxChildHeight, heightMeasureSpec));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int width = r - l;
|
||||
int height = b - t;
|
||||
|
||||
final int count = getChildCount();
|
||||
|
||||
// Calculate the number of visible children.
|
||||
int visibleCount = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
++visibleCount;
|
||||
}
|
||||
|
||||
if (visibleCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate what number of rows and columns will optimize for even horizontal and
|
||||
// vertical whitespace between items. Start with a 1 x N grid, then try 2 x N, and so on.
|
||||
int bestSpaceDifference = Integer.MAX_VALUE;
|
||||
int spaceDifference;
|
||||
|
||||
// Horizontal and vertical space between items
|
||||
int hSpace = 0;
|
||||
int vSpace = 0;
|
||||
|
||||
int cols = 1;
|
||||
int rows;
|
||||
|
||||
while (true) {
|
||||
rows = (visibleCount - 1) / cols + 1;
|
||||
|
||||
hSpace = ((width - mMaxChildWidth * cols) / (cols + 1));
|
||||
vSpace = ((height - mMaxChildHeight * rows) / (rows + 1));
|
||||
|
||||
spaceDifference = Math.abs(vSpace - hSpace);
|
||||
if (rows * cols != visibleCount) {
|
||||
spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER;
|
||||
} else if (rows * mMaxChildHeight > height || cols * mMaxChildWidth > width) {
|
||||
spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER;
|
||||
}
|
||||
|
||||
if (spaceDifference < bestSpaceDifference) {
|
||||
// Found a better whitespace squareness/ratio
|
||||
bestSpaceDifference = spaceDifference;
|
||||
|
||||
// If we found a better whitespace squareness and there's only 1 row, this is
|
||||
// the best we can do.
|
||||
if (rows == 1) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// This is a worse whitespace ratio, use the previous value of cols and exit.
|
||||
--cols;
|
||||
rows = (visibleCount - 1) / cols + 1;
|
||||
hSpace = ((width - mMaxChildWidth * cols) / (cols + 1));
|
||||
vSpace = ((height - mMaxChildHeight * rows) / (rows + 1));
|
||||
break;
|
||||
}
|
||||
|
||||
++cols;
|
||||
}
|
||||
|
||||
// Lay out children based on calculated best-fit number of rows and cols.
|
||||
|
||||
// If we chose a layout that has negative horizontal or vertical space, force it to zero.
|
||||
hSpace = Math.max(0, hSpace);
|
||||
vSpace = Math.max(0, vSpace);
|
||||
|
||||
// Re-use width/height variables to be child width/height.
|
||||
width = (width - hSpace * (cols + 1)) / cols;
|
||||
height = (height - vSpace * (rows + 1)) / rows;
|
||||
|
||||
int left, top;
|
||||
int col, row;
|
||||
int visibleIndex = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
row = visibleIndex / cols;
|
||||
col = visibleIndex % cols;
|
||||
|
||||
left = hSpace * (col + 1) + width * col;
|
||||
top = vSpace * (row + 1) + height * row;
|
||||
|
||||
child.layout(left, top, (hSpace == 0 && col == cols - 1) ? r : (left + width),
|
||||
(vSpace == 0 && row == rows - 1) ? b : (top + height));
|
||||
++visibleIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
org_apg/src/org/thialfihar/android/apg/ui/widget/Editor.java
Normal file
25
org_apg/src/org/thialfihar/android/apg/ui/widget/Editor.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.ui.widget;
|
||||
|
||||
public interface Editor {
|
||||
public interface EditorListener {
|
||||
public void onDeleted(Editor editor);
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* 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.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.ListPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* A list preference which persists its values as integers instead of strings.
|
||||
* Code reading the values should use
|
||||
* {@link android.content.SharedPreferences#getInt}.
|
||||
* When using XML-declared arrays for entry values, the arrays should be regular
|
||||
* string arrays containing valid integer values.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class IntegerListPreference extends ListPreference {
|
||||
|
||||
public IntegerListPreference(Context context) {
|
||||
super(context);
|
||||
|
||||
verifyEntryValues(null);
|
||||
}
|
||||
|
||||
public IntegerListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
verifyEntryValues(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryValues(CharSequence[] entryValues) {
|
||||
CharSequence[] oldValues = getEntryValues();
|
||||
super.setEntryValues(entryValues);
|
||||
verifyEntryValues(oldValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryValues(int entryValuesResId) {
|
||||
CharSequence[] oldValues = getEntryValues();
|
||||
super.setEntryValues(entryValuesResId);
|
||||
verifyEntryValues(oldValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPersistedString(String defaultReturnValue) {
|
||||
// During initial load, there's no known default value
|
||||
int defaultIntegerValue = Integer.MIN_VALUE;
|
||||
if (defaultReturnValue != null) {
|
||||
defaultIntegerValue = Integer.parseInt(defaultReturnValue);
|
||||
}
|
||||
|
||||
// When the list preference asks us to read a string, instead read an
|
||||
// integer.
|
||||
int value = getPersistedInt(defaultIntegerValue);
|
||||
return Integer.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean persistString(String value) {
|
||||
// When asked to save a string, instead save an integer
|
||||
return persistInt(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
private void verifyEntryValues(CharSequence[] oldValues) {
|
||||
CharSequence[] entryValues = getEntryValues();
|
||||
if (entryValues == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (CharSequence entryValue : entryValues) {
|
||||
try {
|
||||
Integer.parseInt(entryValue.toString());
|
||||
} catch (NumberFormatException nfe) {
|
||||
super.setEntryValues(oldValues);
|
||||
throw nfe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
235
org_apg/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java
Normal file
235
org_apg/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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.ui.widget;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.util.Choice;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.DatePicker;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Vector;
|
||||
|
||||
public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private PGPSecretKey mKey;
|
||||
|
||||
private EditorListener mEditorListener = null;
|
||||
|
||||
private boolean mIsMasterKey;
|
||||
ImageButton mDeleteButton;
|
||||
TextView mAlgorithm;
|
||||
TextView mKeyId;
|
||||
Spinner mUsage;
|
||||
TextView mCreationDate;
|
||||
Button mExpiryDateButton;
|
||||
GregorianCalendar mExpiryDate;
|
||||
|
||||
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = new DatePickerDialog.OnDateSetListener() {
|
||||
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
|
||||
GregorianCalendar date = new GregorianCalendar(year, monthOfYear, dayOfMonth);
|
||||
setExpiryDate(date);
|
||||
}
|
||||
};
|
||||
|
||||
public KeyEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public KeyEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mAlgorithm = (TextView) findViewById(R.id.algorithm);
|
||||
mKeyId = (TextView) findViewById(R.id.keyId);
|
||||
mCreationDate = (TextView) findViewById(R.id.creation);
|
||||
mExpiryDateButton = (Button) findViewById(R.id.expiry);
|
||||
mUsage = (Spinner) findViewById(R.id.usage);
|
||||
Choice choices[] = {
|
||||
new Choice(Id.choice.usage.sign_only, getResources().getString(
|
||||
R.string.choice_signOnly)),
|
||||
new Choice(Id.choice.usage.encrypt_only, getResources().getString(
|
||||
R.string.choice_encryptOnly)),
|
||||
new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString(
|
||||
R.string.choice_signAndEncrypt)), };
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mUsage.setAdapter(adapter);
|
||||
|
||||
mDeleteButton = (ImageButton) findViewById(R.id.delete);
|
||||
mDeleteButton.setOnClickListener(this);
|
||||
|
||||
setExpiryDate(null);
|
||||
|
||||
mExpiryDateButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
GregorianCalendar date = mExpiryDate;
|
||||
if (date == null) {
|
||||
date = new GregorianCalendar();
|
||||
}
|
||||
|
||||
DatePickerDialog dialog = new DatePickerDialog(getContext(),
|
||||
mExpiryDateSetListener, date.get(Calendar.YEAR), date.get(Calendar.MONTH),
|
||||
date.get(Calendar.DAY_OF_MONTH));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setButton(Dialog.BUTTON_NEGATIVE, getContext()
|
||||
.getString(R.string.btn_noDate), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
setExpiryDate(null);
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setValue(PGPSecretKey key, boolean isMasterKey, int usage) {
|
||||
mKey = key;
|
||||
|
||||
mIsMasterKey = isMasterKey;
|
||||
if (mIsMasterKey) {
|
||||
mDeleteButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
mAlgorithm.setText(Apg.getAlgorithmInfo(key));
|
||||
String keyId1Str = Apg.getSmallFingerPrint(key.getKeyID());
|
||||
String keyId2Str = Apg.getSmallFingerPrint(key.getKeyID() >> 32);
|
||||
mKeyId.setText(keyId1Str + " " + keyId2Str);
|
||||
|
||||
Vector<Choice> choices = new Vector<Choice>();
|
||||
boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
|
||||
if (!isElGamalKey) {
|
||||
choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString(
|
||||
R.string.choice_signOnly)));
|
||||
}
|
||||
if (!mIsMasterKey) {
|
||||
choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString(
|
||||
R.string.choice_encryptOnly)));
|
||||
}
|
||||
if (!isElGamalKey) {
|
||||
choices.add(new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString(
|
||||
R.string.choice_signAndEncrypt)));
|
||||
}
|
||||
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mUsage.setAdapter(adapter);
|
||||
|
||||
// Set value in choice dropdown to key
|
||||
int selectId = 0;
|
||||
if (Apg.isEncryptionKey(key)) {
|
||||
if (Apg.isSigningKey(key)) {
|
||||
selectId = Id.choice.usage.sign_and_encrypt;
|
||||
} else {
|
||||
selectId = Id.choice.usage.encrypt_only;
|
||||
}
|
||||
} else {
|
||||
// set usage if it is predefined
|
||||
if (usage != -1) {
|
||||
selectId = usage;
|
||||
} else {
|
||||
selectId = Id.choice.usage.sign_only;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (int i = 0; i < choices.size(); ++i) {
|
||||
if (choices.get(i).getId() == selectId) {
|
||||
mUsage.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GregorianCalendar cal = new GregorianCalendar();
|
||||
cal.setTime(Apg.getCreationDate(key));
|
||||
mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime()));
|
||||
cal = new GregorianCalendar();
|
||||
Date date = Apg.getExpiryDate(key);
|
||||
if (date == null) {
|
||||
setExpiryDate(null);
|
||||
} else {
|
||||
cal.setTime(Apg.getExpiryDate(key));
|
||||
setExpiryDate(cal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public PGPSecretKey getValue() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
final ViewGroup parent = (ViewGroup) getParent();
|
||||
if (v == mDeleteButton) {
|
||||
parent.removeView(this);
|
||||
if (mEditorListener != null) {
|
||||
mEditorListener.onDeleted(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener) {
|
||||
mEditorListener = listener;
|
||||
}
|
||||
|
||||
private void setExpiryDate(GregorianCalendar date) {
|
||||
mExpiryDate = date;
|
||||
if (date == null) {
|
||||
mExpiryDateButton.setText(R.string.none);
|
||||
} else {
|
||||
mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime()));
|
||||
}
|
||||
}
|
||||
|
||||
public GregorianCalendar getExpiryDate() {
|
||||
return mExpiryDate;
|
||||
}
|
||||
|
||||
public int getUsage() {
|
||||
return ((Choice) mUsage.getSelectedItem()).getId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.ui.widget;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private EditorListener mEditorListener = null;
|
||||
|
||||
ImageButton mDeleteButton;
|
||||
TextView mServer;
|
||||
|
||||
public KeyServerEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public KeyServerEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mServer = (TextView) findViewById(R.id.server);
|
||||
|
||||
mDeleteButton = (ImageButton) findViewById(R.id.delete);
|
||||
mDeleteButton.setOnClickListener(this);
|
||||
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
mServer.setText(value);
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return mServer.getText().toString().trim();
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
final ViewGroup parent = (ViewGroup)getParent();
|
||||
if (v == mDeleteButton) {
|
||||
parent.removeView(this);
|
||||
if (mEditorListener != null) {
|
||||
mEditorListener.onDeleted(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener) {
|
||||
mEditorListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* 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.ui.widget;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.ui.widget.Editor.EditorListener;
|
||||
import org.thialfihar.android.apg.util.Choice;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Runnable {
|
||||
private LayoutInflater mInflater;
|
||||
private View mAdd;
|
||||
private ViewGroup mEditors;
|
||||
private TextView mTitle;
|
||||
private int mType = 0;
|
||||
|
||||
private Choice mNewKeyAlgorithmChoice;
|
||||
private int mNewKeySize;
|
||||
|
||||
volatile private PGPSecretKey mNewKey;
|
||||
private ProgressDialog mProgressDialog;
|
||||
private Thread mRunningThread = null;
|
||||
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
Bundle data = msg.getData();
|
||||
if (data != null) {
|
||||
boolean closeProgressDialog = data.getBoolean("closeProgressDialog");
|
||||
if (closeProgressDialog) {
|
||||
if (mProgressDialog != null) {
|
||||
mProgressDialog.dismiss();
|
||||
mProgressDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(getContext(),
|
||||
getContext().getString(R.string.errorMessage, error),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
boolean gotNewKey = data.getBoolean("gotNewKey");
|
||||
if (gotNewKey) {
|
||||
KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
|
||||
mEditors, false);
|
||||
view.setEditorListener(SectionView.this);
|
||||
boolean isMasterKey = (mEditors.getChildCount() == 0);
|
||||
view.setValue(mNewKey, isMasterKey, -1);
|
||||
mEditors.addView(view);
|
||||
SectionView.this.updateEditorsVisible();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public SectionView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SectionView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ViewGroup getEditors() {
|
||||
return mEditors;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
mType = type;
|
||||
switch (type) {
|
||||
case Id.type.user_id: {
|
||||
mTitle.setText(R.string.section_userIds);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.type.key: {
|
||||
mTitle.setText(R.string.section_keys);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mAdd = findViewById(R.id.header);
|
||||
mAdd.setOnClickListener(this);
|
||||
|
||||
mEditors = (ViewGroup) findViewById(R.id.editors);
|
||||
mTitle = (TextView) findViewById(R.id.title);
|
||||
|
||||
updateEditorsVisible();
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void onDeleted(Editor editor) {
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
protected void updateEditorsVisible() {
|
||||
final boolean hasChildren = mEditors.getChildCount() > 0;
|
||||
mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void onClick(View v) {
|
||||
switch (mType) {
|
||||
case Id.type.user_id: {
|
||||
UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item,
|
||||
mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
if (mEditors.getChildCount() == 0) {
|
||||
view.setIsMainUserId(true);
|
||||
}
|
||||
mEditors.addView(view);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.type.key: {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
|
||||
|
||||
View view = mInflater.inflate(R.layout.create_key, null);
|
||||
dialog.setView(view);
|
||||
dialog.setTitle(R.string.title_createKey);
|
||||
|
||||
boolean wouldBeMasterKey = (mEditors.getChildCount() == 0);
|
||||
|
||||
final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm);
|
||||
Vector<Choice> choices = new Vector<Choice>();
|
||||
choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString(R.string.dsa)));
|
||||
if (!wouldBeMasterKey) {
|
||||
choices.add(new Choice(Id.choice.algorithm.elgamal, getResources().getString(
|
||||
R.string.elgamal)));
|
||||
}
|
||||
|
||||
choices.add(new Choice(Id.choice.algorithm.rsa, getResources().getString(R.string.rsa)));
|
||||
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
algorithm.setAdapter(adapter);
|
||||
// make RSA the default
|
||||
for (int i = 0; i < choices.size(); ++i) {
|
||||
if (choices.get(i).getId() == Id.choice.algorithm.rsa) {
|
||||
algorithm.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final EditText keySize = (EditText) view.findViewById(R.id.create_key_size);
|
||||
|
||||
dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface di, int id) {
|
||||
di.dismiss();
|
||||
try {
|
||||
mNewKeySize = Integer.parseInt("" + keySize.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
mNewKeySize = 0;
|
||||
}
|
||||
|
||||
mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem();
|
||||
createKey();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.setCancelable(true);
|
||||
dialog.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface di, int id) {
|
||||
di.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.create().show();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
public void setUserIds(Vector<String> list) {
|
||||
if (mType != Id.type.user_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
mEditors.removeAllViews();
|
||||
for (String userId : list) {
|
||||
UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item,
|
||||
mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
view.setValue(userId);
|
||||
if (mEditors.getChildCount() == 0) {
|
||||
view.setIsMainUserId(true);
|
||||
}
|
||||
mEditors.addView(view);
|
||||
}
|
||||
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
public void setKeys(Vector<PGPSecretKey> list, Vector<Integer> usages) {
|
||||
if (mType != Id.type.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
mEditors.removeAllViews();
|
||||
|
||||
// go through all keys and set view based on them
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors,
|
||||
false);
|
||||
view.setEditorListener(this);
|
||||
boolean isMasterKey = (mEditors.getChildCount() == 0);
|
||||
view.setValue(list.get(i), isMasterKey, usages.get(i));
|
||||
mEditors.addView(view);
|
||||
}
|
||||
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
mProgressDialog = new ProgressDialog(getContext());
|
||||
mProgressDialog.setMessage(getContext().getString(R.string.progress_generating));
|
||||
mProgressDialog.setCancelable(false);
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
mProgressDialog.show();
|
||||
mRunningThread = new Thread(this);
|
||||
mRunningThread.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
String error = null;
|
||||
try {
|
||||
PGPSecretKey masterKey = null;
|
||||
String passPhrase;
|
||||
if (mEditors.getChildCount() > 0) {
|
||||
masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
|
||||
passPhrase = Apg.getCachedPassPhrase(masterKey.getKeyID());
|
||||
} else {
|
||||
passPhrase = "";
|
||||
}
|
||||
mNewKey = Apg.createKey(getContext(), mNewKeyAlgorithmChoice.getId(), mNewKeySize,
|
||||
passPhrase, masterKey);
|
||||
} catch (NoSuchProviderException e) {
|
||||
error = "" + e;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
error = "" + e;
|
||||
} catch (PGPException e) {
|
||||
error = "" + e;
|
||||
} catch (InvalidParameterException e) {
|
||||
error = "" + e;
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
|
||||
Message message = new Message();
|
||||
Bundle data = new Bundle();
|
||||
data.putBoolean("closeProgressDialog", true);
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
} else {
|
||||
data.putBoolean("gotNewKey", true);
|
||||
}
|
||||
message.setData(data);
|
||||
mHandler.sendMessage(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* 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.ui.widget;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.provider.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.Keys;
|
||||
import org.thialfihar.android.apg.provider.UserIds;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectPublicKeyListAdapter extends BaseAdapter {
|
||||
protected LayoutInflater mInflater;
|
||||
protected ListView mParent;
|
||||
protected SQLiteDatabase mDatabase;
|
||||
protected Cursor mCursor;
|
||||
protected String mSearchString;
|
||||
protected Activity mActivity;
|
||||
|
||||
public SelectPublicKeyListAdapter(Activity activity, ListView parent, String searchString,
|
||||
long selectedKeyIds[]) {
|
||||
mSearchString = searchString;
|
||||
|
||||
mActivity = activity;
|
||||
mParent = parent;
|
||||
mDatabase = Apg.getDatabase().db();
|
||||
mInflater = (LayoutInflater) parent.getContext().getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
long now = new Date().getTime() / 1000;
|
||||
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') ");
|
||||
|
||||
String inIdList = null;
|
||||
|
||||
if (selectedKeyIds != null && selectedKeyIds.length > 0) {
|
||||
inIdList = KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " IN (";
|
||||
for (int i = 0; i < selectedKeyIds.length; ++i) {
|
||||
if (i != 0) {
|
||||
inIdList += ", ";
|
||||
}
|
||||
inIdList += DatabaseUtils.sqlEscapeString("" + selectedKeyIds[i]);
|
||||
}
|
||||
inIdList += ")";
|
||||
}
|
||||
|
||||
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("))");
|
||||
|
||||
if (inIdList != null) {
|
||||
qb.appendWhere(" OR (" + inIdList + ")");
|
||||
}
|
||||
}
|
||||
|
||||
String orderBy = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC";
|
||||
if (inIdList != null) {
|
||||
orderBy = inIdList + " DESC, " + orderBy;
|
||||
}
|
||||
|
||||
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
|
||||
"(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE "
|
||||
+ "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "."
|
||||
+ KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND "
|
||||
+ "tmp." + Keys.CAN_ENCRYPT + " = '1')", // 3
|
||||
"(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE "
|
||||
+ "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "."
|
||||
+ KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND "
|
||||
+ "tmp." + Keys.CAN_ENCRYPT + " = '1' AND " + "tmp." + Keys.CREATION
|
||||
+ " <= '" + now + "' AND " + "(tmp." + Keys.EXPIRY + " IS NULL OR "
|
||||
+ "tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4
|
||||
}, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { ""
|
||||
+ Id.database.type_public }, null, null, orderBy);
|
||||
|
||||
activity.startManagingCursor(mCursor);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if (mCursor != null) {
|
||||
mActivity.stopManagingCursor(mCursor);
|
||||
mCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getInt(4) > 0; // valid CAN_ENCRYPT
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mCursor.getCount();
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getString(2); // USER_ID
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(1); // MASTER_KEY_ID
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
mCursor.moveToPosition(position);
|
||||
|
||||
View view = mInflater.inflate(R.layout.select_public_key_item, null);
|
||||
boolean enabled = isEnabled(position);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(R.string.noKey);
|
||||
TextView status = (TextView) view.findViewById(R.id.status);
|
||||
status.setText(R.string.unknownStatus);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID
|
||||
keyId.setText(Apg.getSmallFingerPrint(masterKeyId));
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
status.setText(R.string.canEncrypt);
|
||||
} else {
|
||||
if (mCursor.getInt(3) > 0) {
|
||||
// has some CAN_ENCRYPT keys, but col(4) = 0, so must be revoked or expired
|
||||
status.setText(R.string.expired);
|
||||
} else {
|
||||
status.setText(R.string.noKey);
|
||||
}
|
||||
}
|
||||
|
||||
status.setText(status.getText() + " ");
|
||||
|
||||
CheckBox selected = (CheckBox) view.findViewById(R.id.selected);
|
||||
|
||||
if (!enabled) {
|
||||
mParent.setItemChecked(position, false);
|
||||
}
|
||||
|
||||
selected.setChecked(mParent.isItemChecked(position));
|
||||
|
||||
view.setEnabled(enabled);
|
||||
mainUserId.setEnabled(enabled);
|
||||
mainUserIdRest.setEnabled(enabled);
|
||||
keyId.setEnabled(enabled);
|
||||
selected.setEnabled(enabled);
|
||||
status.setEnabled(enabled);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.ui.widget;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.provider.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.Keys;
|
||||
import org.thialfihar.android.apg.provider.UserIds;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectSecretKeyListAdapter extends BaseAdapter {
|
||||
protected LayoutInflater mInflater;
|
||||
protected ListView mParent;
|
||||
protected SQLiteDatabase mDatabase;
|
||||
protected Cursor mCursor;
|
||||
protected String mSearchString;
|
||||
protected Activity mActivity;
|
||||
|
||||
public SelectSecretKeyListAdapter(Activity activity, ListView parent, String searchString) {
|
||||
mSearchString = searchString;
|
||||
|
||||
mActivity = activity;
|
||||
mParent = parent;
|
||||
mDatabase = Apg.getDatabase().db();
|
||||
mInflater = (LayoutInflater) parent.getContext().getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
long now = new Date().getTime() / 1000;
|
||||
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
|
||||
"(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE "
|
||||
+ "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "."
|
||||
+ KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND "
|
||||
+ "tmp." + Keys.CAN_SIGN + " = '1')", // 3,
|
||||
"(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE "
|
||||
+ "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "."
|
||||
+ KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND "
|
||||
+ "tmp." + Keys.CAN_SIGN + " = '1' AND " + "tmp." + Keys.CREATION + " <= '"
|
||||
+ now + "' AND " + "(tmp." + Keys.EXPIRY + " IS NULL OR " + "tmp."
|
||||
+ Keys.EXPIRY + " >= '" + now + "'))", // 4
|
||||
}, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { ""
|
||||
+ Id.database.type_secret }, null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID
|
||||
+ " ASC");
|
||||
|
||||
activity.startManagingCursor(mCursor);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if (mCursor != null) {
|
||||
mActivity.stopManagingCursor(mCursor);
|
||||
mCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getInt(4) > 0; // valid CAN_SIGN
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mCursor.getCount();
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getString(2); // USER_ID
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(1); // MASTER_KEY_ID
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
mCursor.moveToPosition(position);
|
||||
|
||||
View view = mInflater.inflate(R.layout.select_secret_key_item, null);
|
||||
boolean enabled = isEnabled(position);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(R.string.noKey);
|
||||
TextView status = (TextView) view.findViewById(R.id.status);
|
||||
status.setText(R.string.unknownStatus);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID
|
||||
keyId.setText(Apg.getSmallFingerPrint(masterKeyId));
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
status.setText(R.string.canSign);
|
||||
} else {
|
||||
if (mCursor.getInt(3) > 0) {
|
||||
// has some CAN_SIGN keys, but col(4) = 0, so must be revoked or expired
|
||||
status.setText(R.string.expired);
|
||||
} else {
|
||||
status.setText(R.string.noKey);
|
||||
}
|
||||
}
|
||||
|
||||
status.setText(status.getText() + " ");
|
||||
|
||||
view.setEnabled(enabled);
|
||||
mainUserId.setEnabled(enabled);
|
||||
mainUserIdRest.setEnabled(enabled);
|
||||
keyId.setEnabled(enabled);
|
||||
status.setEnabled(enabled);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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.ui.widget;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
|
||||
public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private EditorListener mEditorListener = null;
|
||||
|
||||
private ImageButton mDeleteButton;
|
||||
private RadioButton mIsMainUserId;
|
||||
private EditText mName;
|
||||
private EditText mEmail;
|
||||
private EditText mComment;
|
||||
|
||||
// see http://www.regular-expressions.info/email.html
|
||||
// RFC 2822 if we omit the syntax using double quotes and square brackets
|
||||
private static final Pattern EMAIL_PATTERN = Pattern
|
||||
.compile(
|
||||
"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public static class NoNameException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773343L;
|
||||
|
||||
public NoNameException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NoEmailException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773344L;
|
||||
|
||||
public NoEmailException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidEmailException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773345L;
|
||||
|
||||
public InvalidEmailException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public UserIdEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public UserIdEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mDeleteButton = (ImageButton) findViewById(R.id.delete);
|
||||
mDeleteButton.setOnClickListener(this);
|
||||
mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId);
|
||||
mIsMainUserId.setOnClickListener(this);
|
||||
|
||||
mName = (EditText) findViewById(R.id.name);
|
||||
mEmail = (EditText) findViewById(R.id.email);
|
||||
mComment = (EditText) findViewById(R.id.comment);
|
||||
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setValue(String userId) {
|
||||
mName.setText("");
|
||||
mComment.setText("");
|
||||
mEmail.setText("");
|
||||
|
||||
Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$");
|
||||
Matcher matcher = withComment.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
mName.setText(matcher.group(1));
|
||||
mComment.setText(matcher.group(2));
|
||||
mEmail.setText(matcher.group(3));
|
||||
return;
|
||||
}
|
||||
|
||||
Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$");
|
||||
matcher = withoutComment.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
mName.setText(matcher.group(1));
|
||||
mEmail.setText(matcher.group(2));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public String getValue() throws NoNameException, NoEmailException, InvalidEmailException {
|
||||
String name = ("" + mName.getText()).trim();
|
||||
String email = ("" + mEmail.getText()).trim();
|
||||
String comment = ("" + mComment.getText()).trim();
|
||||
|
||||
if (email.length() > 0) {
|
||||
Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
|
||||
if (!emailMatcher.matches()) {
|
||||
throw new InvalidEmailException(getContext().getString(R.string.error_invalidEmail,
|
||||
email));
|
||||
}
|
||||
}
|
||||
|
||||
String userId = name;
|
||||
if (comment.length() > 0) {
|
||||
userId += " (" + comment + ")";
|
||||
}
|
||||
if (email.length() > 0) {
|
||||
userId += " <" + email + ">";
|
||||
}
|
||||
|
||||
if (userId.equals("")) {
|
||||
// ok, empty one...
|
||||
return userId;
|
||||
}
|
||||
|
||||
// otherwise make sure that name and email exist
|
||||
if (name.equals("")) {
|
||||
throw new NoNameException("need a name");
|
||||
}
|
||||
|
||||
if (email.equals("")) {
|
||||
throw new NoEmailException("need an email");
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
final ViewGroup parent = (ViewGroup) getParent();
|
||||
if (v == mDeleteButton) {
|
||||
boolean wasMainUserId = mIsMainUserId.isChecked();
|
||||
parent.removeView(this);
|
||||
if (mEditorListener != null) {
|
||||
mEditorListener.onDeleted(this);
|
||||
}
|
||||
if (wasMainUserId && parent.getChildCount() > 0) {
|
||||
UserIdEditor editor = (UserIdEditor) parent.getChildAt(0);
|
||||
editor.setIsMainUserId(true);
|
||||
}
|
||||
} else if (v == mIsMainUserId) {
|
||||
for (int i = 0; i < parent.getChildCount(); ++i) {
|
||||
UserIdEditor editor = (UserIdEditor) parent.getChildAt(i);
|
||||
if (editor == this) {
|
||||
editor.setIsMainUserId(true);
|
||||
} else {
|
||||
editor.setIsMainUserId(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsMainUserId(boolean value) {
|
||||
mIsMainUserId.setChecked(value);
|
||||
}
|
||||
|
||||
public boolean isMainUserId() {
|
||||
return mIsMainUserId.isChecked();
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener) {
|
||||
mEditorListener = listener;
|
||||
}
|
||||
}
|
||||
836
org_apg/src/org/thialfihar/android/apg/util/ApgCon.java
Normal file
836
org_apg/src/org/thialfihar/android/apg/util/ApgCon.java
Normal file
@@ -0,0 +1,836 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Markus Doits <markus.doits@googlemail.com>
|
||||
*
|
||||
* 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.util;
|
||||
|
||||
import org.thialfihar.android.apg.IApgService;
|
||||
import org.thialfihar.android.apg.util.ApgConInterface.OnCallFinishListener;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A APG-AIDL-Wrapper
|
||||
*
|
||||
* <p>
|
||||
* This class can be used by other projects to simplify connecting to the
|
||||
* APG-AIDL-Service. Kind of wrapper of for AIDL.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It is not used in this project.
|
||||
* </p>
|
||||
*
|
||||
* @author Markus Doits <markus.doits@googlemail.com>
|
||||
* @version 1.1rc1
|
||||
*
|
||||
*/
|
||||
public class ApgCon {
|
||||
private static final boolean LOCAL_LOGV = true;
|
||||
private static final boolean LOCAL_LOGD = true;
|
||||
|
||||
private final static String TAG = "ApgCon";
|
||||
private final static int API_VERSION = 2; // aidl api-version it expects
|
||||
private final static String BLOB_URI = "content://org.thialfihar.android.apg.provider.apgserviceblobprovider";
|
||||
|
||||
/**
|
||||
* How many seconds to wait for a connection to AGP when connecting.
|
||||
* Being unsuccessful for this number of seconds, a connection
|
||||
* is assumed to be failed.
|
||||
*/
|
||||
public int secondsToWaitForConnection = 15;
|
||||
|
||||
private class CallAsync extends AsyncTask<String, Void, Void> {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(String... arg) {
|
||||
if( LOCAL_LOGD ) Log.d(TAG, "Async execution starting");
|
||||
call(arg[0]);
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Void res) {
|
||||
if( LOCAL_LOGD ) Log.d(TAG, "Async execution finished");
|
||||
mAsyncRunning = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
private final error mConnectionStatus;
|
||||
private boolean mAsyncRunning = false;
|
||||
private OnCallFinishListener mOnCallFinishListener;
|
||||
|
||||
private final Bundle mResult = new Bundle();
|
||||
private final Bundle mArgs = new Bundle();
|
||||
private final ArrayList<String> mErrorList = new ArrayList<String>();
|
||||
private final ArrayList<String> mWarningList = new ArrayList<String>();
|
||||
|
||||
/** Remote service for decrypting and encrypting data */
|
||||
private IApgService mApgService = null;
|
||||
|
||||
/** Set apgService accordingly to connection status */
|
||||
private ServiceConnection mApgConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
if( LOCAL_LOGD ) Log.d(TAG, "IApgService bound to apgService");
|
||||
mApgService = IApgService.Stub.asInterface(service);
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
if( LOCAL_LOGD ) Log.d(TAG, "IApgService disconnected");
|
||||
mApgService = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Different types of local errors
|
||||
*/
|
||||
public static enum error {
|
||||
/**
|
||||
* no error
|
||||
*/
|
||||
NO_ERROR,
|
||||
/**
|
||||
* generic error
|
||||
*/
|
||||
GENERIC,
|
||||
/**
|
||||
* connection to apg service not possible
|
||||
*/
|
||||
CANNOT_BIND_TO_APG,
|
||||
/**
|
||||
* function to call not provided
|
||||
*/
|
||||
CALL_MISSING,
|
||||
/**
|
||||
* apg service does not know what to do
|
||||
*/
|
||||
CALL_NOT_KNOWN,
|
||||
/**
|
||||
* could not find APG being installed
|
||||
*/
|
||||
APG_NOT_FOUND,
|
||||
/**
|
||||
* found APG but without AIDL interface
|
||||
*/
|
||||
APG_AIDL_MISSING,
|
||||
/**
|
||||
* found APG but with wrong API
|
||||
*/
|
||||
APG_API_MISSMATCH
|
||||
}
|
||||
|
||||
private static enum ret {
|
||||
ERROR, // returned from AIDL
|
||||
RESULT, // returned from AIDL
|
||||
WARNINGS, // mixed AIDL and LOCAL
|
||||
ERRORS, // mixed AIDL and LOCAL
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* <p>
|
||||
* Creates a new ApgCon object and searches for the right APG version on
|
||||
* initialization. If not found, errors are printed to the error log.
|
||||
* </p>
|
||||
*
|
||||
* @param ctx
|
||||
* the running context
|
||||
*/
|
||||
public ApgCon(Context ctx) {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "EncryptionService created");
|
||||
mContext = ctx;
|
||||
|
||||
error tmpError = null;
|
||||
try {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "Searching for the right APG version");
|
||||
ServiceInfo apgServices[] = ctx.getPackageManager().getPackageInfo("org.thialfihar.android.apg",
|
||||
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA).services;
|
||||
if (apgServices == null) {
|
||||
Log.e(TAG, "Could not fetch services");
|
||||
tmpError = error.GENERIC;
|
||||
} else {
|
||||
boolean apgServiceFound = false;
|
||||
for (ServiceInfo inf : apgServices) {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "Found service of APG: " + inf.name);
|
||||
if (inf.name.equals("org.thialfihar.android.apg.ApgService")) {
|
||||
apgServiceFound = true;
|
||||
if (inf.metaData == null) {
|
||||
Log.w(TAG, "Could not determine ApgService API");
|
||||
Log.w(TAG, "This probably won't work!");
|
||||
mWarningList.add("(LOCAL) Could not determine ApgService API");
|
||||
tmpError = error.APG_API_MISSMATCH;
|
||||
} else if (inf.metaData.getInt("api_version") != API_VERSION) {
|
||||
Log.w(TAG, "Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
|
||||
Log.w(TAG, "This probably won't work!");
|
||||
mWarningList.add("(LOCAL) Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
|
||||
tmpError = error.APG_API_MISSMATCH;
|
||||
} else {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "Found api_version " + API_VERSION + ", everything should work");
|
||||
tmpError = error.NO_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!apgServiceFound) {
|
||||
Log.e(TAG, "Could not find APG with AIDL interface, this probably won't work");
|
||||
mErrorList.add("(LOCAL) Could not find APG with AIDL interface, this probably won't work");
|
||||
mResult.putInt(ret.ERROR.name(), error.APG_AIDL_MISSING.ordinal());
|
||||
tmpError = error.APG_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "Could not find APG, is it installed?", e);
|
||||
mErrorList.add("(LOCAL) Could not find APG, is it installed?");
|
||||
mResult.putInt(ret.ERROR.name(), error.APG_NOT_FOUND.ordinal());
|
||||
tmpError = error.APG_NOT_FOUND;
|
||||
}
|
||||
|
||||
mConnectionStatus = tmpError;
|
||||
|
||||
}
|
||||
|
||||
/** try to connect to the apg service */
|
||||
private boolean connect() {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "trying to bind the apgService to context");
|
||||
|
||||
if (mApgService != null) {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "allready connected");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
mContext.bindService(new Intent(IApgService.class.getName()), mApgConnection, Context.BIND_AUTO_CREATE);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "could not bind APG service", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
int waitCount = 0;
|
||||
while (mApgService == null && waitCount++ < secondsToWaitForConnection) {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "sleeping 1 second to wait for apg");
|
||||
android.os.SystemClock.sleep(1000);
|
||||
}
|
||||
|
||||
if (waitCount >= secondsToWaitForConnection) {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "slept waiting for nothing!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects ApgCon from Apg
|
||||
*
|
||||
* <p>
|
||||
* This should be called whenever all work with APG is done (e.g. everything
|
||||
* you wanted to encrypt is encrypted), since connections with AIDL should
|
||||
* not be upheld indefinitely.
|
||||
* <p>
|
||||
*
|
||||
* <p>
|
||||
* Also, if you destroy you end using your ApgCon-instance, this must be
|
||||
* called or else the connection to APG is leaked
|
||||
* </p>
|
||||
*/
|
||||
public void disconnect() {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "disconnecting apgService");
|
||||
if (mApgService != null) {
|
||||
mContext.unbindService(mApgConnection);
|
||||
mApgService = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean initialize() {
|
||||
if (mApgService == null) {
|
||||
if (!connect()) {
|
||||
if( LOCAL_LOGV ) Log.v(TAG, "connection to apg service failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function from APG's AIDL-interface
|
||||
*
|
||||
* <p>
|
||||
* After you have set up everything with {@link #setArg(String, String)}
|
||||
* (and variants), you can call a function of the AIDL-interface. This
|
||||
* will:
|
||||
* <ul>
|
||||
* <li>start connection to the remote interface (if not already connected)</li>
|
||||
* <li>call the function passed with all parameters synchronously</li>
|
||||
* <li>set up everything to retrieve the result and/or warnings/errors</li>
|
||||
* <li>call the callback if provided
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note your thread will be blocked during execution - if you want to call
|
||||
* the function asynchronously, see {@link #callAsync(String)}.
|
||||
* </p>
|
||||
*
|
||||
* @param function
|
||||
* a remote function to call
|
||||
* @return true, if call successful (= no errors), else false
|
||||
*
|
||||
* @see #callAsync(String)
|
||||
* @see #setArg(String, String)
|
||||
* @see #setOnCallFinishListener(OnCallFinishListener)
|
||||
*/
|
||||
public boolean call(String function) {
|
||||
boolean success = this.call(function, mArgs, mResult);
|
||||
if (mOnCallFinishListener != null) {
|
||||
try {
|
||||
if( LOCAL_LOGD ) Log.d(TAG, "About to execute callback");
|
||||
mOnCallFinishListener.onCallFinish(mResult);
|
||||
if( LOCAL_LOGD ) Log.d(TAG, "Callback executed");
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Exception on callback: (" + e.getClass() + ") " + e.getMessage(), e);
|
||||
mWarningList.add("(LOCAL) Could not execute callback (" + e.getClass() + "): " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function of remote interface asynchronously
|
||||
*
|
||||
* <p>
|
||||
* This does exactly the same as {@link #call(String)}, but asynchronously.
|
||||
* While connection to APG and work are done in background, your thread can
|
||||
* go on executing.
|
||||
* <p>
|
||||
*
|
||||
* <p>
|
||||
* To see whether the task is finished, you have two possibilities:
|
||||
* <ul>
|
||||
* <li>In your thread, poll {@link #isRunning()}</li>
|
||||
* <li>Supply a callback with {@link #setOnCallFinishListener(OnCallFinishListener)}</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param function
|
||||
* a remote function to call
|
||||
*
|
||||
* @see #call(String)
|
||||
* @see #isRunning()
|
||||
* @see #setOnCallFinishListener(OnCallFinishListener)
|
||||
*/
|
||||
public void callAsync(String function) {
|
||||
mAsyncRunning = true;
|
||||
new CallAsync().execute(function);
|
||||
}
|
||||
|
||||
private boolean call(String function, Bundle pArgs, Bundle pReturn) {
|
||||
|
||||
if (!initialize()) {
|
||||
mErrorList.add("(LOCAL) Cannot bind to ApgService");
|
||||
mResult.putInt(ret.ERROR.name(), error.CANNOT_BIND_TO_APG.ordinal());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (function == null || function.length() == 0) {
|
||||
mErrorList.add("(LOCAL) Function to call missing");
|
||||
mResult.putInt(ret.ERROR.name(), error.CALL_MISSING.ordinal());
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Boolean success = (Boolean) IApgService.class.getMethod(function, Bundle.class, Bundle.class).invoke(mApgService, pArgs, pReturn);
|
||||
mErrorList.addAll(pReturn.getStringArrayList(ret.ERRORS.name()));
|
||||
mWarningList.addAll(pReturn.getStringArrayList(ret.WARNINGS.name()));
|
||||
return success;
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e(TAG, "Remote call not known (" + function + "): " + e.getMessage(), e);
|
||||
mErrorList.add("(LOCAL) Remote call not known (" + function + "): " + e.getMessage());
|
||||
mResult.putInt(ret.ERROR.name(), error.CALL_NOT_KNOWN.ordinal());
|
||||
return false;
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable orig = e.getTargetException();
|
||||
Log.w(TAG, "Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage(), orig);
|
||||
mErrorList.add("(LOCAL) Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage());
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Generic error (" + e.getClass() + "): " + e.getMessage(), e);
|
||||
mErrorList.add("(LOCAL) Generic error (" + e.getClass() + "): " + e.getMessage());
|
||||
mResult.putInt(ret.ERROR.name(), error.GENERIC.ordinal());
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a string argument for APG
|
||||
*
|
||||
* <p>
|
||||
* This defines a string argument for APG's AIDL-interface.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* To know what key-value-pairs are possible (or required), take a look into
|
||||
* the IApgService.aidl
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note that parameters are not reseted after a call, so you have to
|
||||
* reset ({@link #clearArgs()}) them manually if you want to.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param key
|
||||
* the key
|
||||
* @param val
|
||||
* the value
|
||||
*
|
||||
* @see #clearArgs()
|
||||
*/
|
||||
public void setArg(String key, String val) {
|
||||
mArgs.putString(key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a string-array argument for APG
|
||||
*
|
||||
* <p>
|
||||
* If the AIDL-parameter is an {@literal ArrayList<String>}, you have to use
|
||||
* this function.
|
||||
* </p>
|
||||
*
|
||||
* <code>
|
||||
* <pre>
|
||||
* setArg("a key", new String[]{ "entry 1", "entry 2" });
|
||||
* </pre>
|
||||
* </code>
|
||||
*
|
||||
* @param key
|
||||
* the key
|
||||
* @param vals
|
||||
* the value
|
||||
*
|
||||
* @see #setArg(String, String)
|
||||
*/
|
||||
public void setArg(String key, String vals[]) {
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
for (String val : vals) {
|
||||
list.add(val);
|
||||
}
|
||||
mArgs.putStringArrayList(key, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a boolean argument for APG
|
||||
*
|
||||
* @param key
|
||||
* the key
|
||||
* @param vals
|
||||
* the value
|
||||
*
|
||||
* @see #setArg(String, String)
|
||||
*/
|
||||
public void setArg(String key, boolean val) {
|
||||
mArgs.putBoolean(key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a int argument for APG
|
||||
*
|
||||
* @param key
|
||||
* the key
|
||||
* @param vals
|
||||
* the value
|
||||
*
|
||||
* @see #setArg(String, String)
|
||||
*/
|
||||
public void setArg(String key, int val) {
|
||||
mArgs.putInt(key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a int-array argument for APG
|
||||
* <p>
|
||||
* If the AIDL-parameter is an {@literal ArrayList<Integer>}, you have to
|
||||
* use this function.
|
||||
* </p>
|
||||
*
|
||||
* @param key
|
||||
* the key
|
||||
* @param vals
|
||||
* the value
|
||||
*
|
||||
* @see #setArg(String, String)
|
||||
*/
|
||||
public void setArg(String key, int vals[]) {
|
||||
ArrayList<Integer> list = new ArrayList<Integer>();
|
||||
for (int val : vals) {
|
||||
list.add(val);
|
||||
}
|
||||
mArgs.putIntegerArrayList(key, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up binary data to en/decrypt
|
||||
*
|
||||
* @param is
|
||||
* InputStream to get the data from
|
||||
*/
|
||||
public void setBlob(InputStream is) {
|
||||
if( LOCAL_LOGD ) Log.d(TAG, "setBlob() called");
|
||||
// 1. get the new contentUri
|
||||
ContentResolver cr = mContext.getContentResolver();
|
||||
Uri contentUri = cr.insert(Uri.parse(BLOB_URI), new ContentValues());
|
||||
|
||||
// 2. insert binary data
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = cr.openOutputStream(contentUri, "w");
|
||||
} catch( Exception e ) {
|
||||
Log.e(TAG, "... exception on setBlob", e);
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[8];
|
||||
int len = 0;
|
||||
try {
|
||||
while( (len = is.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, len);
|
||||
}
|
||||
if(LOCAL_LOGD) Log.d(TAG, "... write finished, now closing");
|
||||
os.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "... error on writing buffer", e);
|
||||
}
|
||||
|
||||
mArgs.putString("BLOB", contentUri.toString() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all arguments
|
||||
*
|
||||
* <p>
|
||||
* Anything the has been set up with the various
|
||||
* {@link #setArg(String, String)} functions is cleared.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note that any warning, error, callback, result, etc. is NOT cleared with
|
||||
* this.
|
||||
* </p>
|
||||
*
|
||||
* @see #reset()
|
||||
*/
|
||||
public void clearArgs() {
|
||||
mArgs.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object associated with the key
|
||||
*
|
||||
* @param key
|
||||
* the object's key you want to return
|
||||
* @return an object at position key, or null if not set
|
||||
*/
|
||||
public Object getArg(String key) {
|
||||
return mArgs.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the errors
|
||||
*
|
||||
* <p>
|
||||
* With this method you can iterate through all errors. The errors are only
|
||||
* returned once and deleted immediately afterwards, so you can only return
|
||||
* each error once.
|
||||
* </p>
|
||||
*
|
||||
* @return a human readable description of a error that happened, or null if
|
||||
* no more errors
|
||||
*
|
||||
* @see #hasNextError()
|
||||
* @see #clearErrors()
|
||||
*/
|
||||
public String getNextError() {
|
||||
if (mErrorList.size() != 0)
|
||||
return mErrorList.remove(0);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any new errors
|
||||
*
|
||||
* @return true, if there are unreturned errors, false otherwise
|
||||
*
|
||||
* @see #getNextError()
|
||||
*/
|
||||
public boolean hasNextError() {
|
||||
return mErrorList.size() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the numeric representation of the last error
|
||||
*
|
||||
* <p>
|
||||
* Values <100 mean the error happened locally, values >=100 mean the error
|
||||
* happened at the remote side (APG). See the IApgService.aidl (or get the
|
||||
* human readable description with {@link #getNextError()}) for what
|
||||
* errors >=100 mean.
|
||||
* </p>
|
||||
*
|
||||
* @return the id of the error that happened
|
||||
*/
|
||||
public int getError() {
|
||||
if (mResult.containsKey(ret.ERROR.name()))
|
||||
return mResult.getInt(ret.ERROR.name());
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the warnings
|
||||
*
|
||||
* <p>
|
||||
* With this method you can iterate through all warnings. Warnings are
|
||||
* only returned once and deleted immediately afterwards, so you can only
|
||||
* return each warning once.
|
||||
* </p>
|
||||
*
|
||||
* @return a human readable description of a warning that happened, or null
|
||||
* if no more warnings
|
||||
*
|
||||
* @see #hasNextWarning()
|
||||
* @see #clearWarnings()
|
||||
*/
|
||||
public String getNextWarning() {
|
||||
if (mWarningList.size() != 0)
|
||||
return mWarningList.remove(0);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any new warnings
|
||||
*
|
||||
* @return true, if there are unreturned warnings, false otherwise
|
||||
*
|
||||
* @see #getNextWarning()
|
||||
*/
|
||||
public boolean hasNextWarning() {
|
||||
return mWarningList.size() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result
|
||||
*
|
||||
* <p>
|
||||
* This gets your result. After doing an encryption or decryption with APG,
|
||||
* you get the output with this function.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note when your last remote call is unsuccessful, the result will
|
||||
* still have the same value like the last successful call (or null, if no
|
||||
* call was successful). To ensure you do not work with old call's results,
|
||||
* either be sure to {@link #reset()} (or at least {@link #clearResult()})
|
||||
* your instance before each new call or always check that
|
||||
* {@link #hasNextError()} is false.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note: When handling binary data with {@link #setBlob(InputStream)}, you
|
||||
* get your result with {@link #getBlobResult()}.
|
||||
* </p>
|
||||
*
|
||||
* @return the mResult of the last {@link #call(String)} or
|
||||
* {@link #callAsync(String)}.
|
||||
*
|
||||
* @see #reset()
|
||||
* @see #clearResult()
|
||||
* @see #getResultBundle()
|
||||
* @see #getBlobResult()
|
||||
*/
|
||||
public String getResult() {
|
||||
return mResult.getString(ret.RESULT.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the binary result
|
||||
*
|
||||
* <p>
|
||||
* This gets your binary result. It only works if you called {@link #setBlob(InputStream)} before.
|
||||
*
|
||||
* If you did not call encrypt nor decrypt, this will be the same data as you inputed.
|
||||
* </p>
|
||||
*
|
||||
* @return InputStream of the binary data which was en/decrypted
|
||||
*
|
||||
* @see #setBlob(InputStream)
|
||||
* @see #getResult()
|
||||
*/
|
||||
public InputStream getBlobResult() {
|
||||
if(mArgs.containsKey("BLOB")) {
|
||||
ContentResolver cr = mContext.getContentResolver();
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = cr.openInputStream(Uri.parse(mArgs.getString("BLOB")));
|
||||
} catch( Exception e ) {
|
||||
Log.e(TAG, "Could not return blob in result", e);
|
||||
}
|
||||
return in;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result bundle
|
||||
*
|
||||
* <p>
|
||||
* Unlike {@link #getResult()}, which only returns any en-/decrypted
|
||||
* message, this function returns the complete information that was returned
|
||||
* by Apg. This also includes the "RESULT", but additionally the warnings,
|
||||
* errors and any other information.
|
||||
* </p>
|
||||
* <p>
|
||||
* For warnings and errors it is suggested to use the functions that are
|
||||
* provided here, namely {@link #getError()}, {@link #getNextError()},
|
||||
* {@link #get_next_Warning()} etc.), but if any call returns something non
|
||||
* standard, you have access to the complete result bundle to extract the
|
||||
* information.
|
||||
* </p>
|
||||
*
|
||||
* @return the complete result bundle of the last call to apg
|
||||
*/
|
||||
public Bundle getResultBundle() {
|
||||
return mResult;
|
||||
}
|
||||
|
||||
public error getConnectionStatus() {
|
||||
return mConnectionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all unfetched errors
|
||||
*
|
||||
* @see #getNextError()
|
||||
* @see #hasNextError()
|
||||
*/
|
||||
public void clearErrors() {
|
||||
mErrorList.clear();
|
||||
mResult.remove(ret.ERROR.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all unfetched warnings
|
||||
*
|
||||
* @see #getNextWarning()
|
||||
* @see #hasNextWarning()
|
||||
*/
|
||||
public void clearWarnings() {
|
||||
mWarningList.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the last mResult
|
||||
*
|
||||
* @see #getResult()
|
||||
*/
|
||||
public void clearResult() {
|
||||
mResult.remove(ret.RESULT.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback listener when call to AIDL finishes
|
||||
*
|
||||
* @param obj
|
||||
* a object to call back after async execution
|
||||
* @see ApgConInterface
|
||||
*/
|
||||
public void setOnCallFinishListener(OnCallFinishListener lis) {
|
||||
mOnCallFinishListener = lis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any callback object
|
||||
*
|
||||
* @see #setOnCallFinishListener(OnCallFinishListener)
|
||||
*/
|
||||
public void clearOnCallFinishListener() {
|
||||
mOnCallFinishListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an async execution is running
|
||||
*
|
||||
* <p>
|
||||
* If you started something with {@link #callAsync(String)}, this will
|
||||
* return true if the task is still running
|
||||
* </p>
|
||||
*
|
||||
* @return true, if an async task is still running, false otherwise
|
||||
*
|
||||
* @see #callAsync(String)
|
||||
*
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return mAsyncRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely resets your instance
|
||||
*
|
||||
* <p>
|
||||
* This currently resets everything in this instance. Errors, warnings,
|
||||
* results, callbacks, ... are removed. Any connection to the remote
|
||||
* interface is upheld, though.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note when an async execution ({@link #callAsync(String)}) is
|
||||
* running, it's result, warnings etc. will still be evaluated (which might
|
||||
* be not what you want). Also mind that any callback you set is also
|
||||
* reseted, so when finishing the execution any before defined callback will
|
||||
* NOT BE TRIGGERED.
|
||||
* </p>
|
||||
*/
|
||||
public void reset() {
|
||||
clearErrors();
|
||||
clearWarnings();
|
||||
clearArgs();
|
||||
clearOnCallFinishListener();
|
||||
mResult.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
public interface ApgConInterface {
|
||||
public static interface OnCallFinishListener {
|
||||
public abstract void onCallFinish(android.os.Bundle result);
|
||||
}
|
||||
}
|
||||
45
org_apg/src/org/thialfihar/android/apg/util/Choice.java
Normal file
45
org_apg/src/org/thialfihar/android/apg/util/Choice.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
public class Choice {
|
||||
private String mName;
|
||||
private int mId;
|
||||
|
||||
public Choice() {
|
||||
mId = -1;
|
||||
mName = "";
|
||||
}
|
||||
|
||||
public Choice(int id, String name) {
|
||||
mId = id;
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.thialfihar.android.apg.util;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
public class Compatibility {
|
||||
|
||||
private static final String clipboardLabel = "APG";
|
||||
|
||||
/**
|
||||
* Wrapper around ClipboardManager based on Android version using Reflection API, from
|
||||
* http://www.projectsexception.com/blog/?p=87
|
||||
*
|
||||
* @param context
|
||||
* @param text
|
||||
*/
|
||||
public static void copyToClipboard(Context context, String text) {
|
||||
Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
try {
|
||||
if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) {
|
||||
Method method = clipboard.getClass().getMethod("setText", CharSequence.class);
|
||||
method.invoke(clipboard, text);
|
||||
} else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) {
|
||||
Class<?> clazz = Class.forName("android.content.ClipData");
|
||||
Method method = clazz.getMethod("newPlainText", CharSequence.class,
|
||||
CharSequence.class);
|
||||
Object clip = method.invoke(null, clipboardLabel, text);
|
||||
method = clipboard.getClass().getMethod("setPrimaryClip", clazz);
|
||||
method.invoke(clipboard, clip);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ProjectsException", "There was and error copying the text to the clipboard: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around ClipboardManager based on Android version using Reflection API
|
||||
*
|
||||
* @param context
|
||||
* @param text
|
||||
*/
|
||||
public static CharSequence getClipboardText(Context context) {
|
||||
Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
try {
|
||||
if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) {
|
||||
// CharSequence text = clipboard.getText();
|
||||
Method method = clipboard.getClass().getMethod("getText");
|
||||
Object text = method.invoke(clipboard);
|
||||
|
||||
return (CharSequence) text;
|
||||
} else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) {
|
||||
// ClipData clipData = clipboard.getPrimaryClip();
|
||||
Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip");
|
||||
Object clipData = methodGetPrimaryClip.invoke(clipboard);
|
||||
|
||||
// ClipData.Item clipDataItem = clipData.getItemAt(0);
|
||||
Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", Integer.TYPE);
|
||||
Object clipDataItem = methodGetItemAt.invoke(clipData, 0);
|
||||
|
||||
// CharSequence text = clipDataItem.coerceToText(context);
|
||||
Method methodGetString = clipDataItem.getClass().getMethod("coerceToText",
|
||||
Context.class);
|
||||
Object text = methodGetString.invoke(clipDataItem, context);
|
||||
|
||||
return (CharSequence) text;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ProjectsException", "There was and error getting the text from the clipboard: "
|
||||
+ e.getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.thialfihar.android.apg.util;
|
||||
|
||||
public class Constants {
|
||||
public static final String TAG = "APG";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class IterableIterator<T> implements Iterable<T> {
|
||||
private Iterator<T> mIter;
|
||||
|
||||
public IterableIterator(Iterator<T> iter) {
|
||||
mIter = iter;
|
||||
}
|
||||
|
||||
public Iterator<T> iterator() {
|
||||
return mIter;
|
||||
}
|
||||
}
|
||||
75
org_apg/src/org/thialfihar/android/apg/util/Utils.java
Normal file
75
org_apg/src/org/thialfihar/android/apg/util/Utils.java
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.thialfihar.android.apg.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class Utils {
|
||||
|
||||
/**
|
||||
* Reads html files from /res/raw/example.html to output them as string. See
|
||||
* http://www.monocube.com/2011/02/08/android-tutorial-html-file-in-webview/
|
||||
*
|
||||
* @param context
|
||||
* current context
|
||||
* @param resourceID
|
||||
* of html file to read
|
||||
* @return content of html file with formatting
|
||||
*/
|
||||
public static String readContentFromResource(Context context, int resourceID) {
|
||||
InputStream raw = context.getResources().openRawResource(resourceID);
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
int i;
|
||||
try {
|
||||
i = raw.read();
|
||||
while (i != -1) {
|
||||
stream.write(i);
|
||||
i = raw.read();
|
||||
}
|
||||
raw.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return stream.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number if days between two dates
|
||||
*
|
||||
* @param first
|
||||
* @param second
|
||||
* @return number of days
|
||||
*/
|
||||
public static long getNumDaysBetween(GregorianCalendar first, GregorianCalendar second) {
|
||||
GregorianCalendar tmp = new GregorianCalendar();
|
||||
tmp.setTime(first.getTime());
|
||||
long numDays = (second.getTimeInMillis() - first.getTimeInMillis()) / 1000 / 86400;
|
||||
tmp.add(Calendar.DAY_OF_MONTH, (int) numDays);
|
||||
while (tmp.before(second)) {
|
||||
tmp.add(Calendar.DAY_OF_MONTH, 1);
|
||||
++numDays;
|
||||
}
|
||||
return numDays;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user