rename main folders
This commit is contained in:
48
APG/src/org/thialfihar/android/apg/ApgApplication.java
Normal file
48
APG/src/org/thialfihar/android/apg/ApgApplication.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.Security;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Environment;
|
||||
|
||||
public class ApgApplication extends Application {
|
||||
|
||||
static {
|
||||
// Define Java Security Provider to be Bouncy Castle
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// Create APG directory on sdcard if not existing
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
66
APG/src/org/thialfihar/android/apg/Constants.java
Normal file
66
APG/src/org/thialfihar/android/apg/Constants.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 boolean DEBUG = true;
|
||||
|
||||
public static final String TAG = "APG";
|
||||
|
||||
public static final String PACKAGE_NAME = "org.thialfihar.android.apg";
|
||||
|
||||
public static final String PERMISSION_ACCESS_KEY_DATABASE = PACKAGE_NAME
|
||||
+ ".permission.ACCESS_KEY_DATABASE";
|
||||
public static final String PERMISSION_ACCESS_API = PACKAGE_NAME + ".permission.ACCESS_API";
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
*
|
||||
* this would require patching K9Mail
|
||||
*
|
||||
* better naming scheme would be:
|
||||
*
|
||||
* - x.action.DECRYPT (with action as preferred in Android)
|
||||
*
|
||||
* - even better and shorter (without .android.): org.apg.action.DECRYPT
|
||||
*/
|
||||
public static final String INTENT_PREFIX = PACKAGE_NAME + ".intent.";
|
||||
|
||||
public static final class path {
|
||||
public static final String APP_DIR = Environment.getExternalStorageDirectory() + "/APG";
|
||||
}
|
||||
|
||||
public static final class pref {
|
||||
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";
|
||||
}
|
||||
|
||||
}
|
||||
228
APG/src/org/thialfihar/android/apg/Id.java
Normal file
228
APG/src/org/thialfihar/android/apg/Id.java
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
*
|
||||
* TODO:
|
||||
*
|
||||
* - refactor ids, some are not needed and can be done with xml
|
||||
*
|
||||
*/
|
||||
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_qr_code = 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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// use only lower 16 bits due to compatibility lib
|
||||
public static final class message {
|
||||
public static final int progress_update = 0x00006001;
|
||||
public static final int done = 0x00006002;
|
||||
public static final int import_keys = 0x00006003;
|
||||
public static final int export_keys = 0x00006004;
|
||||
public static final int import_done = 0x00006005;
|
||||
public static final int export_done = 0x00006006;
|
||||
public static final int create_key = 0x00006007;
|
||||
public static final int edit_key = 0x00006008;
|
||||
public static final int delete_done = 0x00006009;
|
||||
public static final int query_done = 0x00006010;
|
||||
public static final int unknown_signature_key = 0x00006011;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// }
|
||||
|
||||
// use only lower 16 bits due to compatibility lib
|
||||
public static final class request {
|
||||
public static final int public_keys = 0x00007001;
|
||||
public static final int secret_keys = 0x00007002;
|
||||
public static final int filename = 0x00007003;
|
||||
public static final int output_filename = 0x00007004;
|
||||
public static final int key_server_preference = 0x00007005;
|
||||
public static final int look_up_key_id = 0x00007006;
|
||||
public static final int export_to_server = 0x00007007;
|
||||
public static final int import_from_qr_code = 0x00007008;
|
||||
public static final int sign_key = 0x00007009;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
121
APG/src/org/thialfihar/android/apg/helper/FileHelper.java
Normal file
121
APG/src/org/thialfihar/android/apg/helper/FileHelper.java
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.helper;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class FileHelper {
|
||||
|
||||
/**
|
||||
* Checks if external storage is mounted if file is located on external storage
|
||||
*
|
||||
* @param file
|
||||
* @return true if storage is mounted
|
||||
*/
|
||||
public static boolean isStorageMounted(String file) {
|
||||
if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
|
||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the preferred installed file manager on Android and shows a toast if no manager is
|
||||
* installed.
|
||||
*
|
||||
* @param activity
|
||||
* @param filename
|
||||
* default selected file, not supported by all file managers
|
||||
* @param type
|
||||
* can be text/plain for example
|
||||
* @param requestCode
|
||||
* requestCode used to identify the result coming back from file manager to
|
||||
* onActivityResult() in your activity
|
||||
*/
|
||||
public static void openFile(Activity activity, String filename, String type, int requestCode) {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
|
||||
intent.setData(Uri.parse("file://" + filename));
|
||||
intent.setType(type);
|
||||
|
||||
try {
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// No compatible file manager was found.
|
||||
Toast.makeText(activity, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file path from a Uri.
|
||||
*
|
||||
* from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/
|
||||
* afilechooser/utils/FileUtils.java
|
||||
*
|
||||
* @param context
|
||||
* @param uri
|
||||
* @return
|
||||
*
|
||||
* @author paulburke
|
||||
*/
|
||||
public static String getPath(Context context, Uri uri) {
|
||||
|
||||
Log.d(Constants.TAG + " File -",
|
||||
"Authority: " + uri.getAuthority() + ", Fragment: " + uri.getFragment()
|
||||
+ ", Port: " + uri.getPort() + ", Query: " + uri.getQuery() + ", Scheme: "
|
||||
+ uri.getScheme() + ", Host: " + uri.getHost() + ", Segments: "
|
||||
+ uri.getPathSegments().toString());
|
||||
|
||||
if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
String[] projection = { "_data" };
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, projection, null, null, null);
|
||||
int column_index = cursor.getColumnIndexOrThrow("_data");
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getString(column_index);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Eat it
|
||||
}
|
||||
}
|
||||
|
||||
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
183
APG/src/org/thialfihar/android/apg/helper/OtherHelper.java
Normal file
183
APG/src/org/thialfihar/android/apg/helper/OtherHelper.java
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.helper;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class OtherHelper {
|
||||
|
||||
/**
|
||||
* Gets input stream of raw resource
|
||||
*
|
||||
* @param context
|
||||
* current context
|
||||
* @param resourceID
|
||||
* of html file to read
|
||||
* @return input stream of resource
|
||||
*/
|
||||
public static InputStream getInputStreamFromResource(Context context, int resourceID) {
|
||||
InputStream raw = context.getResources().openRawResource(resourceID);
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs bundle content to debug for inspecting the content
|
||||
*
|
||||
* @param bundle
|
||||
* @param bundleName
|
||||
*/
|
||||
public static void logDebugBundle(Bundle bundle, String bundleName) {
|
||||
if (Constants.DEBUG) {
|
||||
if (bundle != null) {
|
||||
Set<String> ks = bundle.keySet();
|
||||
Iterator<String> iterator = ks.iterator();
|
||||
|
||||
Log.d(Constants.TAG, "Bundle " + bundleName + ":");
|
||||
Log.d(Constants.TAG, "------------------------------");
|
||||
while (iterator.hasNext()) {
|
||||
String key = iterator.next();
|
||||
Object value = bundle.get(key);
|
||||
|
||||
if (value != null) {
|
||||
Log.d(Constants.TAG, key + " : " + value.toString());
|
||||
} else {
|
||||
Log.d(Constants.TAG, key + " : null");
|
||||
}
|
||||
}
|
||||
Log.d(Constants.TAG, "------------------------------");
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Bundle " + bundleName + ": null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set actionbar without home button if called from another app
|
||||
*
|
||||
* @param activity
|
||||
*/
|
||||
public static void setActionBarBackButton(SherlockFragmentActivity activity) {
|
||||
// set actionbar without home button if called from another app
|
||||
final ActionBar actionBar = activity.getSupportActionBar();
|
||||
Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)="
|
||||
+ activity.getCallingPackage());
|
||||
if (activity.getCallingPackage() != null
|
||||
&& activity.getCallingPackage().equals(Constants.PACKAGE_NAME)) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
} else {
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the calling package has the needed permission to invoke an intent with specific
|
||||
* restricted actions.
|
||||
*
|
||||
* If pkgName is null, this will also deny the use of the given action
|
||||
*
|
||||
* @param activity
|
||||
* @param pkgName
|
||||
* @param permName
|
||||
* @param action
|
||||
* @param restrictedActions
|
||||
*/
|
||||
public static void checkPackagePermissionForActions(Activity activity, String pkgName,
|
||||
String permName, String action, String[] restrictedActions) {
|
||||
if (action != null) {
|
||||
PackageManager pkgManager = activity.getPackageManager();
|
||||
|
||||
for (int i = 0; i < restrictedActions.length; i++) {
|
||||
if (restrictedActions[i].equals(action)) {
|
||||
if (pkgName != null
|
||||
&& (pkgManager.checkPermission(permName, pkgName) == PackageManager.PERMISSION_GRANTED || pkgName
|
||||
.equals(Constants.PACKAGE_NAME))) {
|
||||
Log.d(Constants.TAG, pkgName + " has permission " + permName + ". Action "
|
||||
+ action + " was granted!");
|
||||
} else {
|
||||
String error = pkgName + " does NOT have permission " + permName
|
||||
+ ". Action " + action + " was NOT granted!";
|
||||
Log.e(Constants.TAG, error);
|
||||
Toast.makeText(activity, activity.getString(R.string.errorMessage, error),
|
||||
Toast.LENGTH_LONG).show();
|
||||
|
||||
// end activity
|
||||
activity.setResult(Activity.RESULT_CANCELED, null);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits userId string into naming part and email part
|
||||
*
|
||||
* @param userId
|
||||
* @return array with naming (0) and email (1)
|
||||
*/
|
||||
public static String[] splitUserId(String userId) {
|
||||
String[] output = new String[2];
|
||||
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
output[1] = "<" + chunks[1];
|
||||
}
|
||||
output[0] = userId;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.helper;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
public class PGPConversionHelper {
|
||||
|
||||
/**
|
||||
* Convert from byte[] to PGPKeyRing
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static PGPKeyRing BytesToPGPKeyRing(byte[] keysBytes) {
|
||||
PGPObjectFactory factory = new PGPObjectFactory(keysBytes);
|
||||
PGPKeyRing keyRing = null;
|
||||
try {
|
||||
if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) {
|
||||
Log.e(Constants.TAG, "No keys given!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error while converting to PGPKeyRing!", e);
|
||||
}
|
||||
|
||||
return keyRing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from byte[] to ArrayList<PGPSecretKey>
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static ArrayList<PGPSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) {
|
||||
PGPSecretKeyRing keyRing = (PGPSecretKeyRing) BytesToPGPKeyRing(keysBytes);
|
||||
ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
|
||||
while (itr.hasNext()) {
|
||||
keys.add(itr.next());
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from byte[] to PGPSecretKey
|
||||
*
|
||||
* Singles keys are encoded as keyRings with one single key in it by Bouncy Castle
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) {
|
||||
PGPSecretKey key = BytesToPGPSecretKeyList(keyBytes).get(0);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from ArrayList<PGPSecretKey> to byte[]
|
||||
*
|
||||
* @param keys
|
||||
* @return
|
||||
*/
|
||||
public static byte[] PGPSecretKeyArrayListToBytes(ArrayList<PGPSecretKey> keys) {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
for (PGPSecretKey key : keys) {
|
||||
try {
|
||||
key.encode(os);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error while converting ArrayList<PGPSecretKey> to byte[]!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from PGPSecretKey to byte[]
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) {
|
||||
try {
|
||||
return key.getEncoded();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Encoding failed", e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from PGPSecretKeyRing to byte[]
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) {
|
||||
try {
|
||||
return keyRing.getEncoded();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Encoding failed", e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
452
APG/src/org/thialfihar/android/apg/helper/PGPHelper.java
Normal file
452
APG/src/org/thialfihar/android/apg/helper/PGPHelper.java
Normal file
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* 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.helper;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
import org.thialfihar.android.apg.util.IterableIterator;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class PGPHelper {
|
||||
public static PGPKeyRing decodeKeyRing(InputStream is) throws IOException {
|
||||
InputStream in = PGPUtil.getDecoderStream(is);
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
||||
Object obj = objectFactory.nextObject();
|
||||
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
return (PGPKeyRing) obj;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Date getCreationDate(PGPPublicKey key) {
|
||||
return key.getCreationTime();
|
||||
}
|
||||
|
||||
public static Date getCreationDate(PGPSecretKey key) {
|
||||
return key.getPublicKey().getCreationTime();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) {
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
|
||||
if (key.isMasterKey()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) {
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||
if (key.isMasterKey()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Vector<PGPPublicKey> getEncryptKeys(PGPPublicKeyRing keyRing) {
|
||||
Vector<PGPPublicKey> encryptKeys = new Vector<PGPPublicKey>();
|
||||
|
||||
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
|
||||
if (isEncryptionKey(key)) {
|
||||
encryptKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return encryptKeys;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Vector<PGPSecretKey> getSigningKeys(PGPSecretKeyRing keyRing) {
|
||||
Vector<PGPSecretKey> signingKeys = new Vector<PGPSecretKey>();
|
||||
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||
if (isSigningKey(key)) {
|
||||
signingKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return signingKeys;
|
||||
}
|
||||
|
||||
public static Vector<PGPPublicKey> getUsableEncryptKeys(PGPPublicKeyRing keyRing) {
|
||||
Vector<PGPPublicKey> usableKeys = new Vector<PGPPublicKey>();
|
||||
Vector<PGPPublicKey> encryptKeys = getEncryptKeys(keyRing);
|
||||
PGPPublicKey masterKey = null;
|
||||
for (int i = 0; i < encryptKeys.size(); ++i) {
|
||||
PGPPublicKey key = encryptKeys.get(i);
|
||||
if (!isExpired(key)) {
|
||||
if (key.isMasterKey()) {
|
||||
masterKey = key;
|
||||
} else {
|
||||
usableKeys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (masterKey != null) {
|
||||
usableKeys.add(masterKey);
|
||||
}
|
||||
return usableKeys;
|
||||
}
|
||||
|
||||
public static boolean isExpired(PGPPublicKey key) {
|
||||
Date creationDate = getCreationDate(key);
|
||||
Date expiryDate = getExpiryDate(key);
|
||||
Date now = new Date();
|
||||
if (now.compareTo(creationDate) >= 0
|
||||
&& (expiryDate == null || now.compareTo(expiryDate) <= 0)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isExpired(PGPSecretKey key) {
|
||||
return isExpired(key.getPublicKey());
|
||||
}
|
||||
|
||||
public static Vector<PGPSecretKey> getUsableSigningKeys(PGPSecretKeyRing keyRing) {
|
||||
Vector<PGPSecretKey> usableKeys = new Vector<PGPSecretKey>();
|
||||
Vector<PGPSecretKey> signingKeys = getSigningKeys(keyRing);
|
||||
PGPSecretKey masterKey = null;
|
||||
for (int i = 0; i < signingKeys.size(); ++i) {
|
||||
PGPSecretKey key = signingKeys.get(i);
|
||||
if (key.isMasterKey()) {
|
||||
masterKey = key;
|
||||
} else {
|
||||
usableKeys.add(key);
|
||||
}
|
||||
}
|
||||
if (masterKey != null) {
|
||||
usableKeys.add(masterKey);
|
||||
}
|
||||
return usableKeys;
|
||||
}
|
||||
|
||||
public static Date getExpiryDate(PGPPublicKey key) {
|
||||
Date creationDate = getCreationDate(key);
|
||||
if (key.getValidDays() == 0) {
|
||||
// no expiry
|
||||
return null;
|
||||
}
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
calendar.setTime(creationDate);
|
||||
calendar.add(Calendar.DATE, key.getValidDays());
|
||||
Date expiryDate = calendar.getTime();
|
||||
|
||||
return expiryDate;
|
||||
}
|
||||
|
||||
public static Date getExpiryDate(PGPSecretKey key) {
|
||||
return getExpiryDate(key.getPublicKey());
|
||||
}
|
||||
|
||||
public static PGPPublicKey getEncryptPublicKey(Context context, long masterKeyId) {
|
||||
PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(context, masterKeyId);
|
||||
if (keyRing == null) {
|
||||
Log.e(Constants.TAG, "keyRing is null!");
|
||||
return null;
|
||||
}
|
||||
Vector<PGPPublicKey> encryptKeys = getUsableEncryptKeys(keyRing);
|
||||
if (encryptKeys.size() == 0) {
|
||||
Log.e(Constants.TAG, "encryptKeys is null!");
|
||||
return null;
|
||||
}
|
||||
return encryptKeys.get(0);
|
||||
}
|
||||
|
||||
public static PGPSecretKey getSigningKey(Context context, long masterKeyId) {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(context, masterKeyId);
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
Vector<PGPSecretKey> signingKeys = getUsableSigningKeys(keyRing);
|
||||
if (signingKeys.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return signingKeys.get(0);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static String getMainUserId(PGPPublicKey key) {
|
||||
for (String userId : new IterableIterator<String>(key.getUserIDs())) {
|
||||
return userId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static String getMainUserId(PGPSecretKey key) {
|
||||
for (String userId : new IterableIterator<String>(key.getUserIDs())) {
|
||||
return userId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getMainUserIdSafe(Context context, PGPPublicKey key) {
|
||||
String userId = getMainUserId(key);
|
||||
if (userId == null || userId.equals("")) {
|
||||
userId = context.getString(R.string.unknownUserId);
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
public static String getMainUserIdSafe(Context context, PGPSecretKey key) {
|
||||
String userId = getMainUserId(key);
|
||||
if (userId == null || userId.equals("")) {
|
||||
userId = context.getString(R.string.unknownUserId);
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isEncryptionKey(PGPPublicKey key) {
|
||||
if (!key.isEncryptionKey()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key.getVersion() <= 3) {
|
||||
// this must be true now
|
||||
return key.isEncryptionKey();
|
||||
}
|
||||
|
||||
// special cases
|
||||
if (key.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
|
||||
continue;
|
||||
}
|
||||
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
|
||||
|
||||
if (hashed != null
|
||||
&& (hashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
|
||||
|
||||
if (unhashed != null
|
||||
&& (unhashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isEncryptionKey(PGPSecretKey key) {
|
||||
return isEncryptionKey(key.getPublicKey());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isSigningKey(PGPPublicKey key) {
|
||||
if (key.getVersion() <= 3) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// special case
|
||||
if (key.getAlgorithm() == PGPPublicKey.RSA_SIGN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
|
||||
continue;
|
||||
}
|
||||
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
|
||||
|
||||
if (hashed != null && (hashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
|
||||
|
||||
if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isSigningKey(PGPSecretKey key) {
|
||||
return isSigningKey(key.getPublicKey());
|
||||
}
|
||||
|
||||
public static String getAlgorithmInfo(PGPPublicKey key) {
|
||||
return getAlgorithmInfo(key.getAlgorithm(), key.getBitStrength());
|
||||
}
|
||||
|
||||
public static String getAlgorithmInfo(PGPSecretKey key) {
|
||||
return getAlgorithmInfo(key.getPublicKey());
|
||||
}
|
||||
|
||||
public static String getAlgorithmInfo(int algorithm, int keySize) {
|
||||
String algorithmStr = null;
|
||||
|
||||
switch (algorithm) {
|
||||
case PGPPublicKey.RSA_ENCRYPT:
|
||||
case PGPPublicKey.RSA_GENERAL:
|
||||
case PGPPublicKey.RSA_SIGN: {
|
||||
algorithmStr = "RSA";
|
||||
break;
|
||||
}
|
||||
|
||||
case PGPPublicKey.DSA: {
|
||||
algorithmStr = "DSA";
|
||||
break;
|
||||
}
|
||||
|
||||
case PGPPublicKey.ELGAMAL_ENCRYPT:
|
||||
case PGPPublicKey.ELGAMAL_GENERAL: {
|
||||
algorithmStr = "ElGamal";
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
algorithmStr = "???";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return algorithmStr + ", " + keySize + "bit";
|
||||
}
|
||||
|
||||
public static String convertToHex(byte[] fp) {
|
||||
String fingerPrint = "";
|
||||
for (int i = 0; i < fp.length; ++i) {
|
||||
if (i != 0 && i % 10 == 0) {
|
||||
fingerPrint += " ";
|
||||
} else if (i != 0 && i % 2 == 0) {
|
||||
fingerPrint += " ";
|
||||
}
|
||||
String chunk = Integer.toHexString((fp[i] + 256) % 256).toUpperCase();
|
||||
while (chunk.length() < 2) {
|
||||
chunk = "0" + chunk;
|
||||
}
|
||||
fingerPrint += chunk;
|
||||
}
|
||||
|
||||
return fingerPrint;
|
||||
|
||||
}
|
||||
|
||||
public static String getPubkeyAsArmoredString(Context context, long keyId) {
|
||||
PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId);
|
||||
// if it is no public key get it from your own keys...
|
||||
if (key == null) {
|
||||
PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId);
|
||||
if (secretKey == null) {
|
||||
Log.e(Constants.TAG, "Key could not be found!");
|
||||
return null;
|
||||
}
|
||||
key = secretKey.getPublicKey();
|
||||
}
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream aos = new ArmoredOutputStream(bos);
|
||||
aos.setHeader("Version", PGPMain.getFullVersion(context));
|
||||
|
||||
String armouredKey = null;
|
||||
try {
|
||||
aos.write(key.getEncoded());
|
||||
aos.close();
|
||||
|
||||
armouredKey = bos.toString("UTF-8");
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Problems while encoding key as armored string", e);
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "key:" + armouredKey);
|
||||
|
||||
return armouredKey;
|
||||
}
|
||||
|
||||
public static String getFingerPrint(Context context, long keyId) {
|
||||
PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId);
|
||||
// if it is no public key get it from your own keys...
|
||||
if (key == null) {
|
||||
PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId);
|
||||
if (secretKey == null) {
|
||||
Log.e(Constants.TAG, "Key could not be found!");
|
||||
return null;
|
||||
}
|
||||
key = secretKey.getPublicKey();
|
||||
}
|
||||
|
||||
return convertToHex(key.getFingerprint());
|
||||
}
|
||||
|
||||
public static String getSmallFingerPrint(long keyId) {
|
||||
String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase();
|
||||
while (fingerPrint.length() < 8) {
|
||||
fingerPrint = "0" + fingerPrint;
|
||||
}
|
||||
return fingerPrint;
|
||||
}
|
||||
|
||||
public static String keyToHex(long keyId) {
|
||||
return getSmallFingerPrint(keyId >> 32) + getSmallFingerPrint(keyId);
|
||||
}
|
||||
|
||||
public static long keyFromHex(String data) {
|
||||
int len = data.length();
|
||||
String s2 = data.substring(len - 8);
|
||||
String s1 = data.substring(0, len - 8);
|
||||
return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16);
|
||||
}
|
||||
}
|
||||
1827
APG/src/org/thialfihar/android/apg/helper/PGPMain.java
Normal file
1827
APG/src/org/thialfihar/android/apg/helper/PGPMain.java
Normal file
File diff suppressed because it is too large
Load Diff
172
APG/src/org/thialfihar/android/apg/helper/Preferences.java
Normal file
172
APG/src/org/thialfihar/android/apg/helper/Preferences.java
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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.helper;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Singleton Implementation of a Preference Helper
|
||||
*/
|
||||
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 long 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 (long) 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 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();
|
||||
}
|
||||
}
|
||||
209
APG/src/org/thialfihar/android/apg/provider/ApgContract.java
Normal file
209
APG/src/org/thialfihar/android/apg/provider/ApgContract.java
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
public class ApgContract {
|
||||
|
||||
interface KeyRingsColumns {
|
||||
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
||||
String TYPE = "type"; // see KeyTypes
|
||||
String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob
|
||||
}
|
||||
|
||||
interface KeysColumns {
|
||||
String KEY_ID = "key_id"; // not a database id
|
||||
String TYPE = "type"; // see KeyTypes
|
||||
String IS_MASTER_KEY = "is_master_key";
|
||||
String ALGORITHM = "algorithm";
|
||||
String KEY_SIZE = "key_size";
|
||||
String CAN_SIGN = "can_sign";
|
||||
String CAN_ENCRYPT = "can_encrypt";
|
||||
String IS_REVOKED = "is_revoked";
|
||||
String CREATION = "creation";
|
||||
String EXPIRY = "expiry";
|
||||
String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
|
||||
String KEY_DATA = "key_data"; // PGPPublicKey / PGPSecretKey blob
|
||||
String RANK = "rank";
|
||||
}
|
||||
|
||||
interface UserIdsColumns {
|
||||
String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
|
||||
String USER_ID = "user_id"; // not a database id
|
||||
String RANK = "rank";
|
||||
}
|
||||
|
||||
public static final class KeyTypes {
|
||||
public static final int PUBLIC = 0;
|
||||
public static final int SECRET = 1;
|
||||
}
|
||||
|
||||
public static final String CONTENT_AUTHORITY_EXTERNAL = Constants.PACKAGE_NAME;
|
||||
public static final String CONTENT_AUTHORITY_INTERNAL = Constants.PACKAGE_NAME + ".internal";
|
||||
|
||||
private static final Uri BASE_CONTENT_URI_INTERNAL = Uri.parse("content://"
|
||||
+ CONTENT_AUTHORITY_INTERNAL);
|
||||
|
||||
public static final String BASE_KEY_RINGS = "key_rings";
|
||||
public static final String BASE_DATA = "data";
|
||||
|
||||
public static final String PATH_PUBLIC = "public";
|
||||
public static final String PATH_SECRET = "secret";
|
||||
|
||||
public static final String PATH_BY_MASTER_KEY_ID = "master_key_id";
|
||||
public static final String PATH_BY_KEY_ID = "key_id";
|
||||
public static final String PATH_BY_EMAILS = "emails";
|
||||
|
||||
public static final String PATH_USER_IDS = "user_ids";
|
||||
public static final String PATH_KEYS = "keys";
|
||||
|
||||
public static class KeyRings implements KeyRingsColumns, BaseColumns {
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_KEY_RINGS).build();
|
||||
|
||||
/** Use if multiple items get returned */
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key_ring";
|
||||
|
||||
/** Use if a single item is returned */
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key_ring";
|
||||
|
||||
public static Uri buildPublicKeyRingsUri() {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsByMasterKeyIdUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC)
|
||||
.appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsByKeyIdUri(String keyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_KEY_ID)
|
||||
.appendPath(keyId).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsByEmailsUri(String emails) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_EMAILS)
|
||||
.appendPath(emails).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsUri() {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsByMasterKeyIdUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET)
|
||||
.appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsByKeyIdUri(String keyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_KEY_ID)
|
||||
.appendPath(keyId).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsByEmailsUri(String emails) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_EMAILS)
|
||||
.appendPath(emails).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Keys implements KeysColumns, BaseColumns {
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_KEY_RINGS).build();
|
||||
|
||||
/** Use if multiple items get returned */
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key";
|
||||
|
||||
/** Use if a single item is returned */
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key";
|
||||
|
||||
public static Uri buildPublicKeysUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_KEYS).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeysUri(String keyRingRowId, String keyRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_KEYS).appendPath(keyRowId).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeysUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_KEYS).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeysUri(String keyRingRowId, String keyRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_KEYS).appendPath(keyRowId).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class UserIds implements UserIdsColumns, BaseColumns {
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_KEY_RINGS).build();
|
||||
|
||||
/** Use if multiple items get returned */
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.user_id";
|
||||
|
||||
/** Use if a single item is returned */
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.user_id";
|
||||
|
||||
public static Uri buildPublicUserIdsUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_USER_IDS).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicUserIdsUri(String keyRingRowId, String userIdRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretUserIdsUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_USER_IDS).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretUserIdsUri(String keyRingRowId, String userIdRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DataStream {
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_DATA).build();
|
||||
|
||||
public static Uri buildDataStreamUri(String streamFilename) {
|
||||
return CONTENT_URI.buildUpon().appendPath(streamFilename).build();
|
||||
}
|
||||
}
|
||||
|
||||
private ApgContract() {
|
||||
}
|
||||
}
|
||||
104
APG/src/org/thialfihar/android/apg/provider/ApgDatabase.java
Normal file
104
APG/src/org/thialfihar/android/apg/provider/ApgDatabase.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyRingsColumns;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeysColumns;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIdsColumns;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
public class ApgDatabase extends SQLiteOpenHelper {
|
||||
private static final String DATABASE_NAME = "apg.db";
|
||||
private static final int DATABASE_VERSION = 3;
|
||||
|
||||
public interface Tables {
|
||||
String KEY_RINGS = "key_rings";
|
||||
String KEYS = "keys";
|
||||
String USER_IDS = "user_ids";
|
||||
}
|
||||
|
||||
private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
|
||||
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ KeyRingsColumns.MASTER_KEY_ID + " INT64, " + KeyRingsColumns.TYPE + " INTEGER, "
|
||||
+ KeyRingsColumns.KEY_RING_DATA + " BLOB)";
|
||||
|
||||
private static final String CREATE_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
|
||||
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + KeysColumns.KEY_ID
|
||||
+ " INT64, " + KeysColumns.TYPE + " INTEGER, " + KeysColumns.IS_MASTER_KEY
|
||||
+ " INTEGER, " + KeysColumns.ALGORITHM + " INTEGER, " + KeysColumns.KEY_SIZE
|
||||
+ " INTEGER, " + KeysColumns.CAN_SIGN + " INTEGER, " + KeysColumns.CAN_ENCRYPT
|
||||
+ " INTEGER, " + KeysColumns.IS_REVOKED + " INTEGER, " + KeysColumns.CREATION
|
||||
+ " INTEGER, " + KeysColumns.EXPIRY + " INTEGER, " + KeysColumns.KEY_DATA + " BLOB,"
|
||||
+ KeysColumns.RANK + " INTEGER, " + KeysColumns.KEY_RING_ROW_ID
|
||||
+ " INTEGER NOT NULL, FOREIGN KEY(" + KeysColumns.KEY_RING_ROW_ID + ") REFERENCES "
|
||||
+ Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS
|
||||
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ UserIdsColumns.USER_ID + " TEXT, " + UserIdsColumns.RANK + " INTEGER, "
|
||||
+ UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
|
||||
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
|
||||
+ BaseColumns._ID + ") ON DELETE CASCADE)";
|
||||
|
||||
ApgDatabase(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
Log.w(Constants.TAG, "Creating database...");
|
||||
|
||||
db.execSQL(CREATE_KEY_RINGS);
|
||||
db.execSQL(CREATE_KEYS);
|
||||
db.execSQL(CREATE_USER_IDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) {
|
||||
super.onOpen(db);
|
||||
if (!db.isReadOnly()) {
|
||||
// Enable foreign key constraints
|
||||
db.execSQL("PRAGMA foreign_keys=ON;");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
|
||||
|
||||
// Upgrade from oldVersion through all methods to newest one
|
||||
for (int version = oldVersion; version < newVersion; ++version) {
|
||||
Log.w(Constants.TAG, "Upgrading database to version " + version);
|
||||
|
||||
switch (version) {
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
825
APG/src/org/thialfihar/android/apg/provider/ApgProvider.java
Normal file
825
APG/src/org/thialfihar/android/apg/provider/ApgProvider.java
Normal file
@@ -0,0 +1,825 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyRingsColumns;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyTypes;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeysColumns;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIds;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.Keys;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIdsColumns;
|
||||
import org.thialfihar.android.apg.provider.ApgDatabase.Tables;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
|
||||
public class ApgProvider extends ContentProvider {
|
||||
private static final int PUBLIC_KEY_RING = 101;
|
||||
private static final int PUBLIC_KEY_RING_BY_ROW_ID = 102;
|
||||
private static final int PUBLIC_KEY_RING_BY_MASTER_KEY_ID = 103;
|
||||
private static final int PUBLIC_KEY_RING_BY_KEY_ID = 104;
|
||||
private static final int PUBLIC_KEY_RING_BY_EMAILS = 105;
|
||||
|
||||
private static final int PUBLIC_KEY_RING_KEY = 111;
|
||||
private static final int PUBLIC_KEY_RING_KEY_BY_ROW_ID = 112;
|
||||
|
||||
private static final int PUBLIC_KEY_RING_USER_ID = 121;
|
||||
private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122;
|
||||
|
||||
private static final int SECRET_KEY_RING = 201;
|
||||
private static final int SECRET_KEY_RING_BY_ROW_ID = 202;
|
||||
private static final int SECRET_KEY_RING_BY_MASTER_KEY_ID = 203;
|
||||
private static final int SECRET_KEY_RING_BY_KEY_ID = 204;
|
||||
private static final int SECRET_KEY_RING_BY_EMAILS = 205;
|
||||
|
||||
private static final int SECRET_KEY_RING_KEY = 211;
|
||||
private static final int SECRET_KEY_RING_KEY_BY_ROW_ID = 212;
|
||||
|
||||
private static final int SECRET_KEY_RING_USER_ID = 221;
|
||||
private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
|
||||
|
||||
private static final int DATA_STREAM = 301;
|
||||
|
||||
protected static boolean sInternalProvider;
|
||||
protected static UriMatcher sUriMatcher;
|
||||
|
||||
/**
|
||||
* Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by
|
||||
* this {@link ContentProvider}.
|
||||
*/
|
||||
protected static UriMatcher buildUriMatcher(boolean internalProvider) {
|
||||
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
|
||||
String authority;
|
||||
if (internalProvider) {
|
||||
authority = ApgContract.CONTENT_AUTHORITY_INTERNAL;
|
||||
} else {
|
||||
authority = ApgContract.CONTENT_AUTHORITY_EXTERNAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* public key rings
|
||||
*
|
||||
* <pre>
|
||||
* key_rings/public
|
||||
* key_rings/public/#
|
||||
* key_rings/public/master_key_id/_
|
||||
* key_rings/public/key_id/_
|
||||
* key_rings/public/emails/_
|
||||
* </pre>
|
||||
*/
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_PUBLIC,
|
||||
PUBLIC_KEY_RING);
|
||||
matcher.addURI(authority,
|
||||
ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_PUBLIC + "/#",
|
||||
PUBLIC_KEY_RING_BY_ROW_ID);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_PUBLIC + "/"
|
||||
+ ApgContract.PATH_BY_MASTER_KEY_ID + "/*", PUBLIC_KEY_RING_BY_MASTER_KEY_ID);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_PUBLIC + "/"
|
||||
+ ApgContract.PATH_BY_KEY_ID + "/*", PUBLIC_KEY_RING_BY_KEY_ID);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_PUBLIC + "/"
|
||||
+ ApgContract.PATH_BY_EMAILS + "/*", PUBLIC_KEY_RING_BY_EMAILS);
|
||||
|
||||
/**
|
||||
* public keys
|
||||
*
|
||||
* <pre>
|
||||
* key_rings/public/#/keys
|
||||
* key_rings/public/#/keys/#
|
||||
* </pre>
|
||||
*/
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_PUBLIC
|
||||
+ "/#/" + ApgContract.PATH_KEYS, PUBLIC_KEY_RING_KEY);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_PUBLIC
|
||||
+ "/#/" + ApgContract.PATH_KEYS + "/#", PUBLIC_KEY_RING_KEY_BY_ROW_ID);
|
||||
|
||||
/**
|
||||
* public user ids
|
||||
*
|
||||
* <pre>
|
||||
* key_rings/public/#/user_ids
|
||||
* key_rings/public/#/user_ids/#
|
||||
* </pre>
|
||||
*/
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_PUBLIC
|
||||
+ "/#/" + ApgContract.PATH_USER_IDS, PUBLIC_KEY_RING_USER_ID);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_PUBLIC
|
||||
+ "/#/" + ApgContract.PATH_USER_IDS + "/#", PUBLIC_KEY_RING_USER_ID_BY_ROW_ID);
|
||||
|
||||
/**
|
||||
* secret key rings
|
||||
*
|
||||
* <pre>
|
||||
* key_rings/secret
|
||||
* key_rings/secret/#
|
||||
* key_rings/secret/master_key_id/_
|
||||
* key_rings/secret/key_id/_
|
||||
* key_rings/secret/emails/_
|
||||
* </pre>
|
||||
*/
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_SECRET,
|
||||
SECRET_KEY_RING);
|
||||
matcher.addURI(authority,
|
||||
ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_SECRET + "/#",
|
||||
SECRET_KEY_RING_BY_ROW_ID);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_SECRET + "/"
|
||||
+ ApgContract.PATH_BY_MASTER_KEY_ID + "/*", SECRET_KEY_RING_BY_MASTER_KEY_ID);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_SECRET + "/"
|
||||
+ ApgContract.PATH_BY_KEY_ID + "/*", SECRET_KEY_RING_BY_KEY_ID);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_SECRET + "/"
|
||||
+ ApgContract.PATH_BY_EMAILS + "/*", SECRET_KEY_RING_BY_EMAILS);
|
||||
|
||||
/**
|
||||
* secret keys
|
||||
*
|
||||
* <pre>
|
||||
* key_rings/secret/#/keys
|
||||
* key_rings/secret/#/keys/#
|
||||
* </pre>
|
||||
*/
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_SECRET
|
||||
+ "/#/" + ApgContract.PATH_KEYS, SECRET_KEY_RING_KEY);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_SECRET
|
||||
+ "/#/" + ApgContract.PATH_KEYS + "/#", SECRET_KEY_RING_KEY_BY_ROW_ID);
|
||||
|
||||
/**
|
||||
* secret user ids
|
||||
*
|
||||
* <pre>
|
||||
* key_rings/secret/#/user_ids
|
||||
* key_rings/secret/#/user_ids/#
|
||||
* </pre>
|
||||
*/
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_SECRET
|
||||
+ "/#/" + ApgContract.PATH_USER_IDS, SECRET_KEY_RING_USER_ID);
|
||||
matcher.addURI(authority, ApgContract.BASE_KEY_RINGS + "/" + ApgContract.PATH_SECRET
|
||||
+ "/#/" + ApgContract.PATH_USER_IDS + "/#", SECRET_KEY_RING_USER_ID_BY_ROW_ID);
|
||||
|
||||
/**
|
||||
* data stream
|
||||
*
|
||||
* <pre>
|
||||
* data / _
|
||||
* </pre>
|
||||
*/
|
||||
matcher.addURI(authority, ApgContract.BASE_DATA + "/*", DATA_STREAM);
|
||||
|
||||
return matcher;
|
||||
}
|
||||
|
||||
private ApgDatabase mApgDatabase;
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
mApgDatabase = new ApgDatabase(getContext());
|
||||
return true;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
final int match = sUriMatcher.match(uri);
|
||||
switch (match) {
|
||||
case PUBLIC_KEY_RING:
|
||||
case PUBLIC_KEY_RING_BY_EMAILS:
|
||||
case SECRET_KEY_RING:
|
||||
case SECRET_KEY_RING_BY_EMAILS:
|
||||
return KeyRings.CONTENT_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_BY_ROW_ID:
|
||||
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
||||
case PUBLIC_KEY_RING_BY_KEY_ID:
|
||||
case SECRET_KEY_RING_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
||||
case SECRET_KEY_RING_BY_KEY_ID:
|
||||
return KeyRings.CONTENT_ITEM_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_KEY:
|
||||
case SECRET_KEY_RING_KEY:
|
||||
return Keys.CONTENT_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
||||
return Keys.CONTENT_ITEM_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_USER_ID:
|
||||
case SECRET_KEY_RING_USER_ID:
|
||||
return UserIds.CONTENT_TYPE;
|
||||
|
||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
return UserIds.CONTENT_ITEM_TYPE;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of the query (secret/public)
|
||||
*
|
||||
* @param uri
|
||||
* @return
|
||||
*/
|
||||
private int getKeyType(int match) {
|
||||
int type;
|
||||
switch (match) {
|
||||
case PUBLIC_KEY_RING:
|
||||
case PUBLIC_KEY_RING_BY_ROW_ID:
|
||||
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
||||
case PUBLIC_KEY_RING_BY_KEY_ID:
|
||||
case PUBLIC_KEY_RING_BY_EMAILS:
|
||||
case PUBLIC_KEY_RING_KEY:
|
||||
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
||||
case PUBLIC_KEY_RING_USER_ID:
|
||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
type = KeyTypes.PUBLIC;
|
||||
break;
|
||||
|
||||
case SECRET_KEY_RING:
|
||||
case SECRET_KEY_RING_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
||||
case SECRET_KEY_RING_BY_KEY_ID:
|
||||
case SECRET_KEY_RING_BY_EMAILS:
|
||||
case SECRET_KEY_RING_KEY:
|
||||
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_USER_ID:
|
||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
type = KeyTypes.SECRET;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown match " + match);
|
||||
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set result of query to specific columns, don't show blob column for external content provider
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private HashMap<String, String> getProjectionMapForKeyRings() {
|
||||
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||
|
||||
projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS + "." + BaseColumns._ID);
|
||||
projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "."
|
||||
+ KeyRingsColumns.MASTER_KEY_ID);
|
||||
// only give out keyRing blob when we are using the internal content provider
|
||||
if (sInternalProvider) {
|
||||
projectionMap.put(KeyRingsColumns.KEY_RING_DATA, Tables.KEY_RINGS + "."
|
||||
+ KeyRingsColumns.KEY_RING_DATA);
|
||||
}
|
||||
projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID);
|
||||
|
||||
return projectionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set result of query to specific columns, don't show blob column for external content provider
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private HashMap<String, String> getProjectionMapForKeys() {
|
||||
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||
|
||||
projectionMap.put(BaseColumns._ID, BaseColumns._ID);
|
||||
projectionMap.put(KeysColumns.KEY_ID, KeysColumns.KEY_ID);
|
||||
projectionMap.put(KeysColumns.IS_MASTER_KEY, KeysColumns.IS_MASTER_KEY);
|
||||
projectionMap.put(KeysColumns.ALGORITHM, KeysColumns.ALGORITHM);
|
||||
projectionMap.put(KeysColumns.KEY_SIZE, KeysColumns.KEY_SIZE);
|
||||
projectionMap.put(KeysColumns.CAN_SIGN, KeysColumns.CAN_SIGN);
|
||||
projectionMap.put(KeysColumns.CAN_ENCRYPT, KeysColumns.CAN_ENCRYPT);
|
||||
projectionMap.put(KeysColumns.IS_REVOKED, KeysColumns.IS_REVOKED);
|
||||
projectionMap.put(KeysColumns.CREATION, KeysColumns.CREATION);
|
||||
projectionMap.put(KeysColumns.EXPIRY, KeysColumns.EXPIRY);
|
||||
projectionMap.put(KeysColumns.KEY_RING_ROW_ID, KeysColumns.KEY_RING_ROW_ID);
|
||||
// only give out keyRing blob when we are using the internal content provider
|
||||
if (sInternalProvider) {
|
||||
projectionMap.put(KeysColumns.KEY_DATA, KeysColumns.KEY_DATA);
|
||||
}
|
||||
projectionMap.put(KeysColumns.RANK, KeysColumns.RANK);
|
||||
|
||||
return projectionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds default query for keyRings: KeyRings table is joined with Keys and UserIds
|
||||
*
|
||||
* @param qb
|
||||
* @param match
|
||||
* @param isMasterKey
|
||||
* @param sortOrder
|
||||
* @return
|
||||
*/
|
||||
private SQLiteQueryBuilder buildKeyRingQuery(SQLiteQueryBuilder qb, int match, String sortOrder) {
|
||||
qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
|
||||
qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
|
||||
|
||||
qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.USER_IDS + " ON " + "("
|
||||
+ Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "."
|
||||
+ UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "."
|
||||
+ UserIdsColumns.RANK + " = '0')");
|
||||
|
||||
qb.setProjectionMap(getProjectionMapForKeyRings());
|
||||
|
||||
return qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds default query for keyRings: KeyRings table is joined with Keys and UserIds
|
||||
*
|
||||
* @param qb
|
||||
* @param match
|
||||
* @param isMasterKey
|
||||
* @param sortOrder
|
||||
* @return
|
||||
*/
|
||||
private SQLiteQueryBuilder buildKeyRingQueryWithKeys(SQLiteQueryBuilder qb, int match,
|
||||
String sortOrder) {
|
||||
qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
|
||||
qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
|
||||
|
||||
qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.KEYS + " ON " + "("
|
||||
+ Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.KEYS + "."
|
||||
+ KeysColumns.KEY_RING_ROW_ID + " AND " + Tables.KEYS + "."
|
||||
+ KeysColumns.IS_MASTER_KEY + " = '1') " + " INNER JOIN " + Tables.USER_IDS
|
||||
+ " ON " + "(" + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS
|
||||
+ "." + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "."
|
||||
+ UserIdsColumns.RANK + " = '0')");
|
||||
|
||||
qb.setProjectionMap(getProjectionMapForKeyRings());
|
||||
|
||||
return qb;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")");
|
||||
|
||||
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||
SQLiteDatabase db = mApgDatabase.getReadableDatabase();
|
||||
|
||||
int match = sUriMatcher.match(uri);
|
||||
|
||||
switch (match) {
|
||||
case PUBLIC_KEY_RING:
|
||||
case SECRET_KEY_RING:
|
||||
qb = buildKeyRingQuery(qb, match, sortOrder);
|
||||
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PUBLIC_KEY_RING_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_BY_ROW_ID:
|
||||
qb = buildKeyRingQuery(qb, match, sortOrder);
|
||||
|
||||
qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + BaseColumns._ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getLastPathSegment());
|
||||
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
||||
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
||||
qb = buildKeyRingQuery(qb, match, sortOrder);
|
||||
|
||||
qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getLastPathSegment());
|
||||
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SECRET_KEY_RING_BY_KEY_ID:
|
||||
case PUBLIC_KEY_RING_BY_KEY_ID:
|
||||
qb = buildKeyRingQueryWithKeys(qb, match, sortOrder);
|
||||
|
||||
qb.appendWhere(" AND " + Tables.KEYS + "." + KeysColumns.KEY_ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getLastPathSegment());
|
||||
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SECRET_KEY_RING_BY_EMAILS:
|
||||
case PUBLIC_KEY_RING_BY_EMAILS:
|
||||
qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
|
||||
qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
|
||||
|
||||
qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.KEYS + " ON " + "("
|
||||
+ Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.KEYS + "."
|
||||
+ KeysColumns.KEY_RING_ROW_ID + " AND " + Tables.KEYS + "."
|
||||
+ KeysColumns.IS_MASTER_KEY + " = '1'" + ") " + " INNER JOIN "
|
||||
+ Tables.USER_IDS + " ON " + "(" + Tables.KEYS + "." + BaseColumns._ID + " = "
|
||||
+ Tables.USER_IDS + "." + UserIdsColumns.KEY_RING_ROW_ID + " AND "
|
||||
+ Tables.USER_IDS + "." + UserIdsColumns.RANK + " = '0')");
|
||||
|
||||
qb.setProjectionMap(getProjectionMapForKeyRings());
|
||||
|
||||
String emails = uri.getLastPathSegment();
|
||||
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." + UserIdsColumns.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." + BaseColumns._ID + " FROM "
|
||||
+ Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID
|
||||
+ " = " + Tables.KEYS + "." + BaseColumns._ID + " AND (" + emailWhere
|
||||
+ "))");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PUBLIC_KEY_RING_KEY:
|
||||
case SECRET_KEY_RING_KEY:
|
||||
qb.setTables(Tables.KEYS);
|
||||
qb.appendWhere(KeysColumns.TYPE + " = ");
|
||||
qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
|
||||
|
||||
qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
|
||||
|
||||
qb.setProjectionMap(getProjectionMapForKeys());
|
||||
|
||||
break;
|
||||
|
||||
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
||||
qb.setTables(Tables.KEYS);
|
||||
qb.appendWhere(KeysColumns.TYPE + " = ");
|
||||
qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
|
||||
|
||||
qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
|
||||
|
||||
qb.appendWhere(" AND " + BaseColumns._ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getLastPathSegment());
|
||||
|
||||
qb.setProjectionMap(getProjectionMapForKeys());
|
||||
|
||||
break;
|
||||
|
||||
case PUBLIC_KEY_RING_USER_ID:
|
||||
case SECRET_KEY_RING_USER_ID:
|
||||
qb.setTables(Tables.USER_IDS);
|
||||
qb.appendWhere(UserIdsColumns.KEY_RING_ROW_ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
|
||||
|
||||
break;
|
||||
|
||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
qb.setTables(Tables.USER_IDS);
|
||||
qb.appendWhere(UserIdsColumns.KEY_RING_ROW_ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
|
||||
|
||||
qb.appendWhere(" AND " + BaseColumns._ID + " = ");
|
||||
qb.appendWhereEscapeString(uri.getLastPathSegment());
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||
|
||||
}
|
||||
|
||||
// If no sort order is specified use the default
|
||||
String orderBy;
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
orderBy = null;
|
||||
} else {
|
||||
orderBy = sortOrder;
|
||||
}
|
||||
|
||||
Cursor c = qb.query(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);
|
||||
|
||||
if (Constants.DEBUG) {
|
||||
Log.d(Constants.TAG,
|
||||
"Query: "
|
||||
+ qb.buildQuery(projection, selection, selectionArgs, null, null,
|
||||
orderBy, null));
|
||||
Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(c));
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
Log.d(Constants.TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")");
|
||||
|
||||
final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
|
||||
|
||||
Uri rowUri = null;
|
||||
long rowId = -1;
|
||||
try {
|
||||
final int match = sUriMatcher.match(uri);
|
||||
|
||||
switch (match) {
|
||||
case PUBLIC_KEY_RING:
|
||||
values.put(KeyRings.TYPE, KeyTypes.PUBLIC);
|
||||
|
||||
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
|
||||
rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
|
||||
|
||||
break;
|
||||
case PUBLIC_KEY_RING_KEY:
|
||||
values.put(Keys.TYPE, KeyTypes.PUBLIC);
|
||||
|
||||
rowId = db.insertOrThrow(Tables.KEYS, null, values);
|
||||
rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
|
||||
|
||||
break;
|
||||
case PUBLIC_KEY_RING_USER_ID:
|
||||
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
|
||||
rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
|
||||
|
||||
break;
|
||||
case SECRET_KEY_RING:
|
||||
values.put(KeyRings.TYPE, KeyTypes.SECRET);
|
||||
|
||||
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
|
||||
rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
|
||||
|
||||
break;
|
||||
case SECRET_KEY_RING_KEY:
|
||||
values.put(Keys.TYPE, KeyTypes.SECRET);
|
||||
|
||||
rowId = db.insertOrThrow(Tables.KEYS, null, values);
|
||||
rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
|
||||
|
||||
break;
|
||||
case SECRET_KEY_RING_USER_ID:
|
||||
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
|
||||
rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
} catch (SQLiteConstraintException e) {
|
||||
Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?");
|
||||
}
|
||||
|
||||
// notify of changes in db
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
|
||||
return rowUri;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
Log.v(Constants.TAG, "delete(uri=" + uri + ")");
|
||||
|
||||
final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
|
||||
|
||||
int count;
|
||||
final int match = sUriMatcher.match(uri);
|
||||
|
||||
String defaultSelection = null;
|
||||
switch (match) {
|
||||
case PUBLIC_KEY_RING_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_BY_ROW_ID:
|
||||
defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment();
|
||||
// corresponding keys and userIds are deleted by ON DELETE CASCADE
|
||||
count = db.delete(Tables.KEY_RINGS,
|
||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
|
||||
selectionArgs);
|
||||
break;
|
||||
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
||||
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
||||
defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment();
|
||||
// corresponding keys and userIds are deleted by ON DELETE CASCADE
|
||||
count = db.delete(Tables.KEY_RINGS,
|
||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
|
||||
selectionArgs);
|
||||
break;
|
||||
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
||||
count = db.delete(Tables.KEYS,
|
||||
buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs);
|
||||
break;
|
||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
|
||||
selectionArgs);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
|
||||
// notify of changes in db
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
Log.v(Constants.TAG, "update(uri=" + uri + ", values=" + values.toString() + ")");
|
||||
|
||||
final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
|
||||
|
||||
String defaultSelection = null;
|
||||
int count = 0;
|
||||
try {
|
||||
final int match = sUriMatcher.match(uri);
|
||||
switch (match) {
|
||||
case PUBLIC_KEY_RING_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_BY_ROW_ID:
|
||||
defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment();
|
||||
|
||||
count = db.update(
|
||||
Tables.KEY_RINGS,
|
||||
values,
|
||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
|
||||
selection), selectionArgs);
|
||||
break;
|
||||
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
||||
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
||||
defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment();
|
||||
|
||||
count = db.update(
|
||||
Tables.KEY_RINGS,
|
||||
values,
|
||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
|
||||
selection), selectionArgs);
|
||||
break;
|
||||
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
||||
count = db
|
||||
.update(Tables.KEYS, values,
|
||||
buildDefaultKeysSelection(uri, getKeyType(match), selection),
|
||||
selectionArgs);
|
||||
break;
|
||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
count = db.update(Tables.USER_IDS, values,
|
||||
buildDefaultUserIdsSelection(uri, selection), selectionArgs);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
} catch (SQLiteConstraintException e) {
|
||||
Log.e(Constants.TAG, "Constraint exception on update! Entry already existing?");
|
||||
}
|
||||
|
||||
// notify of changes in db
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build default selection statement for KeyRings. If no extra selection is specified only build
|
||||
* where clause with rowId
|
||||
*
|
||||
* @param uri
|
||||
* @param selection
|
||||
* @return
|
||||
*/
|
||||
private String buildDefaultKeyRingsSelection(String defaultSelection, Integer keyType,
|
||||
String selection) {
|
||||
String andType = "";
|
||||
if (keyType != null) {
|
||||
andType = " AND " + KeyRingsColumns.TYPE + "=" + keyType;
|
||||
}
|
||||
|
||||
String andSelection = "";
|
||||
if (!TextUtils.isEmpty(selection)) {
|
||||
andSelection = " AND (" + selection + ")";
|
||||
}
|
||||
|
||||
return defaultSelection + andType + andSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build default selection statement for Keys. If no extra selection is specified only build
|
||||
* where clause with rowId
|
||||
*
|
||||
* @param uri
|
||||
* @param selection
|
||||
* @return
|
||||
*/
|
||||
private String buildDefaultKeysSelection(Uri uri, Integer keyType, String selection) {
|
||||
String rowId = uri.getLastPathSegment();
|
||||
|
||||
String foreignKeyRingRowId = uri.getPathSegments().get(2);
|
||||
String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = "
|
||||
+ foreignKeyRingRowId;
|
||||
|
||||
String andType = "";
|
||||
if (keyType != null) {
|
||||
andType = " AND " + KeysColumns.TYPE + "=" + keyType;
|
||||
}
|
||||
|
||||
String andSelection = "";
|
||||
if (!TextUtils.isEmpty(selection)) {
|
||||
andSelection = " AND (" + selection + ")";
|
||||
}
|
||||
|
||||
return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andType + andSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build default selection statement for UserIds. If no extra selection is specified only build
|
||||
* where clause with rowId
|
||||
*
|
||||
* @param uri
|
||||
* @param selection
|
||||
* @return
|
||||
*/
|
||||
private String buildDefaultUserIdsSelection(Uri uri, String selection) {
|
||||
String rowId = uri.getLastPathSegment();
|
||||
|
||||
String foreignKeyRingRowId = uri.getPathSegments().get(2);
|
||||
String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = "
|
||||
+ foreignKeyRingRowId;
|
||||
|
||||
String andSelection = "";
|
||||
if (!TextUtils.isEmpty(selection)) {
|
||||
andSelection = " AND (" + selection + ")";
|
||||
}
|
||||
|
||||
return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
int match = sUriMatcher.match(uri);
|
||||
if (match != DATA_STREAM) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
String fileName = uri.getLastPathSegment();
|
||||
File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName);
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
/**
|
||||
* The same content provider as ApgProviderInternal except that it does not give out keyRing and key
|
||||
* blob data when querying.
|
||||
*
|
||||
* This provider is exported with a readPermission in AndroidManifest.xml
|
||||
*/
|
||||
public class ApgProviderExternal extends ApgProvider {
|
||||
|
||||
static {
|
||||
sInternalProvider = false;
|
||||
sUriMatcher = buildUriMatcher(sInternalProvider);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
/**
|
||||
* This provider is NOT exported in AndroidManifest.xml as it also return the actual secret keys
|
||||
* from the database
|
||||
*/
|
||||
public class ApgProviderInternal extends ApgProvider {
|
||||
|
||||
static {
|
||||
sInternalProvider = true;
|
||||
sUriMatcher = buildUriMatcher(sInternalProvider);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
public class ApgServiceBlobContract {
|
||||
|
||||
interface BlobsColumns {
|
||||
String KEY = "key";
|
||||
}
|
||||
|
||||
public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".blobs";
|
||||
|
||||
private static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
|
||||
|
||||
public static class Blobs implements BlobsColumns, BaseColumns {
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI;
|
||||
}
|
||||
|
||||
private ApgServiceBlobContract() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 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.provider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import org.thialfihar.android.apg.provider.ApgServiceBlobContract.BlobsColumns;
|
||||
|
||||
public class ApgServiceBlobDatabase extends SQLiteOpenHelper {
|
||||
private static final String DATABASE_NAME = "apg_blob.db";
|
||||
private static final int DATABASE_VERSION = 2;
|
||||
|
||||
public static final String TABLE = "data";
|
||||
|
||||
public ApgServiceBlobDatabase(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE " + TABLE + " ( " + BaseColumns._ID
|
||||
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + BlobsColumns.KEY + " TEXT NOT NULL)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
// no upgrade necessary yet
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 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.provider;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import org.thialfihar.android.apg.provider.ApgServiceBlobContract.Blobs;
|
||||
import org.thialfihar.android.apg.provider.ApgServiceBlobContract.BlobsColumns;
|
||||
import org.thialfihar.android.apg.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 STORE_PATH = Constants.path.APP_DIR + "/ApgBlobs";
|
||||
|
||||
private ApgServiceBlobDatabase mBlobDatabase = null;
|
||||
|
||||
public ApgServiceBlobProvider() {
|
||||
File dir = new File(STORE_PATH);
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
mBlobDatabase = new ApgServiceBlobDatabase(getContext());
|
||||
return true;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues ignored) {
|
||||
// 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(BlobsColumns.KEY, password);
|
||||
|
||||
SQLiteDatabase db = mBlobDatabase.getWritableDatabase();
|
||||
long newRowId = db.insert(ApgServiceBlobDatabase.TABLE, null, vals);
|
||||
Uri insertedUri = ContentUris.withAppendedId(Blobs.CONTENT_URI, newRowId);
|
||||
|
||||
return Uri.withAppendedPath(insertedUri, password);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException,
|
||||
FileNotFoundException {
|
||||
Log.d(Constants.TAG, "openFile() called with uri: " + uri.toString() + " and 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);
|
||||
|
||||
Log.d(Constants.TAG, "Got id: " + id + " and key: " + key);
|
||||
|
||||
// get the data
|
||||
SQLiteDatabase db = mBlobDatabase.getReadableDatabase();
|
||||
Cursor result = db.query(ApgServiceBlobDatabase.TABLE, new String[] { BaseColumns._ID },
|
||||
BaseColumns._ID + " = ? and " + BlobsColumns.KEY + " = ?",
|
||||
new String[] { id, key }, null, null, null);
|
||||
|
||||
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")) {
|
||||
Log.d(Constants.TAG, "Try to open file w");
|
||||
if (!targetFile.exists()) {
|
||||
try {
|
||||
targetFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.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")) {
|
||||
Log.d(Constants.TAG, "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;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
501
APG/src/org/thialfihar/android/apg/provider/ProviderHelper.java
Normal file
501
APG/src/org/thialfihar/android/apg/provider/ProviderHelper.java
Normal file
@@ -0,0 +1,501 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
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.Constants;
|
||||
import org.thialfihar.android.apg.helper.PGPConversionHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIds;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.Keys;
|
||||
import org.thialfihar.android.apg.util.IterableIterator;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
|
||||
public class ProviderHelper {
|
||||
|
||||
/**
|
||||
* Private helper method to get PGPKeyRing from database
|
||||
*
|
||||
* @param context
|
||||
* @param queryUri
|
||||
* @return
|
||||
*/
|
||||
private static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) {
|
||||
Cursor cursor = context.getContentResolver().query(queryUri,
|
||||
new String[] { KeyRings._ID, KeyRings.KEY_RING_DATA }, null, null, null);
|
||||
|
||||
PGPKeyRing keyRing = null;
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int keyRingDataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
|
||||
|
||||
byte[] data = cursor.getBlob(keyRingDataCol);
|
||||
if (data != null) {
|
||||
keyRing = PGPConversionHelper.BytesToPGPKeyRing(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return keyRing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual PGPPublicKeyRing object from the database blob based on the rowId
|
||||
*
|
||||
* @param context
|
||||
* @param rowId
|
||||
* @return
|
||||
*/
|
||||
public static PGPPublicKeyRing getPGPPublicKeyRingByRowId(Context context, long rowId) {
|
||||
Uri queryUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
|
||||
return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual PGPPublicKeyRing object from the database blob based on the maserKeyId
|
||||
*
|
||||
* @param context
|
||||
* @param masterKeyId
|
||||
* @return
|
||||
*/
|
||||
public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context,
|
||||
long masterKeyId) {
|
||||
Uri queryUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
|
||||
return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual PGPPublicKeyRing object from the database blob associated with a key
|
||||
* with this keyId
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
public static PGPPublicKeyRing getPGPPublicKeyRingByKeyId(Context context, long keyId) {
|
||||
Uri queryUri = KeyRings.buildPublicKeyRingsByKeyIdUri(Long.toString(keyId));
|
||||
return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual PGPPublicKey object from the database blob associated with a key with
|
||||
* this keyId
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) {
|
||||
PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId);
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return keyRing.getPublicKey(keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual PGPSecretKeyRing object from the database blob based on the rowId
|
||||
*
|
||||
* @param context
|
||||
* @param rowId
|
||||
* @return
|
||||
*/
|
||||
public static PGPSecretKeyRing getPGPSecretKeyRingByRowId(Context context, long rowId) {
|
||||
Uri queryUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
|
||||
return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual PGPSecretKeyRing object from the database blob based on the maserKeyId
|
||||
*
|
||||
* @param context
|
||||
* @param masterKeyId
|
||||
* @return
|
||||
*/
|
||||
public static PGPSecretKeyRing getPGPSecretKeyRingByMasterKeyId(Context context,
|
||||
long masterKeyId) {
|
||||
Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
|
||||
return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual PGPSecretKeyRing object from the database blob associated with a key
|
||||
* with this keyId
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
public static PGPSecretKeyRing getPGPSecretKeyRingByKeyId(Context context, long keyId) {
|
||||
Uri queryUri = KeyRings.buildSecretKeyRingsByKeyIdUri(Long.toString(keyId));
|
||||
return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual PGPSecretKey object from the database blob associated with a key with
|
||||
* this keyId
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) {
|
||||
PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId);
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return keyRing.getSecretKey(keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves PGPPublicKeyRing with its keys and userIds in DB
|
||||
*
|
||||
* @param context
|
||||
* @param keyRing
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws GeneralException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void saveKeyRing(Context context, PGPPublicKeyRing keyRing) throws IOException {
|
||||
PGPPublicKey masterKey = keyRing.getPublicKey();
|
||||
long masterKeyId = masterKey.getKeyID();
|
||||
|
||||
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
|
||||
Uri deleteUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
|
||||
|
||||
try {
|
||||
context.getContentResolver().delete(deleteUri, null, null);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e);
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
|
||||
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
|
||||
|
||||
// insert new version of this keyRing
|
||||
Uri uri = KeyRings.buildPublicKeyRingsUri();
|
||||
Uri insertedUri = context.getContentResolver().insert(uri, values);
|
||||
long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment());
|
||||
|
||||
// save all keys and userIds included in keyRing object in database
|
||||
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
|
||||
|
||||
int rank = 0;
|
||||
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
|
||||
operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank));
|
||||
++rank;
|
||||
}
|
||||
|
||||
int userIdRank = 0;
|
||||
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||
operations.add(buildPublicUserIdOperations(context, keyRingRowId, userId, userIdRank));
|
||||
++userIdRank;
|
||||
}
|
||||
|
||||
try {
|
||||
context.getContentResolver().applyBatch(ApgContract.CONTENT_AUTHORITY_INTERNAL, operations);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "applyBatch failed!", e);
|
||||
} catch (OperationApplicationException e) {
|
||||
Log.e(Constants.TAG, "applyBatch failed!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves PGPSecretKeyRing with its keys and userIds in DB
|
||||
*
|
||||
* @param context
|
||||
* @param keyRing
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws GeneralException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void saveKeyRing(Context context, PGPSecretKeyRing keyRing) throws IOException {
|
||||
PGPSecretKey masterKey = keyRing.getSecretKey();
|
||||
long masterKeyId = masterKey.getKeyID();
|
||||
|
||||
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
|
||||
Uri deleteUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
|
||||
|
||||
try {
|
||||
context.getContentResolver().delete(deleteUri, null, null);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e);
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
|
||||
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
|
||||
|
||||
// insert new version of this keyRing
|
||||
Uri uri = KeyRings.buildSecretKeyRingsUri();
|
||||
Uri insertedUri = context.getContentResolver().insert(uri, values);
|
||||
long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment());
|
||||
|
||||
// save all keys and userIds included in keyRing object in database
|
||||
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
|
||||
|
||||
int rank = 0;
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||
operations.add(buildSecretKeyOperations(context, keyRingRowId, key, rank));
|
||||
++rank;
|
||||
}
|
||||
|
||||
int userIdRank = 0;
|
||||
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||
operations.add(buildSecretUserIdOperations(context, keyRingRowId, userId, userIdRank));
|
||||
++userIdRank;
|
||||
}
|
||||
|
||||
try {
|
||||
context.getContentResolver().applyBatch(ApgContract.CONTENT_AUTHORITY_INTERNAL, operations);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "applyBatch failed!", e);
|
||||
} catch (OperationApplicationException e) {
|
||||
Log.e(Constants.TAG, "applyBatch failed!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
|
||||
*
|
||||
* @param context
|
||||
* @param keyRingRowId
|
||||
* @param key
|
||||
* @param rank
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private static ContentProviderOperation buildPublicKeyOperations(Context context,
|
||||
long keyRingRowId, PGPPublicKey key, int rank) throws IOException {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Keys.KEY_ID, key.getKeyID());
|
||||
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, PGPHelper.isSigningKey(key));
|
||||
values.put(Keys.CAN_ENCRYPT, PGPHelper.isEncryptionKey(key));
|
||||
values.put(Keys.IS_REVOKED, key.isRevoked());
|
||||
values.put(Keys.CREATION, PGPHelper.getCreationDate(key).getTime() / 1000);
|
||||
Date expiryDate = PGPHelper.getExpiryDate(key);
|
||||
if (expiryDate != null) {
|
||||
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
|
||||
}
|
||||
values.put(Keys.KEY_RING_ROW_ID, keyRingRowId);
|
||||
values.put(Keys.KEY_DATA, key.getEncoded());
|
||||
values.put(Keys.RANK, rank);
|
||||
|
||||
Uri uri = Keys.buildPublicKeysUri(Long.toString(keyRingRowId));
|
||||
|
||||
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing
|
||||
*
|
||||
* @param context
|
||||
* @param keyRingRowId
|
||||
* @param key
|
||||
* @param rank
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private static ContentProviderOperation buildPublicUserIdOperations(Context context,
|
||||
long keyRingRowId, String userId, int rank) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId);
|
||||
values.put(UserIds.USER_ID, userId);
|
||||
values.put(UserIds.RANK, rank);
|
||||
|
||||
Uri uri = UserIds.buildPublicUserIdsUri(Long.toString(keyRingRowId));
|
||||
|
||||
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build ContentProviderOperation to add PGPSecretKey to database corresponding to a keyRing
|
||||
*
|
||||
* @param context
|
||||
* @param keyRingRowId
|
||||
* @param key
|
||||
* @param rank
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private static ContentProviderOperation buildSecretKeyOperations(Context context,
|
||||
long keyRingRowId, PGPSecretKey key, int rank) throws IOException {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Keys.KEY_ID, key.getKeyID());
|
||||
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, PGPHelper.isSigningKey(key));
|
||||
values.put(Keys.CAN_ENCRYPT, PGPHelper.isEncryptionKey(key));
|
||||
values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked());
|
||||
values.put(Keys.CREATION, PGPHelper.getCreationDate(key).getTime() / 1000);
|
||||
Date expiryDate = PGPHelper.getExpiryDate(key);
|
||||
if (expiryDate != null) {
|
||||
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
|
||||
}
|
||||
values.put(Keys.KEY_RING_ROW_ID, keyRingRowId);
|
||||
values.put(Keys.KEY_DATA, key.getEncoded());
|
||||
values.put(Keys.RANK, rank);
|
||||
|
||||
Uri uri = Keys.buildSecretKeysUri(Long.toString(keyRingRowId));
|
||||
|
||||
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build ContentProviderOperation to add SecretUserIds to database corresponding to a keyRing
|
||||
*
|
||||
* @param context
|
||||
* @param keyRingRowId
|
||||
* @param key
|
||||
* @param rank
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private static ContentProviderOperation buildSecretUserIdOperations(Context context,
|
||||
long keyRingRowId, String userId, int rank) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId);
|
||||
values.put(UserIds.USER_ID, userId);
|
||||
values.put(UserIds.RANK, rank);
|
||||
|
||||
Uri uri = UserIds.buildSecretUserIdsUri(Long.toString(keyRingRowId));
|
||||
|
||||
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper method
|
||||
*
|
||||
* @param context
|
||||
* @param queryUri
|
||||
* @return
|
||||
*/
|
||||
private static Vector<Integer> getKeyRingsRowIds(Context context, Uri queryUri) {
|
||||
Cursor cursor = context.getContentResolver().query(queryUri, new String[] { KeyRings._ID },
|
||||
null, null, null);
|
||||
|
||||
Vector<Integer> keyIds = new Vector<Integer>();
|
||||
if (cursor != null) {
|
||||
int idCol = cursor.getColumnIndex(KeyRings._ID);
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
keyIds.add(cursor.getInt(idCol));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return keyIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves ids of all SecretKeyRings
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static Vector<Integer> getSecretKeyRingsRowIds(Context context) {
|
||||
Uri queryUri = KeyRings.buildSecretKeyRingsUri();
|
||||
return getKeyRingsRowIds(context, queryUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves ids of all PublicKeyRings
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static Vector<Integer> getPublicKeyRingsRowIds(Context context) {
|
||||
Uri queryUri = KeyRings.buildPublicKeyRingsUri();
|
||||
return getKeyRingsRowIds(context, queryUri);
|
||||
}
|
||||
|
||||
public static void deletePublicKeyRing(Context context, long rowId) {
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null);
|
||||
}
|
||||
|
||||
public static void deleteSecretKeyRing(Context context, long rowId) {
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
cr.delete(KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)), null, null);
|
||||
}
|
||||
|
||||
public static long getPublicMasterKeyId(Context context, long keyRingRowId) {
|
||||
Uri queryUri = KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowId));
|
||||
return getMasterKeyId(context, queryUri, keyRingRowId);
|
||||
}
|
||||
|
||||
public static long getSecretMasterKeyId(Context context, long keyRingRowId) {
|
||||
Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId));
|
||||
return getMasterKeyId(context, queryUri, keyRingRowId);
|
||||
}
|
||||
|
||||
private static long getMasterKeyId(Context context, Uri queryUri, long keyRingRowId) {
|
||||
String[] projection = new String[] { KeyRings.MASTER_KEY_ID };
|
||||
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
Cursor cursor = cr.query(queryUri, projection, null, null, null);
|
||||
|
||||
long masterKeyId = -1;
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int masterKeyIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
|
||||
|
||||
masterKeyId = cursor.getLong(masterKeyIdCol);
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return masterKeyId;
|
||||
}
|
||||
|
||||
}
|
||||
881
APG/src/org/thialfihar/android/apg/service/ApgIntentService.java
Normal file
881
APG/src/org/thialfihar/android/apg/service/ApgIntentService.java
Normal file
@@ -0,0 +1,881 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
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.ArrayList;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.FileHelper;
|
||||
import org.thialfihar.android.apg.helper.OtherHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPMain;
|
||||
import org.thialfihar.android.apg.helper.Preferences;
|
||||
import org.thialfihar.android.apg.helper.PGPMain.ApgGeneralException;
|
||||
import org.thialfihar.android.apg.helper.PGPConversionHelper;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.DataStream;
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
import org.thialfihar.android.apg.util.HkpKeyServer;
|
||||
import org.thialfihar.android.apg.util.InputData;
|
||||
import org.thialfihar.android.apg.util.KeyServer.KeyInfo;
|
||||
import org.thialfihar.android.apg.util.ProgressDialogUpdater;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
/**
|
||||
* This Service contains all important long lasting operations for APG. It receives Intents with
|
||||
* data from the activities or other apps, queues these intents, executes them, and stops itself
|
||||
* after doing them.
|
||||
*/
|
||||
public class ApgIntentService extends IntentService implements ProgressDialogUpdater {
|
||||
|
||||
/* extras that can be given by intent */
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
public static final String EXTRA_ACTION = "action";
|
||||
public static final String EXTRA_DATA = "data";
|
||||
|
||||
/* possible EXTRA_ACTIONs */
|
||||
public static final int ACTION_ENCRYPT_SIGN = 10;
|
||||
|
||||
public static final int ACTION_DECRYPT_VERIFY = 20;
|
||||
|
||||
public static final int ACTION_SAVE_KEYRING = 30;
|
||||
public static final int ACTION_GENERATE_KEY = 31;
|
||||
public static final int ACTION_GENERATE_DEFAULT_RSA_KEYS = 32;
|
||||
|
||||
public static final int ACTION_DELETE_FILE_SECURELY = 40;
|
||||
|
||||
public static final int ACTION_IMPORT_KEY = 50;
|
||||
public static final int ACTION_EXPORT_KEY = 51;
|
||||
|
||||
public static final int ACTION_UPLOAD_KEY = 60;
|
||||
public static final int ACTION_QUERY_KEY = 61;
|
||||
|
||||
public static final int ACTION_SIGN_KEY = 70;
|
||||
|
||||
/* keys for data bundle */
|
||||
|
||||
// encrypt, decrypt, import export
|
||||
public static final String TARGET = "target";
|
||||
// possible targets:
|
||||
public static final int TARGET_BYTES = 1;
|
||||
public static final int TARGET_FILE = 2;
|
||||
public static final int TARGET_STREAM = 3;
|
||||
|
||||
// encrypt
|
||||
public static final String SECRET_KEY_ID = "secretKeyId";
|
||||
public static final String USE_ASCII_AMOR = "useAsciiAmor";
|
||||
public static final String ENCRYPTION_KEYS_IDS = "encryptionKeysIds";
|
||||
public static final String COMPRESSION_ID = "compressionId";
|
||||
public static final String GENERATE_SIGNATURE = "generateSignature";
|
||||
public static final String SIGN_ONLY = "signOnly";
|
||||
public static final String MESSAGE_BYTES = "messageBytes";
|
||||
public static final String INPUT_FILE = "inputFile";
|
||||
public static final String OUTPUT_FILE = "outputFile";
|
||||
public static final String PROVIDER_URI = "providerUri";
|
||||
|
||||
// decrypt/verify
|
||||
public static final String SIGNED_ONLY = "signedOnly";
|
||||
public static final String RETURN_BYTES = "returnBinary";
|
||||
public static final String CIPHERTEXT_BYTES = "ciphertextBytes";
|
||||
public static final String ASSUME_SYMMETRIC = "assumeSymmetric";
|
||||
public static final String LOOKUP_UNKNOWN_KEY = "lookupUnknownKey";
|
||||
|
||||
// edit keys
|
||||
public static final String NEW_PASSPHRASE = "newPassphrase";
|
||||
public static final String CURRENT_PASSPHRASE = "currentPassphrase";
|
||||
public static final String USER_IDS = "userIds";
|
||||
public static final String KEYS = "keys";
|
||||
public static final String KEYS_USAGES = "keysUsages";
|
||||
public static final String MASTER_KEY_ID = "masterKeyId";
|
||||
|
||||
// generate key
|
||||
public static final String ALGORITHM = "algorithm";
|
||||
public static final String KEY_SIZE = "keySize";
|
||||
public static final String SYMMETRIC_PASSPHRASE = "passphrase";
|
||||
public static final String MASTER_KEY = "masterKey";
|
||||
|
||||
// delete file securely
|
||||
public static final String DELETE_FILE = "deleteFile";
|
||||
|
||||
// import key
|
||||
public static final String IMPORT_INPUT_STREAM = "importInputStream";
|
||||
public static final String IMPORT_FILENAME = "importFilename";
|
||||
public static final String IMPORT_BYTES = "importBytes";
|
||||
public static final String IMPORT_KEY_TYPE = "importKeyType";
|
||||
|
||||
// export key
|
||||
public static final String EXPORT_OUTPUT_STREAM = "exportOutputStream";
|
||||
public static final String EXPORT_FILENAME = "exportFilename";
|
||||
public static final String EXPORT_KEY_TYPE = "exportKeyType";
|
||||
public static final String EXPORT_ALL = "exportAll";
|
||||
public static final String EXPORT_KEY_RING_ID = "exportKeyRingId";
|
||||
|
||||
// upload key
|
||||
public static final String UPLOAD_KEY_SERVER = "uploadKeyServer";
|
||||
public static final String UPLOAD_KEY_KEYRING_ROW_ID = "uploadKeyRingId";
|
||||
|
||||
// query key
|
||||
public static final String QUERY_KEY_SERVER = "queryKeyServer";
|
||||
public static final String QUERY_KEY_TYPE = "queryKeyType";
|
||||
public static final String QUERY_KEY_STRING = "queryKeyString";
|
||||
public static final String QUERY_KEY_ID = "queryKeyId";
|
||||
|
||||
// sign key
|
||||
public static final String SIGN_KEY_MASTER_KEY_ID = "signKeyMasterKeyId";
|
||||
public static final String SIGN_KEY_PUB_KEY_ID = "signKeyPubKeyId";
|
||||
|
||||
/*
|
||||
* possible data keys as result send over messenger
|
||||
*/
|
||||
// keys
|
||||
public static final String RESULT_NEW_KEY = "newKey";
|
||||
public static final String RESULT_NEW_KEY2 = "newKey2";
|
||||
|
||||
// encrypt
|
||||
public static final String RESULT_SIGNATURE_BYTES = "signatureData";
|
||||
public static final String RESULT_SIGNATURE_STRING = "signatureText";
|
||||
public static final String RESULT_ENCRYPTED_STRING = "encryptedMessage";
|
||||
public static final String RESULT_ENCRYPTED_BYTES = "encryptedData";
|
||||
public static final String RESULT_URI = "resultUri";
|
||||
|
||||
// decrypt/verify
|
||||
public static final String RESULT_DECRYPTED_STRING = "decryptedMessage";
|
||||
public static final String RESULT_DECRYPTED_BYTES = "decryptedData";
|
||||
public static final String RESULT_SIGNATURE = "signature";
|
||||
public static final String RESULT_SIGNATURE_KEY_ID = "signatureKeyId";
|
||||
public static final String RESULT_SIGNATURE_USER_ID = "signatureUserId";
|
||||
|
||||
public static final String RESULT_SIGNATURE_SUCCESS = "signatureSuccess";
|
||||
public static final String RESULT_SIGNATURE_UNKNOWN = "signatureUnknown";
|
||||
public static final String RESULT_SIGNATURE_LOOKUP_KEY = "lookupKey";
|
||||
|
||||
// import
|
||||
public static final String RESULT_IMPORT_ADDED = "added";
|
||||
public static final String RESULT_IMPORT_UPDATED = "updated";
|
||||
public static final String RESULT_IMPORT_BAD = "bad";
|
||||
|
||||
// export
|
||||
public static final String RESULT_EXPORT = "exported";
|
||||
|
||||
// query
|
||||
public static final String RESULT_QUERY_KEY_KEY_DATA = "queryKeyKeyData";
|
||||
public static final String RESULT_QUERY_KEY_SEARCH_RESULT = "queryKeySearchResult";
|
||||
|
||||
Messenger mMessenger;
|
||||
|
||||
public ApgIntentService() {
|
||||
super("ApgService");
|
||||
}
|
||||
|
||||
/**
|
||||
* The IntentService calls this method from the default worker thread with the intent that
|
||||
* started the service. When this method returns, IntentService stops the service, as
|
||||
* appropriate.
|
||||
*/
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras == null) {
|
||||
Log.e(Constants.TAG, "Extras bundle is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(extras.containsKey(EXTRA_MESSENGER) || extras.containsKey(EXTRA_DATA) || extras
|
||||
.containsKey(EXTRA_ACTION))) {
|
||||
Log.e(Constants.TAG,
|
||||
"Extra bundle must contain a messenger, a data bundle, and an action!");
|
||||
return;
|
||||
}
|
||||
|
||||
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
|
||||
Bundle data = extras.getBundle(EXTRA_DATA);
|
||||
|
||||
OtherHelper.logDebugBundle(data, "EXTRA_DATA");
|
||||
|
||||
int action = extras.getInt(EXTRA_ACTION);
|
||||
|
||||
// execute action from extra bundle
|
||||
switch (action) {
|
||||
case ACTION_ENCRYPT_SIGN:
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
int target = data.getInt(TARGET);
|
||||
|
||||
long secretKeyId = data.getLong(SECRET_KEY_ID);
|
||||
String encryptionPassphrase = data.getString(SYMMETRIC_PASSPHRASE);
|
||||
|
||||
boolean useAsciiArmor = data.getBoolean(USE_ASCII_AMOR);
|
||||
long encryptionKeyIds[] = data.getLongArray(ENCRYPTION_KEYS_IDS);
|
||||
int compressionId = data.getInt(COMPRESSION_ID);
|
||||
boolean generateSignature = data.getBoolean(GENERATE_SIGNATURE);
|
||||
boolean signOnly = data.getBoolean(SIGN_ONLY);
|
||||
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
OutputStream outStream = null;
|
||||
String streamFilename = null;
|
||||
switch (target) {
|
||||
case TARGET_BYTES: /* encrypting bytes directly */
|
||||
byte[] bytes = data.getByteArray(MESSAGE_BYTES);
|
||||
|
||||
inStream = new ByteArrayInputStream(bytes);
|
||||
inLength = bytes.length;
|
||||
|
||||
inputData = new InputData(inStream, inLength);
|
||||
outStream = new ByteArrayOutputStream();
|
||||
|
||||
break;
|
||||
case TARGET_FILE: /* encrypting file */
|
||||
String inputFile = data.getString(INPUT_FILE);
|
||||
String outputFile = data.getString(OUTPUT_FILE);
|
||||
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(inputFile)
|
||||
|| !FileHelper.isStorageMounted(outputFile)) {
|
||||
throw new ApgGeneralException(
|
||||
getString(R.string.error_externalStorageNotReady));
|
||||
}
|
||||
|
||||
inStream = new FileInputStream(inputFile);
|
||||
File file = new File(inputFile);
|
||||
inLength = file.length();
|
||||
inputData = new InputData(inStream, inLength);
|
||||
|
||||
outStream = new FileOutputStream(outputFile);
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_STREAM: /* Encrypting stream from content uri */
|
||||
Uri providerUri = (Uri) data.getParcelable(PROVIDER_URI);
|
||||
|
||||
// InputStream
|
||||
InputStream in = getContentResolver().openInputStream(providerUri);
|
||||
inLength = PGPMain.getLengthOfStream(in);
|
||||
inputData = new InputData(in, inLength);
|
||||
|
||||
// OutputStream
|
||||
try {
|
||||
while (true) {
|
||||
streamFilename = PGPMain.generateRandomFilename(32);
|
||||
if (streamFilename == null) {
|
||||
throw new PGPMain.ApgGeneralException(
|
||||
"couldn't generate random file name");
|
||||
}
|
||||
openFileInput(streamFilename).close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// found a name that isn't used yet
|
||||
}
|
||||
outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new PGPMain.ApgGeneralException("No target choosen!");
|
||||
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
|
||||
if (generateSignature) {
|
||||
Log.d(Constants.TAG, "generating signature...");
|
||||
PGPMain.generateSignature(this, this, inputData, outStream, useAsciiArmor,
|
||||
false, secretKeyId, PassphraseCacheService.getCachedPassphrase(this,
|
||||
secretKeyId), Preferences.getPreferences(this)
|
||||
.getDefaultHashAlgorithm(), Preferences.getPreferences(this)
|
||||
.getForceV3Signatures());
|
||||
} else if (signOnly) {
|
||||
Log.d(Constants.TAG, "sign only...");
|
||||
PGPMain.signText(this, this, inputData, outStream, secretKeyId,
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences
|
||||
.getPreferences(this).getForceV3Signatures());
|
||||
} else {
|
||||
Log.d(Constants.TAG, "encrypt...");
|
||||
PGPMain.encryptAndSign(this, this, inputData, outStream, useAsciiArmor,
|
||||
compressionId, encryptionKeyIds, encryptionPassphrase, Preferences
|
||||
.getPreferences(this).getDefaultEncryptionAlgorithm(),
|
||||
secretKeyId,
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences
|
||||
.getPreferences(this).getForceV3Signatures(),
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
}
|
||||
|
||||
outStream.close();
|
||||
|
||||
/* Output */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
switch (target) {
|
||||
case TARGET_BYTES:
|
||||
if (useAsciiArmor) {
|
||||
String output = new String(
|
||||
((ByteArrayOutputStream) outStream).toByteArray());
|
||||
if (generateSignature) {
|
||||
resultData.putString(RESULT_SIGNATURE_STRING, output);
|
||||
} else {
|
||||
resultData.putString(RESULT_ENCRYPTED_STRING, output);
|
||||
}
|
||||
} else {
|
||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||
if (generateSignature) {
|
||||
resultData.putByteArray(RESULT_SIGNATURE_BYTES, output);
|
||||
} else {
|
||||
resultData.putByteArray(RESULT_ENCRYPTED_BYTES, output);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case TARGET_FILE:
|
||||
// nothing, file was written, just send okay
|
||||
|
||||
break;
|
||||
case TARGET_STREAM:
|
||||
String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||
resultData.putString(RESULT_URI, uri);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_DECRYPT_VERIFY:
|
||||
try {
|
||||
/* Input */
|
||||
int target = data.getInt(TARGET);
|
||||
|
||||
long secretKeyId = data.getLong(SECRET_KEY_ID);
|
||||
byte[] bytes = data.getByteArray(CIPHERTEXT_BYTES);
|
||||
boolean signedOnly = data.getBoolean(SIGNED_ONLY);
|
||||
boolean returnBytes = data.getBoolean(RETURN_BYTES);
|
||||
boolean assumeSymmetricEncryption = data.getBoolean(ASSUME_SYMMETRIC);
|
||||
|
||||
boolean lookupUnknownKey = data.getBoolean(LOOKUP_UNKNOWN_KEY);
|
||||
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
OutputStream outStream = null;
|
||||
String streamFilename = null;
|
||||
switch (target) {
|
||||
case TARGET_BYTES: /* decrypting bytes directly */
|
||||
inStream = new ByteArrayInputStream(bytes);
|
||||
inLength = bytes.length;
|
||||
|
||||
inputData = new InputData(inStream, inLength);
|
||||
outStream = new ByteArrayOutputStream();
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_FILE: /* decrypting file */
|
||||
String inputFile = data.getString(INPUT_FILE);
|
||||
String outputFile = data.getString(OUTPUT_FILE);
|
||||
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(inputFile)
|
||||
|| !FileHelper.isStorageMounted(outputFile)) {
|
||||
throw new ApgGeneralException(
|
||||
getString(R.string.error_externalStorageNotReady));
|
||||
}
|
||||
|
||||
// InputStream
|
||||
inLength = -1;
|
||||
inStream = new FileInputStream(inputFile);
|
||||
File file = new File(inputFile);
|
||||
inLength = file.length();
|
||||
inputData = new InputData(inStream, inLength);
|
||||
|
||||
// OutputStream
|
||||
outStream = new FileOutputStream(outputFile);
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_STREAM: /* decrypting stream from content uri */
|
||||
Uri providerUri = (Uri) data.getParcelable(PROVIDER_URI);
|
||||
|
||||
// InputStream
|
||||
InputStream in = getContentResolver().openInputStream(providerUri);
|
||||
inLength = PGPMain.getLengthOfStream(in);
|
||||
inputData = new InputData(in, inLength);
|
||||
|
||||
// OutputStream
|
||||
try {
|
||||
while (true) {
|
||||
streamFilename = PGPMain.generateRandomFilename(32);
|
||||
if (streamFilename == null) {
|
||||
throw new PGPMain.ApgGeneralException(
|
||||
"couldn't generate random file name");
|
||||
}
|
||||
openFileInput(streamFilename).close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// found a name that isn't used yet
|
||||
}
|
||||
outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new PGPMain.ApgGeneralException("No target choosen!");
|
||||
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
if (signedOnly) {
|
||||
resultData = PGPMain.verifyText(this, this, inputData, outStream,
|
||||
lookupUnknownKey);
|
||||
} else {
|
||||
resultData = PGPMain.decryptAndVerify(this, this, inputData, outStream,
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
|
||||
assumeSymmetricEncryption);
|
||||
}
|
||||
|
||||
outStream.close();
|
||||
|
||||
/* Output */
|
||||
|
||||
switch (target) {
|
||||
case TARGET_BYTES:
|
||||
if (returnBytes) {
|
||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||
resultData.putByteArray(RESULT_DECRYPTED_BYTES, output);
|
||||
} else {
|
||||
String output = new String(
|
||||
((ByteArrayOutputStream) outStream).toByteArray());
|
||||
resultData.putString(RESULT_DECRYPTED_STRING, output);
|
||||
}
|
||||
|
||||
break;
|
||||
case TARGET_FILE:
|
||||
// nothing, file was written, just send okay and verification bundle
|
||||
|
||||
break;
|
||||
case TARGET_STREAM:
|
||||
String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||
resultData.putString(RESULT_URI, uri);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_SAVE_KEYRING:
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
String oldPassPhrase = data.getString(CURRENT_PASSPHRASE);
|
||||
String newPassPhrase = data.getString(NEW_PASSPHRASE);
|
||||
if (newPassPhrase == null) {
|
||||
newPassPhrase = oldPassPhrase;
|
||||
}
|
||||
ArrayList<String> userIds = data.getStringArrayList(USER_IDS);
|
||||
ArrayList<PGPSecretKey> keys = PGPConversionHelper.BytesToPGPSecretKeyList(data
|
||||
.getByteArray(KEYS));
|
||||
ArrayList<Integer> keysUsages = data.getIntegerArrayList(KEYS_USAGES);
|
||||
long masterKeyId = data.getLong(MASTER_KEY_ID);
|
||||
|
||||
/* Operation */
|
||||
PGPMain.buildSecretKey(this, userIds, keys, keysUsages, masterKeyId, oldPassPhrase,
|
||||
newPassPhrase, this);
|
||||
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);
|
||||
|
||||
/* Output */
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_GENERATE_KEY:
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
int algorithm = data.getInt(ALGORITHM);
|
||||
String passphrase = data.getString(SYMMETRIC_PASSPHRASE);
|
||||
int keysize = data.getInt(KEY_SIZE);
|
||||
PGPSecretKey masterKey = null;
|
||||
if (data.containsKey(MASTER_KEY)) {
|
||||
masterKey = PGPConversionHelper.BytesToPGPSecretKey(data
|
||||
.getByteArray(MASTER_KEY));
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
PGPSecretKeyRing newKeyRing = PGPMain.createKey(this, algorithm, keysize,
|
||||
passphrase, masterKey);
|
||||
|
||||
/* Output */
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putByteArray(RESULT_NEW_KEY,
|
||||
PGPConversionHelper.PGPSecretKeyRingToBytes(newKeyRing));
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_GENERATE_DEFAULT_RSA_KEYS:
|
||||
// generate one RSA 2048 key for signing and one subkey for encrypting!
|
||||
try {
|
||||
/* Input */
|
||||
String passphrase = data.getString(SYMMETRIC_PASSPHRASE);
|
||||
|
||||
/* Operation */
|
||||
PGPSecretKeyRing masterKeyRing = PGPMain.createKey(this, Id.choice.algorithm.rsa,
|
||||
2048, passphrase, null);
|
||||
|
||||
PGPSecretKeyRing subKeyRing = PGPMain.createKey(this, Id.choice.algorithm.rsa,
|
||||
2048, passphrase, masterKeyRing.getSecretKey());
|
||||
|
||||
/* Output */
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putByteArray(RESULT_NEW_KEY,
|
||||
PGPConversionHelper.PGPSecretKeyRingToBytes(masterKeyRing));
|
||||
resultData.putByteArray(RESULT_NEW_KEY2,
|
||||
PGPConversionHelper.PGPSecretKeyRingToBytes(subKeyRing));
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_DELETE_FILE_SECURELY:
|
||||
try {
|
||||
/* Input */
|
||||
String deleteFile = data.getString(DELETE_FILE);
|
||||
|
||||
/* Operation */
|
||||
try {
|
||||
PGPMain.deleteFileSecurely(this, this, new File(deleteFile));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new PGPMain.ApgGeneralException(getString(R.string.error_fileNotFound,
|
||||
deleteFile));
|
||||
} catch (IOException e) {
|
||||
throw new PGPMain.ApgGeneralException(getString(
|
||||
R.string.error_fileDeleteFailed, deleteFile));
|
||||
}
|
||||
|
||||
/* Output */
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_IMPORT_KEY:
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
int target = data.getInt(TARGET);
|
||||
|
||||
int keyType = Id.type.public_key;
|
||||
if (data.containsKey(IMPORT_KEY_TYPE)) {
|
||||
keyType = data.getInt(IMPORT_KEY_TYPE);
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
switch (target) {
|
||||
case TARGET_BYTES: /* import key from bytes directly */
|
||||
byte[] bytes = data.getByteArray(IMPORT_BYTES);
|
||||
|
||||
inStream = new ByteArrayInputStream(bytes);
|
||||
inLength = bytes.length;
|
||||
|
||||
inputData = new InputData(inStream, inLength);
|
||||
|
||||
break;
|
||||
case TARGET_FILE: /* import key from file */
|
||||
String inputFile = data.getString(IMPORT_FILENAME);
|
||||
|
||||
inStream = new FileInputStream(inputFile);
|
||||
File file = new File(inputFile);
|
||||
inLength = file.length();
|
||||
inputData = new InputData(inStream, inLength);
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_STREAM:
|
||||
// TODO: not implemented
|
||||
break;
|
||||
}
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
resultData = PGPMain.importKeyRings(this, keyType, inputData, this);
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_EXPORT_KEY:
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
int keyType = Id.type.public_key;
|
||||
if (data.containsKey(EXPORT_KEY_TYPE)) {
|
||||
keyType = data.getInt(EXPORT_KEY_TYPE);
|
||||
}
|
||||
|
||||
String outputFile = data.getString(EXPORT_FILENAME);
|
||||
|
||||
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
||||
int keyRingId = -1;
|
||||
if (!exportAll) {
|
||||
keyRingId = data.getInt(EXPORT_KEY_RING_ID);
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(outputFile)) {
|
||||
throw new ApgGeneralException(getString(R.string.error_externalStorageNotReady));
|
||||
}
|
||||
|
||||
// OutputStream
|
||||
FileOutputStream outStream = new FileOutputStream(outputFile);
|
||||
|
||||
Vector<Integer> keyRingIds = new Vector<Integer>();
|
||||
if (exportAll) {
|
||||
if (keyType == Id.type.public_key) {
|
||||
keyRingIds = ProviderHelper.getPublicKeyRingsRowIds(this);
|
||||
} else {
|
||||
keyRingIds = ProviderHelper.getSecretKeyRingsRowIds(this);
|
||||
}
|
||||
} else {
|
||||
keyRingIds.add(keyRingId);
|
||||
}
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
resultData = PGPMain.exportKeyRings(this, keyRingIds, outStream, this);
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_UPLOAD_KEY:
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
int keyRingRowId = data.getInt(UPLOAD_KEY_KEYRING_ROW_ID);
|
||||
String keyServer = data.getString(UPLOAD_KEY_SERVER);
|
||||
|
||||
/* Operation */
|
||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
||||
|
||||
PGPPublicKeyRing keyring = ProviderHelper.getPGPPublicKeyRingByRowId(this, keyRingRowId);
|
||||
if (keyring != null) {
|
||||
boolean uploaded = PGPMain.uploadKeyRingToServer(server,
|
||||
(PGPPublicKeyRing) keyring);
|
||||
if (!uploaded) {
|
||||
throw new ApgGeneralException("Unable to export key to selected server");
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_QUERY_KEY:
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
int queryType = data.getInt(QUERY_KEY_TYPE);
|
||||
String keyServer = data.getString(QUERY_KEY_SERVER);
|
||||
|
||||
String queryString = data.getString(QUERY_KEY_STRING);
|
||||
long keyId = data.getLong(QUERY_KEY_ID);
|
||||
|
||||
/* Operation */
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
||||
if (queryType == Id.keyserver.search) {
|
||||
ArrayList<KeyInfo> searchResult = server.search(queryString);
|
||||
|
||||
resultData.putParcelableArrayList(RESULT_QUERY_KEY_SEARCH_RESULT, searchResult);
|
||||
} else if (queryType == Id.keyserver.get) {
|
||||
String keyData = server.get(keyId);
|
||||
|
||||
resultData.putString(RESULT_QUERY_KEY_KEY_DATA, keyData);
|
||||
}
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_SIGN_KEY:
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
long masterKeyId = data.getLong(SIGN_KEY_MASTER_KEY_ID);
|
||||
long pubKeyId = data.getLong(SIGN_KEY_PUB_KEY_ID);
|
||||
|
||||
/* Operation */
|
||||
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||
masterKeyId);
|
||||
|
||||
PGPPublicKeyRing signedPubKeyRing = PGPMain.signKey(this, masterKeyId, pubKeyId,
|
||||
signaturePassPhrase);
|
||||
|
||||
// store the signed key in our local cache
|
||||
int retval = PGPMain.storeKeyRingInCache(this, signedPubKeyRing);
|
||||
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
||||
throw new ApgGeneralException("Failed to store signed key in local cache");
|
||||
}
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendErrorToHandler(Exception e) {
|
||||
Log.e(Constants.TAG, "ApgService Exception: ", e);
|
||||
e.printStackTrace();
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putString(ApgIntentServiceHandler.DATA_ERROR, e.getMessage());
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_EXCEPTION, null, data);
|
||||
}
|
||||
|
||||
private void sendMessageToHandler(Integer arg1, Integer arg2, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = arg1;
|
||||
if (arg2 != null) {
|
||||
msg.arg2 = arg2;
|
||||
}
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessageToHandler(Integer arg1, Bundle data) {
|
||||
sendMessageToHandler(arg1, null, data);
|
||||
}
|
||||
|
||||
private void sendMessageToHandler(Integer arg1) {
|
||||
sendMessageToHandler(arg1, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set progress of ProgressDialog by sending message to handler on UI thread
|
||||
*/
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
|
||||
+ max);
|
||||
|
||||
Bundle data = new Bundle();
|
||||
if (message != null) {
|
||||
data.putString(ApgIntentServiceHandler.DATA_MESSAGE, message);
|
||||
}
|
||||
data.putInt(ApgIntentServiceHandler.DATA_PROGRESS, progress);
|
||||
data.putInt(ApgIntentServiceHandler.DATA_PROGRESS_MAX, max);
|
||||
|
||||
sendMessageToHandler(ApgIntentServiceHandler.MESSAGE_UPDATE_PROGRESS, null, data);
|
||||
}
|
||||
|
||||
public void setProgress(int resourceId, int progress, int max) {
|
||||
setProgress(getString(resourceId), progress, max);
|
||||
}
|
||||
|
||||
public void setProgress(int progress, int max) {
|
||||
setProgress(null, progress, max);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.ui.dialog.ProgressDialogFragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class ApgIntentServiceHandler extends Handler {
|
||||
|
||||
// possible messages send from this service to handler on ui
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
public static final int MESSAGE_EXCEPTION = 2;
|
||||
public static final int MESSAGE_UPDATE_PROGRESS = 3;
|
||||
|
||||
// possible data keys for messages
|
||||
public static final String DATA_ERROR = "error";
|
||||
public static final String DATA_PROGRESS = "progress";
|
||||
public static final String DATA_PROGRESS_MAX = "max";
|
||||
public static final String DATA_MESSAGE = "message";
|
||||
public static final String DATA_MESSAGE_ID = "message_id";
|
||||
|
||||
Activity mActivity;
|
||||
ProgressDialogFragment mProgressDialogFragment;
|
||||
|
||||
public ApgIntentServiceHandler(Activity activity) {
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
public ApgIntentServiceHandler(Activity activity, ProgressDialogFragment progressDialogFragment) {
|
||||
this.mActivity = activity;
|
||||
this.mProgressDialogFragment = progressDialogFragment;
|
||||
}
|
||||
|
||||
public ApgIntentServiceHandler(Activity activity, int progressDialogMessageId, int progressDialogStyle) {
|
||||
this.mActivity = activity;
|
||||
this.mProgressDialogFragment = ProgressDialogFragment.newInstance(progressDialogMessageId,
|
||||
progressDialogStyle);
|
||||
}
|
||||
|
||||
public void showProgressDialog(FragmentActivity activity) {
|
||||
mProgressDialogFragment.show(activity.getSupportFragmentManager(), "progressDialog");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
switch (message.arg1) {
|
||||
case MESSAGE_OKAY:
|
||||
mProgressDialogFragment.dismiss();
|
||||
|
||||
break;
|
||||
|
||||
case MESSAGE_EXCEPTION:
|
||||
mProgressDialogFragment.dismiss();
|
||||
|
||||
// show error from service
|
||||
if (data.containsKey(DATA_ERROR)) {
|
||||
Toast.makeText(mActivity,
|
||||
mActivity.getString(R.string.errorMessage, data.getString(DATA_ERROR)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MESSAGE_UPDATE_PROGRESS:
|
||||
if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) {
|
||||
|
||||
// update progress from service
|
||||
if (data.containsKey(DATA_MESSAGE)) {
|
||||
mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
|
||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||
} else if (data.containsKey(DATA_MESSAGE_ID)) {
|
||||
mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
|
||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||
} else {
|
||||
mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
|
||||
data.getInt(DATA_PROGRESS_MAX));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
326
APG/src/org/thialfihar/android/apg/service/ApgService.java
Normal file
326
APG/src/org/thialfihar/android/apg/service/ApgService.java
Normal file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.PGPMain;
|
||||
import org.thialfihar.android.apg.helper.PGPMain.ApgGeneralException;
|
||||
import org.thialfihar.android.apg.util.InputData;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
*
|
||||
* - is this service thread safe? Probably not!
|
||||
*
|
||||
*/
|
||||
public class ApgService extends Service {
|
||||
Context mContext;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mContext = this;
|
||||
Log.d(Constants.TAG, "ApgService, onCreate()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.d(Constants.TAG, "ApgService, onDestroy()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void encryptAndSignImplementation(byte[] inputBytes, String inputUri, boolean useAsciiArmor,
|
||||
int compression, long[] encryptionKeyIds, String encryptionPassphrase,
|
||||
int symmetricEncryptionAlgorithm, long signatureKeyId, int signatureHashAlgorithm,
|
||||
boolean signatureForceV3, String signaturePassphrase, IApgEncryptDecryptHandler handler)
|
||||
throws RemoteException {
|
||||
|
||||
try {
|
||||
// TODO use inputUri
|
||||
|
||||
// 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?
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData input = new InputData(inputStream, inputLength);
|
||||
|
||||
OutputStream output = new ByteArrayOutputStream();
|
||||
|
||||
PGPMain.encryptAndSign(mContext, null, input, output, useAsciiArmor, compression,
|
||||
encryptionKeyIds, encryptionPassphrase, symmetricEncryptionAlgorithm,
|
||||
signatureKeyId, signatureHashAlgorithm, signatureForceV3, signaturePassphrase);
|
||||
|
||||
output.close();
|
||||
|
||||
// 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());
|
||||
// }
|
||||
|
||||
byte[] outputBytes = ((ByteArrayOutputStream) output).toByteArray();
|
||||
|
||||
// return over handler on client side
|
||||
handler.onSuccessEncrypt(outputBytes, null);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "ApgService, Exception!", e);
|
||||
|
||||
try {
|
||||
handler.onException(getExceptionId(e), e.getMessage());
|
||||
} catch (Exception t) {
|
||||
Log.e(Constants.TAG, "Error returning exception to client", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void decryptAndVerifyImplementation(byte[] inputBytes, String inputUri,
|
||||
String passphrase, boolean assumeSymmetric, IApgEncryptDecryptHandler handler)
|
||||
throws RemoteException {
|
||||
|
||||
try {
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData inputData = new InputData(inputStream, inputLength);
|
||||
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
Bundle outputBundle = PGPMain.decryptAndVerify(mContext, null, inputData, outputStream,
|
||||
passphrase, assumeSymmetric);
|
||||
|
||||
outputStream.close();
|
||||
|
||||
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
|
||||
|
||||
// get signature informations from bundle
|
||||
boolean signature = outputBundle.getBoolean(ApgIntentService.RESULT_SIGNATURE);
|
||||
long signatureKeyId = outputBundle.getLong(ApgIntentService.RESULT_SIGNATURE_KEY_ID);
|
||||
String signatureUserId = outputBundle
|
||||
.getString(ApgIntentService.RESULT_SIGNATURE_USER_ID);
|
||||
boolean signatureSuccess = outputBundle
|
||||
.getBoolean(ApgIntentService.RESULT_SIGNATURE_SUCCESS);
|
||||
boolean signatureUnknown = outputBundle
|
||||
.getBoolean(ApgIntentService.RESULT_SIGNATURE_UNKNOWN);
|
||||
|
||||
// return over handler on client side
|
||||
handler.onSuccessDecrypt(outputBytes, null, signature, signatureKeyId, signatureUserId,
|
||||
signatureSuccess, signatureUnknown);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "ApgService, Exception!", e);
|
||||
|
||||
try {
|
||||
handler.onException(getExceptionId(e), e.getMessage());
|
||||
} catch (Exception t) {
|
||||
Log.e(Constants.TAG, "Error returning exception to client", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getDecryptionKeyImplementation(byte[] inputBytes, String inputUri,
|
||||
IApgHelperHandler handler) {
|
||||
|
||||
// TODO: implement inputUri
|
||||
|
||||
try {
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
|
||||
long secretKeyId = Id.key.none;
|
||||
boolean symmetric;
|
||||
|
||||
try {
|
||||
secretKeyId = PGPMain.getDecryptionKeyId(ApgService.this, inputStream);
|
||||
if (secretKeyId == Id.key.none) {
|
||||
throw new ApgGeneralException(getString(R.string.error_noSecretKeyFound));
|
||||
}
|
||||
symmetric = false;
|
||||
} catch (PGPMain.NoAsymmetricEncryptionException e) {
|
||||
secretKeyId = Id.key.symmetric;
|
||||
if (!PGPMain.hasSymmetricEncryption(ApgService.this, inputStream)) {
|
||||
throw new ApgGeneralException(getString(R.string.error_noKnownEncryptionFound));
|
||||
}
|
||||
symmetric = true;
|
||||
}
|
||||
|
||||
handler.onSuccessGetDecryptionKey(secretKeyId, symmetric);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "ApgService, Exception!", e);
|
||||
|
||||
try {
|
||||
handler.onException(getExceptionId(e), e.getMessage());
|
||||
} catch (Exception t) {
|
||||
Log.e(Constants.TAG, "Error returning exception to client", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the implementation of the interface IApgService. All methods are oneway, meaning
|
||||
* asynchronous and return to the client using IApgHandler.
|
||||
*
|
||||
* The real PGP code is located in PGPMain.
|
||||
*/
|
||||
private final IApgService.Stub mBinder = new IApgService.Stub() {
|
||||
|
||||
@Override
|
||||
public void encryptAsymmetric(byte[] inputBytes, String inputUri, boolean useAsciiArmor,
|
||||
int compression, long[] encryptionKeyIds, int symmetricEncryptionAlgorithm,
|
||||
IApgEncryptDecryptHandler handler) throws RemoteException {
|
||||
|
||||
encryptAndSignImplementation(inputBytes, inputUri, useAsciiArmor, compression,
|
||||
encryptionKeyIds, null, symmetricEncryptionAlgorithm, Id.key.none, 0, false,
|
||||
null, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encryptSymmetric(byte[] inputBytes, String inputUri, boolean useAsciiArmor,
|
||||
int compression, String encryptionPassphrase, int symmetricEncryptionAlgorithm,
|
||||
IApgEncryptDecryptHandler handler) throws RemoteException {
|
||||
|
||||
encryptAndSignImplementation(inputBytes, inputUri, useAsciiArmor, compression, null,
|
||||
encryptionPassphrase, symmetricEncryptionAlgorithm, Id.key.none, 0, false,
|
||||
null, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encryptAndSignAsymmetric(byte[] inputBytes, String inputUri,
|
||||
boolean useAsciiArmor, int compression, long[] encryptionKeyIds,
|
||||
int symmetricEncryptionAlgorithm, long signatureKeyId, int signatureHashAlgorithm,
|
||||
boolean signatureForceV3, String signaturePassphrase,
|
||||
IApgEncryptDecryptHandler handler) throws RemoteException {
|
||||
|
||||
encryptAndSignImplementation(inputBytes, inputUri, useAsciiArmor, compression,
|
||||
encryptionKeyIds, null, symmetricEncryptionAlgorithm, signatureKeyId,
|
||||
signatureHashAlgorithm, signatureForceV3, signaturePassphrase, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encryptAndSignSymmetric(byte[] inputBytes, String inputUri,
|
||||
boolean useAsciiArmor, int compression, String encryptionPassphrase,
|
||||
int symmetricEncryptionAlgorithm, long signatureKeyId, int signatureHashAlgorithm,
|
||||
boolean signatureForceV3, String signaturePassphrase,
|
||||
IApgEncryptDecryptHandler handler) throws RemoteException {
|
||||
|
||||
encryptAndSignImplementation(inputBytes, inputUri, useAsciiArmor, compression, null,
|
||||
encryptionPassphrase, symmetricEncryptionAlgorithm, signatureKeyId,
|
||||
signatureHashAlgorithm, signatureForceV3, signaturePassphrase, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decryptAndVerifyAsymmetric(byte[] inputBytes, String inputUri,
|
||||
String keyPassphrase, IApgEncryptDecryptHandler handler) throws RemoteException {
|
||||
|
||||
decryptAndVerifyImplementation(inputBytes, inputUri, keyPassphrase, false, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decryptAndVerifySymmetric(byte[] inputBytes, String inputUri,
|
||||
String encryptionPassphrase, IApgEncryptDecryptHandler handler)
|
||||
throws RemoteException {
|
||||
|
||||
decryptAndVerifyImplementation(inputBytes, inputUri, encryptionPassphrase, true,
|
||||
handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDecryptionKey(byte[] inputBytes, String inputUri, IApgHelperHandler handler)
|
||||
throws RemoteException {
|
||||
|
||||
getDecryptionKeyImplementation(inputBytes, inputUri, handler);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* As we can not throw an exception through Android RPC, we assign identifiers to the exception
|
||||
* types.
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
private int getExceptionId(Exception e) {
|
||||
if (e instanceof NoSuchProviderException) {
|
||||
return 0;
|
||||
} else if (e instanceof NoSuchAlgorithmException) {
|
||||
return 1;
|
||||
} else if (e instanceof SignatureException) {
|
||||
return 2;
|
||||
} else if (e instanceof IOException) {
|
||||
return 3;
|
||||
} else if (e instanceof ApgGeneralException) {
|
||||
return 4;
|
||||
} else if (e instanceof PGPException) {
|
||||
return 5;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
interface IApgEncryptDecryptHandler {
|
||||
/**
|
||||
* Either output or streamUri is given. One of them is null
|
||||
*
|
||||
*/
|
||||
oneway void onSuccessEncrypt(in byte[] outputBytes, in String outputUri);
|
||||
|
||||
oneway void onSuccessDecrypt(in byte[] outputBytes, in String outputUri, in boolean signature,
|
||||
in long signatureKeyId, in String signatureUserId, in boolean signatureSuccess,
|
||||
in boolean signatureUnknown);
|
||||
|
||||
|
||||
oneway void onException(in int exceptionNumber, in String message);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
interface IApgHelperHandler {
|
||||
|
||||
oneway void onSuccessGetDecryptionKey(in long secretKeyId, in boolean symmetric);
|
||||
|
||||
|
||||
oneway void onException(in int exceptionNumber, in String message);
|
||||
}
|
||||
146
APG/src/org/thialfihar/android/apg/service/IApgService.aidl
Normal file
146
APG/src/org/thialfihar/android/apg/service/IApgService.aidl
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import org.thialfihar.android.apg.service.IApgEncryptDecryptHandler;
|
||||
import org.thialfihar.android.apg.service.IApgSignVerifyHandler;
|
||||
import org.thialfihar.android.apg.service.IApgHelperHandler;
|
||||
|
||||
/**
|
||||
* All methods are oneway, which means they are asynchronous and non-blocking.
|
||||
* Results are returned into given Handler, which has to be implemented on client side.
|
||||
*/
|
||||
interface IApgService {
|
||||
|
||||
/**
|
||||
* Encrypt
|
||||
*
|
||||
* Either inputBytes or inputUri is given, the other should be null.
|
||||
*
|
||||
* @param inputBytes
|
||||
* Byte array you want to encrypt
|
||||
* @param inputUri
|
||||
* Blob in ContentProvider you want to encrypt
|
||||
* @param useAsciiArmor
|
||||
* Convert bytes to ascii armored text to guard against encoding problems
|
||||
* @param compression
|
||||
* Compression: 0x21070001: none, 1: Zip, 2: Zlib, 3: BZip2
|
||||
* @param encryptionKeyIds
|
||||
* Ids of public keys used for encryption
|
||||
* @param symmetricEncryptionAlgorithm
|
||||
* 7: AES-128, 8: AES-192, 9: AES-256, 4: Blowfish, 10: Twofish, 3: CAST5,
|
||||
* 6: DES, 2: Triple DES, 1: IDEA
|
||||
* @param handler
|
||||
* Results are returned to this IApgEncryptDecryptHandler Handler
|
||||
* to onSuccessEncrypt(in byte[] output), after successful encryption
|
||||
*/
|
||||
oneway void encryptAsymmetric(in byte[] inputBytes, in String inputUri, in boolean useAsciiArmor,
|
||||
in int compression, in long[] encryptionKeyIds, in int symmetricEncryptionAlgorithm,
|
||||
in IApgEncryptDecryptHandler handler);
|
||||
|
||||
/**
|
||||
* Same as encryptAsymmetric but using a passphrase for symmetric encryption
|
||||
*
|
||||
* @param encryptionPassphrase
|
||||
* Passphrase for direct symmetric encryption using symmetricEncryptionAlgorithm
|
||||
*/
|
||||
oneway void encryptSymmetric(in byte[] inputBytes, in String inputUri, in boolean useAsciiArmor,
|
||||
in int compression, in String encryptionPassphrase, in int symmetricEncryptionAlgorithm,
|
||||
in IApgEncryptDecryptHandler handler);
|
||||
|
||||
/**
|
||||
* Encrypt and sign
|
||||
*
|
||||
* Either inputBytes or inputUri is given, the other should be null.
|
||||
*
|
||||
* @param inputBytes
|
||||
* Byte array you want to encrypt
|
||||
* @param inputUri
|
||||
* Blob in ContentProvider you want to encrypt
|
||||
* @param useAsciiArmor
|
||||
* Convert bytes to ascii armored text to guard against encoding problems
|
||||
* @param compression
|
||||
* Compression: 0x21070001: none, 1: Zip, 2: Zlib, 3: BZip2
|
||||
* @param encryptionKeyIds
|
||||
* Ids of public keys used for encryption
|
||||
* @param symmetricEncryptionAlgorithm
|
||||
* 7: AES-128, 8: AES-192, 9: AES-256, 4: Blowfish, 10: Twofish, 3: CAST5,
|
||||
* 6: DES, 2: Triple DES, 1: IDEA
|
||||
* @param signatureKeyId
|
||||
* Key id of key to sign with
|
||||
* @param signatureHashAlgorithm
|
||||
* 1: MD5, 3: RIPEMD-160, 2: SHA-1, 11: SHA-224, 8: SHA-256, 9: SHA-384,
|
||||
* 10: SHA-512
|
||||
* @param signatureForceV3
|
||||
* Force V3 signatures
|
||||
* @param signaturePassphrase
|
||||
* Passphrase to unlock signature key
|
||||
* @param handler
|
||||
* Results are returned to this IApgEncryptDecryptHandler Handler
|
||||
* to onSuccessEncrypt(in byte[] output), after successful encryption and signing
|
||||
*/
|
||||
oneway void encryptAndSignAsymmetric(in byte[] inputBytes, in String inputUri,
|
||||
in boolean useAsciiArmor, in int compression, in long[] encryptionKeyIds,
|
||||
in int symmetricEncryptionAlgorithm, in long signatureKeyId, in int signatureHashAlgorithm,
|
||||
in boolean signatureForceV3, in String signaturePassphrase,
|
||||
in IApgEncryptDecryptHandler handler);
|
||||
|
||||
/**
|
||||
* Same as encryptAndSignAsymmetric but using a passphrase for symmetric encryption
|
||||
*
|
||||
* @param encryptionPassphrase
|
||||
* Passphrase for direct symmetric encryption using symmetricEncryptionAlgorithm
|
||||
*/
|
||||
oneway void encryptAndSignSymmetric(in byte[] inputBytes, in String inputUri,
|
||||
in boolean useAsciiArmor, in int compression, in String encryptionPassphrase,
|
||||
in int symmetricEncryptionAlgorithm, in long signatureKeyId, in int signatureHashAlgorithm,
|
||||
in boolean signatureForceV3, in String signaturePassphrase,
|
||||
in IApgEncryptDecryptHandler handler);
|
||||
|
||||
/**
|
||||
* Decrypts and verifies given input bytes. If no signature is present this method
|
||||
* will only decrypt.
|
||||
*
|
||||
* @param inputBytes
|
||||
* Byte array you want to decrypt and verify
|
||||
* @param inputUri
|
||||
* Blob in ContentProvider you want to decrypt and verify
|
||||
* @param keyPassphrase
|
||||
* Passphrase to unlock secret key for decryption.
|
||||
* @param handler
|
||||
* Handler where to return results to after successful encryption
|
||||
*/
|
||||
oneway void decryptAndVerifyAsymmetric(in byte[] inputBytes, in String inputUri,
|
||||
in String keyPassphrase, in IApgEncryptDecryptHandler handler);
|
||||
|
||||
/**
|
||||
* Same as decryptAndVerifyAsymmetric but for symmetric decryption.
|
||||
*
|
||||
* @param encryptionPassphrase
|
||||
* Passphrase to decrypt
|
||||
*/
|
||||
oneway void decryptAndVerifySymmetric(in byte[] inputBytes, in String inputUri,
|
||||
in String encryptionPassphrase, in IApgEncryptDecryptHandler handler);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
oneway void getDecryptionKey(in byte[] inputBytes, in String inputUri,
|
||||
in IApgHelperHandler handler);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
interface IApgSignVerifyHandler {
|
||||
oneway void onSuccessSign(in byte[] outputBytes, in String outputUri);
|
||||
|
||||
oneway void onSuccessVerify(in boolean signature, in long signatureKeyId,
|
||||
in String signatureUserId, in boolean signatureSuccess, in boolean signatureUnknown);
|
||||
|
||||
|
||||
oneway void onException(in int exceptionNumber, in String message);
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.helper.Preferences;
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class PassphraseCacheService extends Service {
|
||||
public static final String TAG = Constants.TAG + ": PassphraseCacheService";
|
||||
|
||||
public static final String BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_SERVICE";
|
||||
|
||||
public static final String EXTRA_TTL = "ttl";
|
||||
public static final String EXTRA_KEY_ID = "keyId";
|
||||
public static final String EXTRA_PASSPHRASE = "passphrase";
|
||||
|
||||
private static final int REQUEST_ID = 0;
|
||||
private static final long DEFAULT_TTL = 15;
|
||||
|
||||
private BroadcastReceiver mIntentReceiver;
|
||||
|
||||
// This is static to be easily retrieved by getCachedPassphrase() without the need of callback
|
||||
// functions
|
||||
private static HashMap<Long, String> mPassphraseCache = new HashMap<Long, String>();
|
||||
|
||||
/**
|
||||
* This caches a new passphrase by sending a new command to the service. An android service is
|
||||
* only run once. Thus, when the service is already started, new commands just add new events to
|
||||
* the alarm manager for new passphrases to let them timeout in the future.
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @param passphrase
|
||||
*/
|
||||
public static void addCachedPassphrase(Context context, long keyId, String passphrase) {
|
||||
Log.d(TAG, "cacheNewPassphrase() for " + keyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassPhraseCacheTtl());
|
||||
intent.putExtra(EXTRA_PASSPHRASE, passphrase);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cached passphrase from memory
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
public static String getCachedPassphrase(Context context, long keyId) {
|
||||
// try to get master key id which is used as an identifier for cached passphrases
|
||||
long masterKeyId = keyId;
|
||||
if (masterKeyId != Id.key.symmetric) {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, keyId);
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
PGPSecretKey masterKey = PGPHelper.getMasterKey(keyRing);
|
||||
if (masterKey == null) {
|
||||
return null;
|
||||
}
|
||||
masterKeyId = masterKey.getKeyID();
|
||||
}
|
||||
|
||||
// get cached passphrase
|
||||
String cachedPassphrase = mPassphraseCache.get(masterKeyId);
|
||||
if (cachedPassphrase == null) {
|
||||
return null;
|
||||
}
|
||||
// set it again to reset the cache life cycle
|
||||
Log.d(TAG, "Cache passphrase again when getting it!");
|
||||
addCachedPassphrase(context, masterKeyId, cachedPassphrase);
|
||||
|
||||
return cachedPassphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register BroadcastReceiver that is unregistered when service is destroyed. This
|
||||
* BroadcastReceiver hears on intents with ACTION_PASSPHRASE_CACHE_SERVICE to then timeout
|
||||
* specific passphrases in memory.
|
||||
*/
|
||||
private void registerReceiver() {
|
||||
if (mIntentReceiver == null) {
|
||||
mIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
Log.d(TAG, "Received broadcast...");
|
||||
|
||||
if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
timeout(context, keyId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
|
||||
registerReceiver(mIntentReceiver, filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pending intent that is executed by alarm manager to time out a specific passphrase
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
private static PendingIntent buildIntent(Context context, long keyId) {
|
||||
Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_ID, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
return sender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.d(TAG, "onCreate()");
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed when service is started by intent
|
||||
*/
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(TAG, "onStartCommand()");
|
||||
|
||||
// register broadcastreceiver
|
||||
registerReceiver();
|
||||
|
||||
if (intent != null) {
|
||||
long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE);
|
||||
|
||||
Log.d(TAG, "Received intent in onStartCommand() with keyId: " + keyId + ", ttl: " + ttl);
|
||||
|
||||
// add keyId and passphrase to memory
|
||||
mPassphraseCache.put(keyId, passphrase);
|
||||
|
||||
// register new alarm with keyId for this passphrase
|
||||
long triggerTime = new Date().getTime() + (ttl * 1000);
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId));
|
||||
}
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one specific passphrase for keyId timed out
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
*/
|
||||
private void timeout(Context context, long keyId) {
|
||||
// remove passphrase corresponding to keyId from memory
|
||||
mPassphraseCache.remove(keyId);
|
||||
|
||||
Log.d(TAG, "Timeout of " + keyId + ", removed from memory!");
|
||||
|
||||
// stop whole service if no cached passphrases remaining
|
||||
if (mPassphraseCache.isEmpty()) {
|
||||
Log.d(TAG, "No passphrases remaining in memory, stopping service!");
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "onDestroy()");
|
||||
|
||||
unregisterReceiver(mIntentReceiver);
|
||||
}
|
||||
|
||||
public class PassphraseCacheBinder extends Binder {
|
||||
public PassphraseCacheService getService() {
|
||||
return PassphraseCacheService.this;
|
||||
}
|
||||
}
|
||||
|
||||
private final IBinder mBinder = new PassphraseCacheBinder();
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
}
|
||||
940
APG/src/org/thialfihar/android/apg/ui/DecryptActivity.java
Normal file
940
APG/src/org/thialfihar/android/apg/ui/DecryptActivity.java
Normal file
@@ -0,0 +1,940 @@
|
||||
/*
|
||||
* 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.PGPPublicKeyRing;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.helper.FileHelper;
|
||||
import org.thialfihar.android.apg.helper.OtherHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPMain;
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
import org.thialfihar.android.apg.service.ApgIntentServiceHandler;
|
||||
import org.thialfihar.android.apg.service.ApgIntentService;
|
||||
import org.thialfihar.android.apg.service.PassphraseCacheService;
|
||||
import org.thialfihar.android.apg.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.thialfihar.android.apg.ui.dialog.FileDialogFragment;
|
||||
import org.thialfihar.android.apg.ui.dialog.LookupUnknownKeyDialogFragment;
|
||||
import org.thialfihar.android.apg.ui.dialog.PassphraseDialogFragment;
|
||||
import org.thialfihar.android.apg.util.Compatibility;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import org.thialfihar.android.apg.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.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class DecryptActivity extends SherlockFragmentActivity {
|
||||
|
||||
// possible intent actions for this activity
|
||||
public static final String ACTION_DECRYPT = Constants.INTENT_PREFIX + "DECRYPT";
|
||||
public static final String ACTION_DECRYPT_FILE = Constants.INTENT_PREFIX + "DECRYPT_FILE";
|
||||
public static final String ACTION_DECRYPT_AND_RETURN = Constants.INTENT_PREFIX
|
||||
+ "DECRYPT_AND_RETURN";
|
||||
|
||||
// possible extra keys
|
||||
public static final String EXTRA_TEXT = "text";
|
||||
public static final String EXTRA_DATA = "data";
|
||||
public static final String EXTRA_REPLY_TO = "replyTo";
|
||||
public static final String EXTRA_SUBJECT = "subject";
|
||||
public static final String EXTRA_BINARY = "binary";
|
||||
|
||||
private long mSignatureKeyId = 0;
|
||||
|
||||
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 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[] mDataBytes = null;
|
||||
private boolean mReturnBinary = false;
|
||||
|
||||
private long mUnknownSignatureKeyId = 0;
|
||||
|
||||
private long mSecretKeyId = Id.key.none;
|
||||
|
||||
private FileDialogFragment mFileDialog;
|
||||
|
||||
private boolean mLookupUnknownKey = true;
|
||||
|
||||
@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 android.R.id.home:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
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);
|
||||
|
||||
// check permissions for intent actions without user interaction
|
||||
String[] restrictedActions = new String[] { ACTION_DECRYPT_AND_RETURN };
|
||||
OtherHelper.checkPackagePermissionForActions(this, this.getCallingPackage(),
|
||||
Constants.PERMISSION_ACCESS_API, getIntent().getAction(), restrictedActions);
|
||||
|
||||
setContentView(R.layout.decrypt);
|
||||
|
||||
// set actionbar without home button if called from another app
|
||||
OtherHelper.setActionBarBackButton(this);
|
||||
|
||||
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) {
|
||||
FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*",
|
||||
Id.request.filename);
|
||||
}
|
||||
});
|
||||
|
||||
mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption);
|
||||
|
||||
// default: message source
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
|
||||
mSource.showNext();
|
||||
}
|
||||
|
||||
boolean decryptImmediately = false;
|
||||
|
||||
// Get intent, action and MIME type
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
String type = intent.getType();
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(action)) {
|
||||
// Android's Action when opening file associated to APG (see AndroidManifest.xml)
|
||||
|
||||
// This gets the Uri, where an inputStream can be opened from
|
||||
mContentUri = intent.getData();
|
||||
|
||||
// TODO: old implementation of ACTION_VIEW. Is this used in K9?
|
||||
// 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
|
||||
// }
|
||||
|
||||
// same as ACTION_DECRYPT_FILE but decrypt it immediately
|
||||
handleActionDecryptFile(intent);
|
||||
decryptImmediately = true;
|
||||
} else if (Intent.ACTION_SEND.equals(action) && type != null) {
|
||||
// Android's Action when sending to APG Decrypt
|
||||
|
||||
if ("text/plain".equals(type)) {
|
||||
// plain text
|
||||
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (sharedText != null) {
|
||||
intent.putExtra(EXTRA_TEXT, sharedText);
|
||||
handleActionDecrypt(intent);
|
||||
decryptImmediately = true;
|
||||
}
|
||||
} else {
|
||||
// binary via content provider (could also be files)
|
||||
Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
if (uri != null) {
|
||||
mContentUri = uri;
|
||||
}
|
||||
}
|
||||
} else if (ACTION_DECRYPT.equals(action)) {
|
||||
handleActionDecrypt(intent);
|
||||
} else if (ACTION_DECRYPT_FILE.equals(action)) {
|
||||
handleActionDecryptFile(intent);
|
||||
} else if (ACTION_DECRYPT_AND_RETURN.equals(action)) {
|
||||
handleActionDecryptAndReturn(intent);
|
||||
}
|
||||
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceMessage
|
||||
&& mMessage.getText().length() == 0) {
|
||||
|
||||
CharSequence clipboardText = Compatibility.getClipboardText(this);
|
||||
|
||||
String data = "";
|
||||
if (clipboardText != null) {
|
||||
Matcher matcher = PGPMain.PGP_MESSAGE.matcher(clipboardText);
|
||||
if (!matcher.matches()) {
|
||||
matcher = PGPMain.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 = ProviderHelper.getPGPPublicKeyRingByKeyId(
|
||||
DecryptActivity.this, mSignatureKeyId);
|
||||
if (key != null) {
|
||||
Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class);
|
||||
intent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID);
|
||||
intent.putExtra(KeyServerQueryActivity.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 (decryptImmediately
|
||||
|| (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText()
|
||||
.length() > 0 || mDataBytes != null || mContentUri != null))) {
|
||||
decryptClicked();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles activity intent with ACTION_DECRYPT
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionDecrypt(Intent intent) {
|
||||
Log.d(Constants.TAG, "Apg Intent DECRYPT startet");
|
||||
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras == null) {
|
||||
Log.d(Constants.TAG, "extra bundle was null");
|
||||
extras = new Bundle();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "got extras");
|
||||
}
|
||||
|
||||
mDataBytes = extras.getByteArray(EXTRA_DATA);
|
||||
String textData = null;
|
||||
if (mDataBytes == null) {
|
||||
Log.d(Constants.TAG, "EXTRA_DATA was null");
|
||||
textData = extras.getString(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 = PGPMain.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 = PGPMain.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(EXTRA_REPLY_TO);
|
||||
mSubject = extras.getString(EXTRA_SUBJECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles activity intent with ACTION_DECRYPT_FILE
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionDecryptFile(Intent intent) {
|
||||
mInputFilename = intent.getData().getPath();
|
||||
mFilename.setText(mInputFilename);
|
||||
guessOutputFilename();
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceFile) {
|
||||
mSource.showNext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles activity intent with ACTION_DECRYPT_AND_RETURN
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionDecryptAndReturn(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
mReturnBinary = extras.getBoolean(EXTRA_BINARY, false);
|
||||
|
||||
if (mContentUri == null) {
|
||||
mDataBytes = extras.getByteArray(EXTRA_DATA);
|
||||
String data = extras.getString(EXTRA_TEXT);
|
||||
if (data != null) {
|
||||
Matcher matcher = PGPMain.PGP_MESSAGE.matcher(data);
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
data = data.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(data);
|
||||
} else {
|
||||
matcher = PGPMain.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;
|
||||
}
|
||||
|
||||
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 = PGPMain.PGP_SIGNED_MESSAGE.matcher(messageData);
|
||||
if (matcher.matches()) {
|
||||
mSignedOnly = true;
|
||||
decryptStart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// else treat it as an decrypted message/file
|
||||
mSignedOnly = false;
|
||||
|
||||
getDecryptionKeyFromInputStream();
|
||||
|
||||
// if we need a symmetric passphrase or a passphrase to use a secret key ask for it
|
||||
if (mSecretKeyId == Id.key.symmetric
|
||||
|| PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) {
|
||||
showPassphraseDialog();
|
||||
} else {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||
* for a symmetric passphrase
|
||||
*/
|
||||
private void showPassphraseDialog() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
|
||||
messenger, mSecretKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PGPMain.ApgGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Rework function, remove global variables
|
||||
*/
|
||||
private void getDecryptionKeyFromInputStream() {
|
||||
InputStream inStream = null;
|
||||
if (mContentUri != null) {
|
||||
try {
|
||||
inStream = getContentResolver().openInputStream(mContentUri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "File not found!", e);
|
||||
Toast.makeText(this, getString(R.string.error_fileNotFound, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(mInputFilename)) {
|
||||
Toast.makeText(this, getString(R.string.error_externalStorageNotReady),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
inStream = new FileInputStream(mInputFilename);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "File not found!", e);
|
||||
Toast.makeText(this, getString(R.string.error_fileNotFound, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
if (mDataBytes != null) {
|
||||
inStream = new ByteArrayInputStream(mDataBytes);
|
||||
} else {
|
||||
inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
// get decryption key for this inStream
|
||||
try {
|
||||
try {
|
||||
mSecretKeyId = PGPMain.getDecryptionKeyId(this, inStream);
|
||||
if (mSecretKeyId == Id.key.none) {
|
||||
throw new PGPMain.ApgGeneralException(
|
||||
getString(R.string.error_noSecretKeyFound));
|
||||
}
|
||||
mAssumeSymmetricEncryption = false;
|
||||
} catch (PGPMain.NoAsymmetricEncryptionException e) {
|
||||
mSecretKeyId = Id.key.symmetric;
|
||||
if (!PGPMain.hasSymmetricEncryption(this, inStream)) {
|
||||
throw new PGPMain.ApgGeneralException(
|
||||
getString(R.string.error_noKnownEncryptionFound));
|
||||
}
|
||||
mAssumeSymmetricEncryption = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void replyClicked() {
|
||||
Intent intent = new Intent(this, EncryptActivity.class);
|
||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||
String data = mMessage.getText().toString();
|
||||
data = data.replaceAll("(?m)^", "> ");
|
||||
data = "\n\n" + data;
|
||||
intent.putExtra(EncryptActivity.EXTRA_TEXT, data);
|
||||
intent.putExtra(EncryptActivity.EXTRA_SUBJECT, "Re: " + mSubject);
|
||||
intent.putExtra(EncryptActivity.EXTRA_SEND_TO, mReplyTo);
|
||||
intent.putExtra(EncryptActivity.EXTRA_SIGNATURE_KEY_ID, mSecretKeyId);
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId });
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void askForOutputFilename() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
mFileDialog = FileDialogFragment.newInstance(messenger,
|
||||
getString(R.string.title_decryptToFile),
|
||||
getString(R.string.specifyFileToDecryptTo), mOutputFilename, null,
|
||||
Id.request.output_filename);
|
||||
|
||||
mFileDialog.show(getSupportFragmentManager(), "fileDialog");
|
||||
}
|
||||
|
||||
private void lookupUnknownKey(long unknownKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_OKAY) {
|
||||
// the result is handled by onActivityResult() as LookupUnknownKeyDialogFragment
|
||||
// starts a new Intent which then returns data
|
||||
} else if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_CANCEL) {
|
||||
// decrypt again, but don't lookup unknown keys!
|
||||
mLookupUnknownKey = false;
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
LookupUnknownKeyDialogFragment lookupKeyDialog = LookupUnknownKeyDialogFragment
|
||||
.newInstance(messenger, unknownKeyId);
|
||||
|
||||
lookupKeyDialog.show(getSupportFragmentManager(), "unknownKeyDialog");
|
||||
}
|
||||
|
||||
private void decryptStart() {
|
||||
Log.d(Constants.TAG, "decryptStart");
|
||||
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(this, ApgIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
// choose action based on input: decrypt stream, file or bytes
|
||||
if (mContentUri != null) {
|
||||
data.putInt(ApgIntentService.TARGET, ApgIntentService.TARGET_STREAM);
|
||||
|
||||
data.putParcelable(ApgIntentService.PROVIDER_URI, mContentUri);
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
data.putInt(ApgIntentService.TARGET, ApgIntentService.TARGET_FILE);
|
||||
|
||||
Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
|
||||
+ mOutputFilename);
|
||||
|
||||
data.putString(ApgIntentService.INPUT_FILE, mInputFilename);
|
||||
data.putString(ApgIntentService.OUTPUT_FILE, mOutputFilename);
|
||||
} else {
|
||||
data.putInt(ApgIntentService.TARGET, ApgIntentService.TARGET_BYTES);
|
||||
|
||||
if (mDataBytes != null) {
|
||||
data.putByteArray(ApgIntentService.CIPHERTEXT_BYTES, mDataBytes);
|
||||
} else {
|
||||
String message = mMessage.getText().toString();
|
||||
data.putByteArray(ApgIntentService.CIPHERTEXT_BYTES, message.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
data.putLong(ApgIntentService.SECRET_KEY_ID, mSecretKeyId);
|
||||
|
||||
data.putBoolean(ApgIntentService.SIGNED_ONLY, mSignedOnly);
|
||||
data.putBoolean(ApgIntentService.LOOKUP_UNKNOWN_KEY, mLookupUnknownKey);
|
||||
data.putBoolean(ApgIntentService.RETURN_BYTES, mReturnBinary);
|
||||
data.putBoolean(ApgIntentService.ASSUME_SYMMETRIC, mAssumeSymmetricEncryption);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(this,
|
||||
R.string.progress_decrypting, ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
// if key is unknown show lookup dialog
|
||||
if (returnData.getBoolean(ApgIntentService.RESULT_SIGNATURE_LOOKUP_KEY)
|
||||
&& mLookupUnknownKey) {
|
||||
mUnknownSignatureKeyId = returnData
|
||||
.getLong(ApgIntentService.RESULT_SIGNATURE_KEY_ID);
|
||||
lookupUnknownKey(mUnknownSignatureKeyId);
|
||||
return;
|
||||
}
|
||||
|
||||
mSignatureKeyId = 0;
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mReplyEnabled = false;
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
|
||||
Toast.makeText(DecryptActivity.this, R.string.decryptionSuccessful,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
if (mReturnResult) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtras(returnData);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mDecryptTarget) {
|
||||
case Id.target.message:
|
||||
String decryptedMessage = returnData
|
||||
.getString(ApgIntentService.RESULT_DECRYPTED_STRING);
|
||||
mMessage.setText(decryptedMessage);
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
mReplyEnabled = false;
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
break;
|
||||
|
||||
case Id.target.file:
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mInputFilename);
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// shouldn't happen
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (returnData.getBoolean(ApgIntentService.RESULT_SIGNATURE)) {
|
||||
String userId = returnData
|
||||
.getString(ApgIntentService.RESULT_SIGNATURE_USER_ID);
|
||||
mSignatureKeyId = returnData
|
||||
.getLong(ApgIntentService.RESULT_SIGNATURE_KEY_ID);
|
||||
mUserIdRest
|
||||
.setText("id: " + PGPHelper.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 (returnData.getBoolean(ApgIntentService.RESULT_SIGNATURE_SUCCESS)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
} else if (returnData.getBoolean(ApgIntentService.RESULT_SIGNATURE_UNKNOWN)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
Toast.makeText(DecryptActivity.this,
|
||||
R.string.unknownSignatureKeyTouchToLookUp, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} else {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
}
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.filename: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = FileHelper.getPath(this, data.getData());
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case Id.request.output_filename: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = data.getData().getPath();
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
mFileDialog.setFilename(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// this request is returned after LookupUnknownKeyDialogFragment started
|
||||
// KeyServerQueryActivity and user looked uo key
|
||||
case Id.request.look_up_key_id: {
|
||||
Log.d(Constants.TAG, "Returning from Lookup Key...");
|
||||
// decrypt again without lookup
|
||||
mLookupUnknownKey = false;
|
||||
decryptStart();
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
}
|
||||
569
APG/src/org/thialfihar/android/apg/ui/EditKeyActivity.java
Normal file
569
APG/src/org/thialfihar/android/apg/ui/EditKeyActivity.java
Normal file
@@ -0,0 +1,569 @@
|
||||
/*
|
||||
* 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.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.helper.OtherHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPMain;
|
||||
import org.thialfihar.android.apg.helper.PGPConversionHelper;
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
import org.thialfihar.android.apg.service.ApgIntentServiceHandler;
|
||||
import org.thialfihar.android.apg.service.ApgIntentService;
|
||||
import org.thialfihar.android.apg.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.thialfihar.android.apg.ui.widget.KeyEditor;
|
||||
import org.thialfihar.android.apg.ui.widget.SectionView;
|
||||
import org.thialfihar.android.apg.ui.widget.UserIdEditor;
|
||||
import org.thialfihar.android.apg.util.IterableIterator;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import org.thialfihar.android.apg.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.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EditKeyActivity extends SherlockFragmentActivity {
|
||||
|
||||
// possible intent actions for this activity
|
||||
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
|
||||
public static final String ACTION_EDIT_KEY = Constants.INTENT_PREFIX + "EDIT_KEY";
|
||||
|
||||
// possible extra keys
|
||||
public static final String EXTRA_USER_IDS = "userIds";
|
||||
public static final String EXTRA_NO_PASSPHRASE = "noPassphrase";
|
||||
public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generateDefaultKeys";
|
||||
public static final String EXTRA_KEY_ID = "keyId";
|
||||
|
||||
private ActionBar mActionBar;
|
||||
|
||||
private PGPSecretKeyRing mKeyRing = null;
|
||||
|
||||
private SectionView mUserIdsView;
|
||||
private SectionView mKeysView;
|
||||
|
||||
private String mCurrentPassPhrase = null;
|
||||
private String mNewPassPhrase = null;
|
||||
|
||||
private Button mChangePassPhrase;
|
||||
|
||||
private CheckBox mNoPassphrase;
|
||||
|
||||
Vector<String> mUserIds;
|
||||
Vector<PGPSecretKey> mKeys;
|
||||
Vector<Integer> mKeysUsages;
|
||||
|
||||
// will be set to false to build layout later in handler
|
||||
private boolean mBuildLayout = true;
|
||||
|
||||
@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:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, KeyListSecretActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
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);
|
||||
|
||||
// check permissions for intent actions without user interaction
|
||||
String[] restrictedActions = new String[] { ACTION_CREATE_KEY };
|
||||
OtherHelper.checkPackagePermissionForActions(this, this.getCallingPackage(),
|
||||
Constants.PERMISSION_ACCESS_API, getIntent().getAction(), restrictedActions);
|
||||
|
||||
setContentView(R.layout.edit_key);
|
||||
|
||||
mActionBar = getSupportActionBar();
|
||||
mActionBar.setDisplayShowTitleEnabled(true);
|
||||
|
||||
// set actionbar without home button if called from another app
|
||||
OtherHelper.setActionBarBackButton(this);
|
||||
|
||||
// find views
|
||||
mChangePassPhrase = (Button) findViewById(R.id.edit_key_btn_change_pass_phrase);
|
||||
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
|
||||
|
||||
mUserIds = new Vector<String>();
|
||||
mKeys = new Vector<PGPSecretKey>();
|
||||
mKeysUsages = new Vector<Integer>();
|
||||
|
||||
// Catch Intents opened from other apps
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
if (ACTION_CREATE_KEY.equals(action)) {
|
||||
handleActionCreateKey(intent);
|
||||
} else if (ACTION_EDIT_KEY.equals(action)) {
|
||||
handleActionEditKey(intent);
|
||||
}
|
||||
|
||||
mChangePassPhrase.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
showSetPassphraseDialog();
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (mBuildLayout) {
|
||||
buildLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle intent action to create new key
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionCreateKey(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
mActionBar.setTitle(R.string.title_createKey);
|
||||
|
||||
mCurrentPassPhrase = "";
|
||||
|
||||
if (extras != null) {
|
||||
// if userId is given, prefill the fields
|
||||
if (extras.containsKey(EXTRA_USER_IDS)) {
|
||||
Log.d(Constants.TAG, "UserIds are given!");
|
||||
mUserIds.add(extras.getString(EXTRA_USER_IDS));
|
||||
}
|
||||
|
||||
// if no passphrase is given
|
||||
if (extras.containsKey(EXTRA_NO_PASSPHRASE)) {
|
||||
boolean noPassphrase = extras.getBoolean(EXTRA_NO_PASSPHRASE);
|
||||
if (noPassphrase) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassPhrase.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// generate key
|
||||
if (extras.containsKey(EXTRA_GENERATE_DEFAULT_KEYS)) {
|
||||
boolean generateDefaultKeys = extras.getBoolean(EXTRA_GENERATE_DEFAULT_KEYS);
|
||||
if (generateDefaultKeys) {
|
||||
|
||||
// build layout in handler after generating keys not directly in onCreate
|
||||
mBuildLayout = false;
|
||||
|
||||
// Send all information needed to service generate keys in other thread
|
||||
Intent serviceIntent = new Intent(this, ApgIntentService.class);
|
||||
serviceIntent.putExtra(ApgIntentService.EXTRA_ACTION,
|
||||
ApgIntentService.ACTION_GENERATE_DEFAULT_RSA_KEYS);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putString(ApgIntentService.SYMMETRIC_PASSPHRASE, mCurrentPassPhrase);
|
||||
|
||||
serviceIntent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after generating is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(this,
|
||||
R.string.progress_generating, ProgressDialog.STYLE_SPINNER) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get new key from data bundle returned from service
|
||||
Bundle data = message.getData();
|
||||
PGPSecretKeyRing masterKeyRing = (PGPSecretKeyRing) PGPConversionHelper
|
||||
.BytesToPGPKeyRing(data
|
||||
.getByteArray(ApgIntentService.RESULT_NEW_KEY));
|
||||
PGPSecretKeyRing subKeyRing = (PGPSecretKeyRing) PGPConversionHelper
|
||||
.BytesToPGPKeyRing(data
|
||||
.getByteArray(ApgIntentService.RESULT_NEW_KEY2));
|
||||
|
||||
// add master key
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> masterIt = masterKeyRing.getSecretKeys();
|
||||
mKeys.add(masterIt.next());
|
||||
mKeysUsages.add(Id.choice.usage.sign_only);
|
||||
|
||||
// add sub key
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> subIt = subKeyRing.getSecretKeys();
|
||||
subIt.next(); // masterkey
|
||||
mKeys.add(subIt.next());
|
||||
mKeysUsages.add(Id.choice.usage.encrypt_only);
|
||||
|
||||
buildLayout();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
serviceIntent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(serviceIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle intent action to edit existing key
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionEditKey(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
mActionBar.setTitle(R.string.title_editKey);
|
||||
|
||||
mCurrentPassPhrase = PGPMain.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(EXTRA_KEY_ID)) {
|
||||
long keyId = extras.getLong(EXTRA_KEY_ID);
|
||||
|
||||
if (keyId != 0) {
|
||||
PGPSecretKey masterKey = null;
|
||||
mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, keyId);
|
||||
if (mKeyRing != null) {
|
||||
masterKey = PGPHelper.getMasterKey(mKeyRing);
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(
|
||||
mKeyRing.getSecretKeys())) {
|
||||
mKeys.add(key);
|
||||
mKeysUsages.add(-1); // get usage when view is created
|
||||
}
|
||||
}
|
||||
if (masterKey != null) {
|
||||
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||
Log.d(Constants.TAG, "Added userId " + userId);
|
||||
mUserIds.add(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the dialog to set a new passphrase
|
||||
*/
|
||||
private void showSetPassphraseDialog() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
// set new returned passphrase!
|
||||
mNewPassPhrase = data
|
||||
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
||||
|
||||
updatePassPhraseButtonText();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
// set title based on isPassphraseSet()
|
||||
int title = -1;
|
||||
if (isPassphraseSet()) {
|
||||
title = R.string.title_changePassPhrase;
|
||||
} else {
|
||||
title = R.string.title_setPassPhrase;
|
||||
}
|
||||
|
||||
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
|
||||
messenger, title);
|
||||
|
||||
setPassphraseDialog.show(getSupportFragmentManager(), "setPassphraseDialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
|
||||
* id and key.
|
||||
*/
|
||||
private void buildLayout() {
|
||||
// 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);
|
||||
mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mUserIdsView.setType(Id.type.user_id);
|
||||
mUserIdsView.setUserIds(mUserIds);
|
||||
container.addView(mUserIdsView);
|
||||
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mKeysView.setType(Id.type.key);
|
||||
mKeysView.setKeys(mKeys, mKeysUsages);
|
||||
container.addView(mKeysView);
|
||||
|
||||
updatePassPhraseButtonText();
|
||||
}
|
||||
|
||||
private long getMasterKeyId() {
|
||||
if (mKeysView.getEditors().getChildCount() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((KeyEditor) mKeysView.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;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveClicked() {
|
||||
try {
|
||||
if (!isPassphraseSet()) {
|
||||
throw new PGPMain.ApgGeneralException(this.getString(R.string.setAPassPhrase));
|
||||
}
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(this, ApgIntentService.class);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putString(ApgIntentService.CURRENT_PASSPHRASE, mCurrentPassPhrase);
|
||||
data.putString(ApgIntentService.NEW_PASSPHRASE, mNewPassPhrase);
|
||||
data.putStringArrayList(ApgIntentService.USER_IDS, getUserIds(mUserIdsView));
|
||||
ArrayList<PGPSecretKey> keys = getKeys(mKeysView);
|
||||
data.putByteArray(ApgIntentService.KEYS,
|
||||
PGPConversionHelper.PGPSecretKeyArrayListToBytes(keys));
|
||||
data.putIntegerArrayList(ApgIntentService.KEYS_USAGES, getKeysUsages(mKeysView));
|
||||
data.putLong(ApgIntentService.MASTER_KEY_ID, getMasterKeyId());
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after saving is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(this,
|
||||
R.string.progress_saving, ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
finish();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
} catch (PGPMain.ApgGeneralException e) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user ids from the SectionView
|
||||
*
|
||||
* @param userIdsView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<String> getUserIds(SectionView userIdsView)
|
||||
throws PGPMain.ApgGeneralException {
|
||||
ArrayList<String> userIds = new ArrayList<String>();
|
||||
|
||||
ViewGroup userIdEditors = userIdsView.getEditors();
|
||||
|
||||
boolean gotMainUserId = false;
|
||||
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
|
||||
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
|
||||
String userId = null;
|
||||
try {
|
||||
userId = editor.getValue();
|
||||
} catch (UserIdEditor.NoNameException e) {
|
||||
throw new PGPMain.ApgGeneralException(
|
||||
this.getString(R.string.error_userIdNeedsAName));
|
||||
} catch (UserIdEditor.NoEmailException e) {
|
||||
throw new PGPMain.ApgGeneralException(
|
||||
this.getString(R.string.error_userIdNeedsAnEmailAddress));
|
||||
} catch (UserIdEditor.InvalidEmailException e) {
|
||||
throw new PGPMain.ApgGeneralException(e.getMessage());
|
||||
}
|
||||
|
||||
if (userId.equals("")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (editor.isMainUserId()) {
|
||||
userIds.add(0, userId);
|
||||
gotMainUserId = true;
|
||||
} else {
|
||||
userIds.add(userId);
|
||||
}
|
||||
}
|
||||
|
||||
if (userIds.size() == 0) {
|
||||
throw new PGPMain.ApgGeneralException(getString(R.string.error_keyNeedsAUserId));
|
||||
}
|
||||
|
||||
if (!gotMainUserId) {
|
||||
throw new PGPMain.ApgGeneralException(
|
||||
getString(R.string.error_mainUserIdMustNotBeEmpty));
|
||||
}
|
||||
|
||||
return userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns keys from the SectionView
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<PGPSecretKey> getKeys(SectionView keysView)
|
||||
throws PGPMain.ApgGeneralException {
|
||||
ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PGPMain.ApgGeneralException(getString(R.string.error_keyNeedsMasterKey));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
keys.add(editor.getValue());
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns usage selections of keys from the SectionView
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getKeysUsages(SectionView keysView)
|
||||
throws PGPMain.ApgGeneralException {
|
||||
ArrayList<Integer> getKeysUsages = new ArrayList<Integer>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PGPMain.ApgGeneralException(getString(R.string.error_keyNeedsMasterKey));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
getKeysUsages.add(editor.getUsage());
|
||||
}
|
||||
|
||||
return getKeysUsages;
|
||||
}
|
||||
|
||||
private void updatePassPhraseButtonText() {
|
||||
mChangePassPhrase.setText(isPassphraseSet() ? R.string.btn_changePassPhrase
|
||||
: R.string.btn_setPassPhrase);
|
||||
}
|
||||
}
|
||||
1099
APG/src/org/thialfihar/android/apg/ui/EncryptActivity.java
Normal file
1099
APG/src/org/thialfihar/android/apg/ui/EncryptActivity.java
Normal file
File diff suppressed because it is too large
Load Diff
162
APG/src/org/thialfihar/android/apg/ui/HelpActivity.java
Normal file
162
APG/src/org/thialfihar/android/apg/ui/HelpActivity.java
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.ActionBar.Tab;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
|
||||
public class HelpActivity extends SherlockFragmentActivity {
|
||||
ViewPager mViewPager;
|
||||
TabsAdapter mTabsAdapter;
|
||||
TextView tabCenter;
|
||||
TextView tabText;
|
||||
|
||||
/**
|
||||
* Menu Items
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mViewPager = new ViewPager(this);
|
||||
mViewPager.setId(R.id.pager);
|
||||
|
||||
setContentView(mViewPager);
|
||||
ActionBar bar = getSupportActionBar();
|
||||
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||
bar.setDisplayShowTitleEnabled(true);
|
||||
bar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mTabsAdapter = new TabsAdapter(this, mViewPager);
|
||||
|
||||
Bundle startBundle = new Bundle();
|
||||
startBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_start);
|
||||
mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.help_tab_start)),
|
||||
HelpFragmentHtml.class, startBundle);
|
||||
|
||||
Bundle changelogBundle = new Bundle();
|
||||
changelogBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_changelog);
|
||||
mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.help_tab_changelog)),
|
||||
HelpFragmentHtml.class, changelogBundle);
|
||||
|
||||
mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.help_tab_about)),
|
||||
HelpFragmentAbout.class, null);
|
||||
}
|
||||
|
||||
public static class TabsAdapter extends FragmentPagerAdapter implements ActionBar.TabListener,
|
||||
ViewPager.OnPageChangeListener {
|
||||
private final Context mContext;
|
||||
private final ActionBar mActionBar;
|
||||
private final ViewPager mViewPager;
|
||||
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
|
||||
|
||||
static final class TabInfo {
|
||||
private final Class<?> clss;
|
||||
private final Bundle args;
|
||||
|
||||
TabInfo(Class<?> _class, Bundle _args) {
|
||||
clss = _class;
|
||||
args = _args;
|
||||
}
|
||||
}
|
||||
|
||||
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
|
||||
super(activity.getSupportFragmentManager());
|
||||
mContext = activity;
|
||||
mActionBar = activity.getSupportActionBar();
|
||||
mViewPager = pager;
|
||||
mViewPager.setAdapter(this);
|
||||
mViewPager.setOnPageChangeListener(this);
|
||||
}
|
||||
|
||||
public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
|
||||
TabInfo info = new TabInfo(clss, args);
|
||||
tab.setTag(info);
|
||||
tab.setTabListener(this);
|
||||
mTabs.add(info);
|
||||
mActionBar.addTab(tab);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mTabs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
TabInfo info = mTabs.get(position);
|
||||
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
|
||||
}
|
||||
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
}
|
||||
|
||||
public void onPageSelected(int position) {
|
||||
mActionBar.setSelectedNavigationItem(position);
|
||||
}
|
||||
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
}
|
||||
|
||||
public void onTabSelected(Tab tab, FragmentTransaction ft) {
|
||||
Object tag = tab.getTag();
|
||||
for (int i = 0; i < mTabs.size(); i++) {
|
||||
if (mTabs.get(i) == tag) {
|
||||
mViewPager.setCurrentItem(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
|
||||
}
|
||||
|
||||
public void onTabReselected(Tab tab, FragmentTransaction ft) {
|
||||
}
|
||||
}
|
||||
}
|
||||
107
APG/src/org/thialfihar/android/apg/ui/HelpFragmentAbout.java
Normal file
107
APG/src/org/thialfihar/android/apg/ui/HelpFragmentAbout.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.nightwhistler.htmlspanner.HtmlSpanner;
|
||||
import net.nightwhistler.htmlspanner.JellyBeanSpanFixTextView;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.OtherHelper;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragment;
|
||||
|
||||
public class HelpFragmentAbout extends SherlockFragment {
|
||||
|
||||
/**
|
||||
* Workaround for Android Bug. See
|
||||
* http://stackoverflow.com/questions/8748064/starting-activity-from
|
||||
* -fragment-causes-nullpointerexception
|
||||
*/
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
setUserVisibleHint(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.help_fragment_about, container, false);
|
||||
|
||||
// load html from html file from /res/raw
|
||||
InputStream inputStreamText = OtherHelper.getInputStreamFromResource(this.getActivity(),
|
||||
R.raw.help_about);
|
||||
|
||||
TextView versionText = (TextView) view.findViewById(R.id.help_about_version);
|
||||
versionText.setText(getString(R.string.help_about_version) + " " + getVersion());
|
||||
|
||||
JellyBeanSpanFixTextView aboutTextView = (JellyBeanSpanFixTextView) view
|
||||
.findViewById(R.id.help_about_text);
|
||||
|
||||
// load html into textview
|
||||
HtmlSpanner htmlSpanner = new HtmlSpanner();
|
||||
htmlSpanner.setStripExtraWhiteSpace(true);
|
||||
try {
|
||||
aboutTextView.setText(htmlSpanner.fromHtml(inputStreamText));
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error while reading raw resources as stream", e);
|
||||
}
|
||||
|
||||
// make links work
|
||||
aboutTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
// no flickering when clicking textview for Android < 4
|
||||
aboutTextView.setTextColor(getResources().getColor(android.R.color.black));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current package version.
|
||||
*
|
||||
* @return The current version.
|
||||
*/
|
||||
private String getVersion() {
|
||||
String result = "";
|
||||
try {
|
||||
PackageManager manager = getActivity().getPackageManager();
|
||||
PackageInfo info = manager.getPackageInfo(getActivity().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;
|
||||
}
|
||||
|
||||
}
|
||||
109
APG/src/org/thialfihar/android/apg/ui/HelpFragmentHtml.java
Normal file
109
APG/src/org/thialfihar/android/apg/ui/HelpFragmentHtml.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.nightwhistler.htmlspanner.HtmlSpanner;
|
||||
import net.nightwhistler.htmlspanner.JellyBeanSpanFixTextView;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.helper.OtherHelper;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragment;
|
||||
|
||||
public class HelpFragmentHtml extends SherlockFragment {
|
||||
private Activity mActivity;
|
||||
|
||||
private int htmlFile;
|
||||
|
||||
public static final String ARG_HTML_FILE = "htmlFile";
|
||||
|
||||
/**
|
||||
* Create a new instance of HelpFragmentHtml, providing "htmlFile" as an argument.
|
||||
*/
|
||||
static HelpFragmentHtml newInstance(int htmlFile) {
|
||||
HelpFragmentHtml f = new HelpFragmentHtml();
|
||||
|
||||
// Supply html raw file input as an argument.
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_HTML_FILE, htmlFile);
|
||||
f.setArguments(args);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround for Android Bug. See
|
||||
* http://stackoverflow.com/questions/8748064/starting-activity-from
|
||||
* -fragment-causes-nullpointerexception
|
||||
*/
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
setUserVisibleHint(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
htmlFile = getArguments().getInt(ARG_HTML_FILE);
|
||||
|
||||
// load html from html file from /res/raw
|
||||
InputStream inputStreamText = OtherHelper.getInputStreamFromResource(this.getActivity(),
|
||||
htmlFile);
|
||||
|
||||
mActivity = getActivity();
|
||||
|
||||
ScrollView scroller = new ScrollView(mActivity);
|
||||
JellyBeanSpanFixTextView text = new JellyBeanSpanFixTextView(mActivity);
|
||||
|
||||
// padding
|
||||
int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, mActivity
|
||||
.getResources().getDisplayMetrics());
|
||||
text.setPadding(padding, padding, padding, 0);
|
||||
|
||||
scroller.addView(text);
|
||||
|
||||
// load html into textview
|
||||
HtmlSpanner htmlSpanner = new HtmlSpanner();
|
||||
htmlSpanner.setStripExtraWhiteSpace(true);
|
||||
try {
|
||||
text.setText(htmlSpanner.fromHtml(inputStreamText));
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error while reading raw resources as stream", e);
|
||||
}
|
||||
|
||||
// make links work
|
||||
text.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
// no flickering when clicking textview for Android < 4
|
||||
text.setTextColor(getResources().getColor(android.R.color.black));
|
||||
|
||||
return scroller;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
* 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.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.helper.OtherHelper;
|
||||
import org.thialfihar.android.apg.service.ApgIntentServiceHandler;
|
||||
import org.thialfihar.android.apg.service.ApgIntentService;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
public class ImportFromQRCodeActivity extends SherlockFragmentActivity {
|
||||
|
||||
// Not used in sourcode, but listed in AndroidManifest!
|
||||
public static final String IMPORT_FROM_QR_CODE = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_FROM_QR_CODE";
|
||||
|
||||
// public static final String EXTRA_KEY_ID = "keyId";
|
||||
|
||||
private TextView mContentView;
|
||||
|
||||
private String mScannedContent;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.import_from_qr_code);
|
||||
mContentView = (TextView) findViewById(R.id.import_from_qr_code_content);
|
||||
|
||||
// set actionbar without home button if called from another app
|
||||
OtherHelper.setActionBarBackButton(this);
|
||||
|
||||
// start scanning
|
||||
new IntentIntegrator(this).initiateScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private void importAndSignOld(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
|
||||
//
|
||||
// // TODO: there should be only 1
|
||||
// HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]);
|
||||
// String encodedKey = server.get(keyId);
|
||||
//
|
||||
// PGPKeyRing keyring = PGPHelper.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 = PGPHelper.convertToHex(publicKeyRing
|
||||
// .getPublicKey().getFingerprint());
|
||||
// if (expectedFingerprint.equals(actualFingerprint)) {
|
||||
// // store the signed key in our local cache
|
||||
// int retval = PGPMain.storeKeyRingInCache(publicKeyRing);
|
||||
// if (retval != Id.return_value.ok
|
||||
// && retval != Id.return_value.updated) {
|
||||
// status.putString(EXTRA_ERROR,
|
||||
// "Failed to store signed key in local cache");
|
||||
// } else {
|
||||
// Intent intent = new Intent(ImportFromQRCodeActivity.this,
|
||||
// SignKeyActivity.class);
|
||||
// intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
// startActivityForResult(intent, Id.request.sign_key);
|
||||
// }
|
||||
// } else {
|
||||
// status.putString(
|
||||
// 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(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(EXTRA_ERROR, "Failed to query KeyServer");
|
||||
// status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// t.setName("KeyExchange Download Thread");
|
||||
// t.setDaemon(true);
|
||||
// t.start();
|
||||
// }
|
||||
// }
|
||||
|
||||
public void scanAgainOnClick(View view) {
|
||||
new IntentIntegrator(this).initiateScan();
|
||||
}
|
||||
|
||||
public void finishOnClick(View view) {
|
||||
finish();
|
||||
}
|
||||
|
||||
public void importOnClick(View view) {
|
||||
Log.d(Constants.TAG, "import key started");
|
||||
|
||||
if (mScannedContent != null) {
|
||||
// Send all information needed to service to import key in other thread
|
||||
Intent intent = new Intent(this, ApgIntentService.class);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_IMPORT_KEY);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putInt(ApgIntentService.IMPORT_KEY_TYPE, Id.type.public_key);
|
||||
|
||||
data.putInt(ApgIntentService.TARGET, ApgIntentService.TARGET_BYTES);
|
||||
data.putByteArray(ApgIntentService.IMPORT_BYTES, mScannedContent.getBytes());
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after importing is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(this,
|
||||
R.string.progress_importing, ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
int added = returnData.getInt(ApgIntentService.RESULT_IMPORT_ADDED);
|
||||
int updated = returnData.getInt(ApgIntentService.RESULT_IMPORT_UPDATED);
|
||||
int bad = returnData.getInt(ApgIntentService.RESULT_IMPORT_BAD);
|
||||
String toastMessage;
|
||||
if (added > 0 && updated > 0) {
|
||||
toastMessage = getString(R.string.keysAddedAndUpdated, added, updated);
|
||||
} else if (added > 0) {
|
||||
toastMessage = getString(R.string.keysAdded, added);
|
||||
} else if (updated > 0) {
|
||||
toastMessage = getString(R.string.keysUpdated, updated);
|
||||
} else {
|
||||
toastMessage = getString(R.string.noKeysAddedOrUpdated);
|
||||
}
|
||||
Toast.makeText(ImportFromQRCodeActivity.this, toastMessage,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
if (bad > 0) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(
|
||||
ImportFromQRCodeActivity.this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(ImportFromQRCodeActivity.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();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public void signAndUploadOnClick(View view) {
|
||||
// first, import!
|
||||
importOnClick(view);
|
||||
|
||||
// TODO: implement sign and upload!
|
||||
Toast.makeText(ImportFromQRCodeActivity.this, "Not implemented right now!",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case IntentIntegrator.REQUEST_CODE: {
|
||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode,
|
||||
data);
|
||||
if (scanResult != null && scanResult.getFormatName() != null) {
|
||||
|
||||
mScannedContent = scanResult.getContents();
|
||||
|
||||
mContentView.setText(mScannedContent);
|
||||
// String[] bits = 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;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
452
APG/src/org/thialfihar/android/apg/ui/KeyListActivity.java
Normal file
452
APG/src/org/thialfihar/android/apg/ui/KeyListActivity.java
Normal file
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* 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.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.service.ApgIntentService;
|
||||
import org.thialfihar.android.apg.service.ApgIntentServiceHandler;
|
||||
import org.thialfihar.android.apg.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.thialfihar.android.apg.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.thialfihar.android.apg.ui.dialog.FileDialogFragment;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.app.SearchManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
public class KeyListActivity extends SherlockFragmentActivity {
|
||||
|
||||
public static final String ACTION_IMPORT = Constants.INTENT_PREFIX + "IMPORT";
|
||||
|
||||
public static final String EXTRA_TEXT = "text";
|
||||
|
||||
// protected View mFilterLayout;
|
||||
// protected Button mClearFilterButton;
|
||||
// protected TextView mFilterInfo;
|
||||
|
||||
protected String mImportFilename = Constants.path.APP_DIR + "/";
|
||||
protected String mExportFilename = Constants.path.APP_DIR + "/";
|
||||
|
||||
protected String mImportData;
|
||||
protected boolean mDeleteAfterImport = false;
|
||||
|
||||
protected int mKeyType;
|
||||
|
||||
FileDialogFragment mFileDialog;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
|
||||
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);
|
||||
|
||||
// Get intent, action
|
||||
// Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(action)) {
|
||||
// Android's Action when opening file associated to APG (see AndroidManifest.xml)
|
||||
|
||||
handleActionImport(intent);
|
||||
} else if (ACTION_IMPORT.equals(action)) {
|
||||
// APG's own Actions
|
||||
|
||||
handleActionImport(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles import action
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionImport(Intent intent) {
|
||||
if ("file".equals(intent.getScheme()) && intent.getDataString() != null) {
|
||||
mImportFilename = intent.getData().getPath();
|
||||
} else {
|
||||
mImportData = intent.getStringExtra(EXTRA_TEXT);
|
||||
}
|
||||
importKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.filename: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = data.getData().getPath();
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
// set filename used in export/import dialogs
|
||||
mFileDialog.setFilename(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!", e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(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(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 android.R.id.home:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
case Id.menu.option.import_keys: {
|
||||
showImportKeysDialog();
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.option.export_keys: {
|
||||
showExportKeysDialog(-1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// case Id.menu.option.search:
|
||||
// startSearch("", false, null, false);
|
||||
// return true;
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show to dialog from where to import keys
|
||||
*/
|
||||
public void showImportKeysDialog() {
|
||||
// Message is received after file is selected
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
mImportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
||||
|
||||
mDeleteAfterImport = data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED);
|
||||
importKeys();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
mFileDialog = FileDialogFragment.newInstance(messenger,
|
||||
getString(R.string.title_importKeys), getString(R.string.specifyFileToImportFrom),
|
||||
mImportFilename, null, Id.request.filename);
|
||||
|
||||
mFileDialog.show(getSupportFragmentManager(), "fileDialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog where to export keys
|
||||
*
|
||||
* @param keyRingId
|
||||
* if -1 export all keys
|
||||
*/
|
||||
public void showExportKeysDialog(final long keyRingId) {
|
||||
String title = null;
|
||||
if (keyRingId != -1) {
|
||||
// single key export
|
||||
title = getString(R.string.title_exportKey);
|
||||
} else {
|
||||
title = getString(R.string.title_exportKeys);
|
||||
}
|
||||
|
||||
String message = null;
|
||||
if (mKeyType == Id.type.public_key) {
|
||||
message = getString(R.string.specifyFileToExportTo);
|
||||
} else {
|
||||
message = getString(R.string.specifyFileToExportSecretKeysTo);
|
||||
}
|
||||
|
||||
// Message is received after file is selected
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
||||
|
||||
exportKeys(keyRingId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
mFileDialog = FileDialogFragment.newInstance(messenger, title, message, mExportFilename,
|
||||
null, Id.request.filename);
|
||||
|
||||
mFileDialog.show(getSupportFragmentManager(), "fileDialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog to delete key
|
||||
*
|
||||
* @param keyRingId
|
||||
*/
|
||||
public void showDeleteKeyDialog(long keyRingId) {
|
||||
// Message is received after key is deleted
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
||||
// refreshList();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
|
||||
keyRingId, mKeyType);
|
||||
|
||||
deleteKeyDialog.show(getSupportFragmentManager(), "deleteKeyDialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Import keys with mImportData
|
||||
*/
|
||||
public void importKeys() {
|
||||
Log.d(Constants.TAG, "importKeys started");
|
||||
|
||||
// Send all information needed to service to import key in other thread
|
||||
Intent intent = new Intent(this, ApgIntentService.class);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_IMPORT_KEY);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putInt(ApgIntentService.IMPORT_KEY_TYPE, mKeyType);
|
||||
|
||||
if (mImportData != null) {
|
||||
data.putInt(ApgIntentService.TARGET, ApgIntentService.TARGET_BYTES);
|
||||
data.putByteArray(ApgIntentService.IMPORT_BYTES, mImportData.getBytes());
|
||||
} else {
|
||||
data.putInt(ApgIntentService.TARGET, ApgIntentService.TARGET_FILE);
|
||||
data.putString(ApgIntentService.IMPORT_FILENAME, mImportFilename);
|
||||
}
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after importing is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(this, R.string.progress_importing,
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
int added = returnData.getInt(ApgIntentService.RESULT_IMPORT_ADDED);
|
||||
int updated = returnData.getInt(ApgIntentService.RESULT_IMPORT_UPDATED);
|
||||
int bad = returnData.getInt(ApgIntentService.RESULT_IMPORT_BAD);
|
||||
String toastMessage;
|
||||
if (added > 0 && updated > 0) {
|
||||
toastMessage = getString(R.string.keysAddedAndUpdated, added, updated);
|
||||
} else if (added > 0) {
|
||||
toastMessage = getString(R.string.keysAdded, added);
|
||||
} else if (updated > 0) {
|
||||
toastMessage = getString(R.string.keysUpdated, updated);
|
||||
} else {
|
||||
toastMessage = getString(R.string.noKeysAddedOrUpdated);
|
||||
}
|
||||
Toast.makeText(KeyListActivity.this, toastMessage, Toast.LENGTH_SHORT).show();
|
||||
if (bad > 0) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(KeyListActivity.this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(KeyListActivity.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
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mImportFilename);
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
// refreshList();
|
||||
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export keys
|
||||
*
|
||||
* @param keyRingId
|
||||
* if -1 export all keys
|
||||
*/
|
||||
public void exportKeys(long keyRingId) {
|
||||
Log.d(Constants.TAG, "exportKeys started");
|
||||
|
||||
// Send all information needed to service to export key in other thread
|
||||
Intent intent = new Intent(this, ApgIntentService.class);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_EXPORT_KEY);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putString(ApgIntentService.EXPORT_FILENAME, mExportFilename);
|
||||
data.putInt(ApgIntentService.EXPORT_KEY_TYPE, mKeyType);
|
||||
|
||||
if (keyRingId == -1) {
|
||||
data.putBoolean(ApgIntentService.EXPORT_ALL, true);
|
||||
} else {
|
||||
data.putLong(ApgIntentService.EXPORT_KEY_RING_ID, keyRingId);
|
||||
}
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after exporting is done in ApgService
|
||||
ApgIntentServiceHandler exportHandler = new ApgIntentServiceHandler(this, R.string.progress_exporting,
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
int exported = returnData.getInt(ApgIntentService.RESULT_EXPORT);
|
||||
String toastMessage;
|
||||
if (exported == 1) {
|
||||
toastMessage = getString(R.string.keyExported);
|
||||
} else if (exported > 0) {
|
||||
toastMessage = getString(R.string.keysExported, exported);
|
||||
} else {
|
||||
toastMessage = getString(R.string.noKeysExported);
|
||||
}
|
||||
Toast.makeText(KeyListActivity.this, toastMessage, Toast.LENGTH_SHORT).show();
|
||||
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(exportHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
exportHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
82
APG/src/org/thialfihar/android/apg/ui/KeyListFragment.java
Normal file
82
APG/src/org/thialfihar/android/apg/ui/KeyListFragment.java
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.ui.widget.ExpandableListFragment;
|
||||
|
||||
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 KeyListFragment extends ExpandableListFragment {
|
||||
protected KeyListActivity mKeyListActivity;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mKeyListActivity = (KeyListActivity) getActivity();
|
||||
|
||||
// register long press context menu
|
||||
registerForContextMenu(getListView());
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.listEmpty));
|
||||
}
|
||||
|
||||
/**
|
||||
* Context Menu on Long Click
|
||||
*/
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
menu.add(0, Id.menu.export, 0, R.string.menu_exportKey);
|
||||
menu.add(0, Id.menu.delete, 1, R.string.menu_deleteKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(android.view.MenuItem item) {
|
||||
ExpandableListContextMenuInfo expInfo = (ExpandableListContextMenuInfo) item.getMenuInfo();
|
||||
|
||||
// expInfo.id would also return row id of childs, but we always want to get the row id of
|
||||
// the group item, thus we are using the following way
|
||||
int groupPosition = ExpandableListView.getPackedPositionGroup(expInfo.packedPosition);
|
||||
long keyRingRowId = getExpandableListAdapter().getGroupId(groupPosition);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.export:
|
||||
mKeyListActivity.showExportKeysDialog(keyRingRowId);
|
||||
return true;
|
||||
|
||||
case Id.menu.delete:
|
||||
mKeyListActivity.showDeleteKeyDialog(keyRingRowId);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
102
APG/src/org/thialfihar/android/apg/ui/KeyListPublicActivity.java
Normal file
102
APG/src/org/thialfihar/android/apg/ui/KeyListPublicActivity.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.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;
|
||||
|
||||
public class KeyListPublicActivity extends KeyListActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mKeyType = Id.type.public_key;
|
||||
|
||||
setContentView(R.layout.key_list_public_activity);
|
||||
|
||||
mExportFilename = Constants.path.APP_DIR + "/pubexport.asc";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
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(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);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.key_server: {
|
||||
startActivityForResult(new Intent(this, KeyServerQueryActivity.class), 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
case Id.menu.option.scanQRCode: {
|
||||
Intent intent = new Intent(this, ImportFromQRCodeActivity.class);
|
||||
intent.setAction(ImportFromQRCodeActivity.IMPORT_FROM_QR_CODE);
|
||||
startActivityForResult(intent, Id.request.import_from_qr_code);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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(KeyServerQueryActivity.RESULT_EXTRA_TEXT) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, KeyListPublicActivity.class);
|
||||
intent.setAction(KeyListPublicActivity.ACTION_IMPORT);
|
||||
intent.putExtra(KeyListPublicActivity.EXTRA_TEXT,
|
||||
data.getStringExtra(KeyListActivity.EXTRA_TEXT));
|
||||
handleIntent(intent);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
183
APG/src/org/thialfihar/android/apg/ui/KeyListPublicFragment.java
Normal file
183
APG/src/org/thialfihar/android/apg/ui/KeyListPublicFragment.java
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.PGPPublicKeyRing;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIds;
|
||||
import org.thialfihar.android.apg.ui.widget.KeyListAdapter;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.View;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||
|
||||
public class KeyListPublicFragment extends KeyListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private KeyListPublicActivity mKeyListPublicActivity;
|
||||
|
||||
private KeyListAdapter mAdapter;
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mKeyListPublicActivity = (KeyListPublicActivity) getActivity();
|
||||
|
||||
mAdapter = new KeyListAdapter(mKeyListPublicActivity, null, Id.type.public_key);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
// id is -1 as the child cursors are numbered 0,...,n
|
||||
getLoaderManager().initLoader(-1, null, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Context Menu on Long Click
|
||||
*/
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
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 item) {
|
||||
ExpandableListContextMenuInfo expInfo = (ExpandableListContextMenuInfo) item.getMenuInfo();
|
||||
|
||||
// expInfo.id would also return row id of childs, but we always want to get the row id of
|
||||
// the group item, thus we are using the following way
|
||||
int groupPosition = ExpandableListView.getPackedPositionGroup(expInfo.packedPosition);
|
||||
long keyRingRowId = getExpandableListAdapter().getGroupId(groupPosition);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.update:
|
||||
long updateKeyId = 0;
|
||||
PGPPublicKeyRing updateKeyRing = ProviderHelper.getPGPPublicKeyRingByRowId(mKeyListActivity,
|
||||
keyRingRowId);
|
||||
if (updateKeyRing != null) {
|
||||
updateKeyId = PGPHelper.getMasterKey(updateKeyRing).getKeyID();
|
||||
}
|
||||
if (updateKeyId == 0) {
|
||||
// this shouldn't happen
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent queryIntent = new Intent(mKeyListActivity, KeyServerQueryActivity.class);
|
||||
queryIntent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID_AND_RETURN);
|
||||
queryIntent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, updateKeyId);
|
||||
|
||||
// TODO: lookup??
|
||||
startActivityForResult(queryIntent, Id.request.look_up_key_id);
|
||||
|
||||
return true;
|
||||
|
||||
case Id.menu.exportToServer:
|
||||
Intent uploadIntent = new Intent(mKeyListActivity, KeyServerUploadActivity.class);
|
||||
uploadIntent.setAction(KeyServerUploadActivity.ACTION_EXPORT_KEY_TO_SERVER);
|
||||
uploadIntent.putExtra(KeyServerUploadActivity.EXTRA_KEYRING_ROW_ID, keyRingRowId);
|
||||
startActivityForResult(uploadIntent, Id.request.export_to_server);
|
||||
|
||||
return true;
|
||||
|
||||
case Id.menu.signKey:
|
||||
long keyId = 0;
|
||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByRowId(mKeyListActivity,
|
||||
keyRingRowId);
|
||||
if (signKeyRing != null) {
|
||||
keyId = PGPHelper.getMasterKey(signKeyRing).getKeyID();
|
||||
}
|
||||
if (keyId == 0) {
|
||||
// this shouldn't happen
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent signIntent = new Intent(mKeyListActivity, SignKeyActivity.class);
|
||||
signIntent.putExtra(SignKeyActivity.EXTRA_KEY_ID, keyId);
|
||||
startActivity(signIntent);
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID };
|
||||
|
||||
static final String SORT_ORDER = UserIds.USER_ID + " ASC";
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildPublicKeyRingsUri();
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, SORT_ORDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.setGroupCursor(data);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// This is called when the last Cursor provided to onLoadFinished()
|
||||
// above is about to be closed. We need to make sure we are no
|
||||
// longer using it.
|
||||
mAdapter.setGroupCursor(null);
|
||||
}
|
||||
|
||||
}
|
||||
124
APG/src/org/thialfihar/android/apg/ui/KeyListSecretActivity.java
Normal file
124
APG/src/org/thialfihar/android/apg/ui/KeyListSecretActivity.java
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.PGPMain;
|
||||
import org.thialfihar.android.apg.service.PassphraseCacheService;
|
||||
import org.thialfihar.android.apg.ui.dialog.PassphraseDialogFragment;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
|
||||
public class KeyListSecretActivity extends KeyListActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mKeyType = Id.type.secret_key;
|
||||
|
||||
setContentView(R.layout.key_list_secret_activity);
|
||||
|
||||
mExportFilename = Constants.path.APP_DIR + "/secexport.asc";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
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);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.create: {
|
||||
createKey();
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void checkPassPhraseAndEdit(long keyId) {
|
||||
String passPhrase = PassphraseCacheService.getCachedPassphrase(this, keyId);
|
||||
if (passPhrase == null) {
|
||||
showPassphraseDialog(keyId);
|
||||
} else {
|
||||
PGPMain.setEditPassPhrase(passPhrase);
|
||||
editKey(keyId);
|
||||
}
|
||||
}
|
||||
|
||||
private void showPassphraseDialog(final long secretKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
String passPhrase = PassphraseCacheService.getCachedPassphrase(
|
||||
KeyListSecretActivity.this, secretKeyId);
|
||||
PGPMain.setEditPassPhrase(passPhrase);
|
||||
editKey(secretKeyId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(
|
||||
KeyListSecretActivity.this, messenger, secretKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PGPMain.ApgGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
PGPMain.setEditPassPhrase("");
|
||||
Intent intent = new Intent(EditKeyActivity.ACTION_CREATE_KEY);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
private void editKey(long keyId) {
|
||||
Intent intent = new Intent(EditKeyActivity.ACTION_EDIT_KEY);
|
||||
intent.putExtra(EditKeyActivity.EXTRA_KEY_ID, keyId);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
}
|
||||
154
APG/src/org/thialfihar/android/apg/ui/KeyListSecretFragment.java
Normal file
154
APG/src/org/thialfihar/android/apg/ui/KeyListSecretFragment.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIds;
|
||||
import org.thialfihar.android.apg.ui.widget.KeyListAdapter;
|
||||
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.View;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||
|
||||
public class KeyListSecretFragment extends KeyListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private KeyListSecretActivity mKeyListSecretActivity;
|
||||
|
||||
private KeyListAdapter mAdapter;
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mKeyListSecretActivity = (KeyListSecretActivity) getActivity();
|
||||
|
||||
mAdapter = new KeyListAdapter(mKeyListSecretActivity, null, Id.type.secret_key);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
// id is -1 as the child cursors are numbered 0,...,n
|
||||
getLoaderManager().initLoader(-1, null, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Context Menu on Long Click
|
||||
*/
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
menu.add(0, Id.menu.edit, 0, R.string.menu_editKey);
|
||||
menu.add(0, Id.menu.share_qr_code, 2, R.string.menu_share);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(android.view.MenuItem item) {
|
||||
ExpandableListContextMenuInfo expInfo = (ExpandableListContextMenuInfo) item.getMenuInfo();
|
||||
|
||||
// expInfo.id would also return row id of childs, but we always want to get the row id of
|
||||
// the group item, thus we are using the following way
|
||||
int groupPosition = ExpandableListView.getPackedPositionGroup(expInfo.packedPosition);
|
||||
long keyRingRowId = getExpandableListAdapter().getGroupId(groupPosition);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.edit:
|
||||
// TODO: do it better directly with keyRingRowId?
|
||||
long masterKeyId = ProviderHelper.getSecretMasterKeyId(mKeyListSecretActivity,
|
||||
keyRingRowId);
|
||||
|
||||
mKeyListSecretActivity.checkPassPhraseAndEdit(masterKeyId);
|
||||
return true;
|
||||
|
||||
case Id.menu.share_qr_code:
|
||||
// TODO: do it better directly with keyRingRowId?
|
||||
long masterKeyId2 = ProviderHelper.getSecretMasterKeyId(mKeyListSecretActivity,
|
||||
keyRingRowId);
|
||||
|
||||
String msg = PGPHelper.getPubkeyAsArmoredString(mKeyListSecretActivity, masterKeyId2);
|
||||
|
||||
new IntentIntegrator(mKeyListSecretActivity).shareText(msg);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID };
|
||||
|
||||
static final String SORT_ORDER = UserIds.USER_ID + " ASC";
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildSecretKeyRingsUri();
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, SORT_ORDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.setGroupCursor(data);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// This is called when the last Cursor provided to onLoadFinished()
|
||||
// above is about to be closed. We need to make sure we are no
|
||||
// longer using it.
|
||||
mAdapter.setGroupCursor(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.helper.Preferences;
|
||||
import org.thialfihar.android.apg.service.ApgIntentServiceHandler;
|
||||
import org.thialfihar.android.apg.service.ApgIntentService;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
import org.thialfihar.android.apg.util.KeyServer.KeyInfo;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
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 SherlockFragmentActivity {
|
||||
|
||||
// possible intent actions for this activity
|
||||
public static final String ACTION_LOOK_UP_KEY_ID = Constants.INTENT_PREFIX + "LOOK_UP_KEY_ID";
|
||||
public static final String ACTION_LOOK_UP_KEY_ID_AND_RETURN = Constants.INTENT_PREFIX
|
||||
+ "LOOK_UP_KEY_ID_AND_RETURN";
|
||||
|
||||
public static final String EXTRA_KEY_ID = "keyId";
|
||||
|
||||
public static final String RESULT_EXTRA_TEXT = "text";
|
||||
|
||||
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:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, KeyListPublicActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
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, Preferences.getPreferences(this)
|
||||
.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();
|
||||
String action = intent.getAction();
|
||||
if (ACTION_LOOK_UP_KEY_ID.equals(action) || ACTION_LOOK_UP_KEY_ID_AND_RETURN.equals(action)) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
|
||||
if (keyId != 0) {
|
||||
String query = "0x" + PGPHelper.keyToHex(keyId);
|
||||
mQuery.setText(query);
|
||||
search(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void search(String query) {
|
||||
mQueryType = Id.keyserver.search;
|
||||
mQueryString = query;
|
||||
mAdapter.setKeys(new ArrayList<KeyInfo>());
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
private void get(long keyId) {
|
||||
mQueryType = Id.keyserver.get;
|
||||
mQueryId = keyId;
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
Log.d(Constants.TAG, "start search with service");
|
||||
|
||||
// Send all information needed to service to query keys in other thread
|
||||
Intent intent = new Intent(this, ApgIntentService.class);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_QUERY_KEY);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
String server = (String) mKeyServer.getSelectedItem();
|
||||
data.putString(ApgIntentService.QUERY_KEY_SERVER, server);
|
||||
|
||||
data.putInt(ApgIntentService.QUERY_KEY_TYPE, mQueryType);
|
||||
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
data.putString(ApgIntentService.QUERY_KEY_STRING, mQueryString);
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
data.putLong(ApgIntentService.QUERY_KEY_ID, mQueryId);
|
||||
}
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after querying is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(this, R.string.progress_querying,
|
||||
ProgressDialog.STYLE_SPINNER) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
mSearchResult = returnData
|
||||
.getParcelableArrayList(ApgIntentService.RESULT_QUERY_KEY_SEARCH_RESULT);
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
mKeyData = returnData.getString(ApgIntentService.RESULT_QUERY_KEY_KEY_DATA);
|
||||
}
|
||||
|
||||
// TODO: IMPROVE CODE!!! some global variables can be avoided!!!
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
if (mSearchResult != null) {
|
||||
Toast.makeText(KeyServerQueryActivity.this,
|
||||
getString(R.string.keysFound, mSearchResult.size()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
mAdapter.setKeys(mSearchResult);
|
||||
}
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
Intent orgIntent = getIntent();
|
||||
if (ACTION_LOOK_UP_KEY_ID_AND_RETURN.equals(orgIntent.getAction())) {
|
||||
if (mKeyData != null) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(RESULT_EXTRA_TEXT, mKeyData);
|
||||
setResult(RESULT_OK, intent);
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
}
|
||||
finish();
|
||||
} else {
|
||||
if (mKeyData != null) {
|
||||
Intent intent = new Intent(KeyServerQueryActivity.this,
|
||||
KeyListPublicActivity.class);
|
||||
intent.setAction(KeyListActivity.ACTION_IMPORT);
|
||||
intent.putExtra(KeyListActivity.EXTRA_TEXT, mKeyData);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(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 ArrayList<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(PGPHelper.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.MATCH_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
* 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.Constants;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.Preferences;
|
||||
import org.thialfihar.android.apg.service.ApgIntentServiceHandler;
|
||||
import org.thialfihar.android.apg.service.ApgIntentService;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
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 KeyServerUploadActivity extends SherlockFragmentActivity {
|
||||
|
||||
// Not used in sourcode, but listed in AndroidManifest!
|
||||
public static final String ACTION_EXPORT_KEY_TO_SERVER = Constants.INTENT_PREFIX
|
||||
+ "EXPORT_KEY_TO_SERVER";
|
||||
|
||||
public static final String EXTRA_KEYRING_ROW_ID = "keyId";
|
||||
|
||||
private Button export;
|
||||
private Spinner keyServer;
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, KeyListPublicActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
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, Preferences.getPreferences(this)
|
||||
.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) {
|
||||
uploadKey();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void uploadKey() {
|
||||
// Send all information needed to service to upload key in other thread
|
||||
Intent intent = new Intent(this, ApgIntentService.class);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_UPLOAD_KEY);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
int keyRingId = getIntent().getIntExtra(EXTRA_KEYRING_ROW_ID, -1);
|
||||
data.putInt(ApgIntentService.UPLOAD_KEY_KEYRING_ROW_ID, keyRingId);
|
||||
|
||||
String server = (String) keyServer.getSelectedItem();
|
||||
data.putString(ApgIntentService.UPLOAD_KEY_SERVER, server);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after uploading is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(this, R.string.progress_exporting,
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
Toast.makeText(KeyServerUploadActivity.this, R.string.keySendSuccess,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
102
APG/src/org/thialfihar/android/apg/ui/MainActivity.java
Normal file
102
APG/src/org/thialfihar/android/apg/ui/MainActivity.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
public class MainActivity extends SherlockActivity {
|
||||
|
||||
public void manageKeysOnClick(View view) {
|
||||
// used instead of startActivity set actionbar based on callingPackage
|
||||
startActivityForResult(new Intent(this, KeyListPublicActivity.class), 0);
|
||||
}
|
||||
|
||||
public void myKeysOnClick(View view) {
|
||||
// used instead of startActivity set actionbar based on callingPackage
|
||||
startActivityForResult(new Intent(this, KeyListSecretActivity.class), 0);
|
||||
}
|
||||
|
||||
public void encryptOnClick(View view) {
|
||||
Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
|
||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||
// used instead of startActivity set actionbar based on callingPackage
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
public void decryptOnClick(View view) {
|
||||
Intent intent = new Intent(MainActivity.this, DecryptActivity.class);
|
||||
intent.setAction(DecryptActivity.ACTION_DECRYPT);
|
||||
// used instead of startActivity set actionbar based on callingPackage
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
public void scanQrcodeOnClick(View view) {
|
||||
Intent intent = new Intent(this, ImportFromQRCodeActivity.class);
|
||||
intent.setAction(ImportFromQRCodeActivity.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);
|
||||
}
|
||||
|
||||
@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);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case Id.menu.option.preferences:
|
||||
startActivity(new Intent(this, PreferencesActivity.class));
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
240
APG/src/org/thialfihar/android/apg/ui/PreferencesActivity.java
Normal file
240
APG/src/org/thialfihar/android/apg/ui/PreferencesActivity.java
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* 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.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.helper.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);
|
||||
actionBar.setHomeButtonEnabled(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()));
|
||||
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,
|
||||
PreferencesKeyServerActivity.class);
|
||||
intent.putExtra(PreferencesKeyServerActivity.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(PreferencesKeyServerActivity.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:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
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.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
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.TextView;
|
||||
|
||||
public class PreferencesKeyServerActivity extends SherlockActivity implements OnClickListener,
|
||||
EditorListener {
|
||||
|
||||
public static final String EXTRA_KEY_SERVERS = "keyServers";
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
private ViewGroup mEditors;
|
||||
private View mAdd;
|
||||
private TextView mTitle;
|
||||
private TextView mSummary;
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, PreferencesActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
|
||||
return true;
|
||||
|
||||
case Id.menu.option.okay:
|
||||
okClicked();
|
||||
|
||||
return true;
|
||||
|
||||
case Id.menu.option.cancel:
|
||||
cancelClicked();
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ActionBar menu is created based on class variables to change it at runtime
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
|
||||
menu.add(1, Id.menu.option.cancel, 0, android.R.string.cancel).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
|
||||
menu.add(1, Id.menu.option.okay, 1, android.R.string.ok).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.key_server_preference);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
|
||||
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(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(EXTRA_KEY_SERVERS, servers.toArray(dummy));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class SelectPublicKeyActivity extends SherlockFragmentActivity {
|
||||
|
||||
// Not used in sourcode, but listed in AndroidManifest!
|
||||
public static final String ACTION_SELECT_PUBLIC_KEYS = Constants.INTENT_PREFIX
|
||||
+ "SELECT_PUBLIC_KEYS";
|
||||
|
||||
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "masterKeyIds";
|
||||
|
||||
public static final String RESULT_EXTRA_MASTER_KEY_IDS = "masterKeyIds";
|
||||
public static final String RESULT_EXTRA_USER_IDS = "userIds";
|
||||
|
||||
SelectPublicKeyFragment mSelectFragment;
|
||||
|
||||
long selectedMasterKeyIds[];
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.select_public_key_activity);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
mSelectFragment = (SelectPublicKeyFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.select_public_key_fragment);
|
||||
|
||||
//
|
||||
// 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));
|
||||
// }
|
||||
|
||||
// preselected master keys
|
||||
selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns preselected key ids, this is used in the fragment
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public long[] getSelectedMasterKeyIds() {
|
||||
return selectedMasterKeyIds;
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void okClicked() {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_IDS, mSelectFragment.getSelectedMasterKeyIds());
|
||||
data.putExtra(RESULT_EXTRA_USER_IDS, mSelectFragment.getSelectedUserIds());
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu Options
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
case Id.menu.option.okay:
|
||||
okClicked();
|
||||
return true;
|
||||
|
||||
case Id.menu.option.cancel:
|
||||
cancelClicked();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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 java.util.Date;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.provider.ApgDatabase;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.Keys;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIds;
|
||||
import org.thialfihar.android.apg.provider.ApgDatabase.Tables;
|
||||
import org.thialfihar.android.apg.ui.widget.SelectKeyCursorAdapter;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListFragment;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class SelectPublicKeyFragment extends SherlockListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private SelectPublicKeyActivity mActivity;
|
||||
private SelectKeyCursorAdapter mAdapter;
|
||||
private ListView mListView;
|
||||
|
||||
private long mSelectedMasterKeyIds[];
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mActivity = (SelectPublicKeyActivity) getSherlockActivity();
|
||||
mListView = getListView();
|
||||
|
||||
// get selected master key ids, which are given to activity by intent
|
||||
mSelectedMasterKeyIds = mActivity.getSelectedMasterKeyIds();
|
||||
|
||||
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.listEmpty));
|
||||
|
||||
mAdapter = new SelectKeyCursorAdapter(mActivity, mListView, null, Id.type.public_key);
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround for Android 4.1. Items are not checked in layout. See
|
||||
* http://code.google.com/p/android/issues/detail?id=35885
|
||||
*/
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
l.setItemChecked(position, l.isItemChecked(position));
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects items based on master key ids in list view
|
||||
*
|
||||
* @param masterKeyIds
|
||||
*/
|
||||
private void preselectMasterKeyIds(long[] masterKeyIds) {
|
||||
if (masterKeyIds != null) {
|
||||
for (int i = 0; i < mListView.getCount(); ++i) {
|
||||
long keyId = mAdapter.getMasterKeyId(i);
|
||||
for (int j = 0; j < masterKeyIds.length; ++j) {
|
||||
if (keyId == masterKeyIds[j]) {
|
||||
mListView.setItemChecked(i, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all selected master key ids
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public long[] getSelectedMasterKeyIds() {
|
||||
// mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key
|
||||
// ids!
|
||||
Vector<Long> vector = new Vector<Long>();
|
||||
for (int i = 0; i < mListView.getCount(); ++i) {
|
||||
if (mListView.isItemChecked(i)) {
|
||||
vector.add(mAdapter.getMasterKeyId(i));
|
||||
}
|
||||
}
|
||||
|
||||
// convert to long array
|
||||
long[] selectedMasterKeyIds = new long[vector.size()];
|
||||
for (int i = 0; i < vector.size(); ++i) {
|
||||
selectedMasterKeyIds[i] = vector.get(i);
|
||||
}
|
||||
|
||||
return selectedMasterKeyIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all selected user ids
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] getSelectedUserIds() {
|
||||
Vector<String> userIds = new Vector<String>();
|
||||
for (int i = 0; i < mListView.getCount(); ++i) {
|
||||
if (mListView.isItemChecked(i)) {
|
||||
userIds.add((String) mAdapter.getUserId(i));
|
||||
}
|
||||
}
|
||||
|
||||
// make empty array to not return null
|
||||
String userIdArray[] = new String[0];
|
||||
return userIds.toArray(userIdArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildPublicKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
long now = new Date().getTime() / 1000;
|
||||
String[] projection = new String[] {
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID,
|
||||
"(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ ApgDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||
+ " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
|
||||
+ Keys.CAN_ENCRYPT + " = '1') AS "
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ ApgDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys."
|
||||
+ Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_ENCRYPT
|
||||
+ " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND "
|
||||
+ "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY
|
||||
+ " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
|
||||
String inMasterKeyList = null;
|
||||
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
|
||||
inMasterKeyList = KeyRings.MASTER_KEY_ID + " IN (";
|
||||
for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) {
|
||||
if (i != 0) {
|
||||
inMasterKeyList += ", ";
|
||||
}
|
||||
inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]);
|
||||
}
|
||||
inMasterKeyList += ")";
|
||||
}
|
||||
|
||||
// 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.USER_ID + " ASC";
|
||||
if (inMasterKeyList != null) {
|
||||
// sort by selected master keys
|
||||
orderBy = inMasterKeyList + " DESC, " + orderBy;
|
||||
}
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, projection, null, null, orderBy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
|
||||
// preselect given master keys
|
||||
preselectMasterKeyIds(mSelectedMasterKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// This is called when the last Cursor provided to onLoadFinished()
|
||||
// above is about to be closed. We need to make sure we are no
|
||||
// longer using it.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class SelectSecretKeyActivity extends SherlockFragmentActivity {
|
||||
|
||||
// Not used in sourcode, but listed in AndroidManifest!
|
||||
public static final String ACTION_SELECT_SECRET_KEY = Constants.INTENT_PREFIX
|
||||
+ "SELECT_SECRET_KEY";
|
||||
|
||||
public static final String RESULT_EXTRA_MASTER_KEY_ID = "masterKeyId";
|
||||
public static final String RESULT_EXTRA_USER_ID = "userId";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.select_secret_key_activity);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* This is executed by SelectSecretKeyFragment after clicking on an item
|
||||
*
|
||||
* @param masterKeyId
|
||||
* @param userId
|
||||
*/
|
||||
public void afterListSelection(long masterKeyId, String userId) {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, masterKeyId);
|
||||
data.putExtra(RESULT_EXTRA_USER_ID, (String) userId);
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@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));
|
||||
// }
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu Options
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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 java.util.Date;
|
||||
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.provider.ApgDatabase;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.Keys;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIds;
|
||||
import org.thialfihar.android.apg.provider.ApgDatabase.Tables;
|
||||
import org.thialfihar.android.apg.ui.widget.SelectKeyCursorAdapter;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListFragment;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class SelectSecretKeyFragment extends SherlockListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private SelectSecretKeyActivity mActivity;
|
||||
private SelectKeyCursorAdapter mAdapter;
|
||||
private ListView mListView;
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mActivity = (SelectSecretKeyActivity) getSherlockActivity();
|
||||
mListView = getListView();
|
||||
|
||||
mListView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
long masterKeyId = mAdapter.getMasterKeyId(position);
|
||||
String userId = mAdapter.getUserId(position);
|
||||
|
||||
// return data to activity, which results in finishing it
|
||||
mActivity.afterListSelection(masterKeyId, userId);
|
||||
}
|
||||
});
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.listEmpty));
|
||||
|
||||
mAdapter = new SelectKeyCursorAdapter(mActivity, mListView, null, Id.type.secret_key);
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildPublicKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
long now = new Date().getTime() / 1000;
|
||||
String[] projection = new String[] {
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID,
|
||||
"(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ ApgDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||
+ " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
|
||||
+ Keys.CAN_SIGN + " = '1') AS "
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ ApgDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys."
|
||||
+ Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_SIGN
|
||||
+ " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND "
|
||||
+ "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY
|
||||
+ " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
|
||||
// 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(")");
|
||||
// }
|
||||
|
||||
String orderBy = UserIds.USER_ID + " ASC";
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, projection, null, null, orderBy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// This is called when the last Cursor provided to onLoadFinished()
|
||||
// above is about to be closed. We need to make sure we are no
|
||||
// longer using it.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
}
|
||||
313
APG/src/org/thialfihar/android/apg/ui/SignKeyActivity.java
Normal file
313
APG/src/org/thialfihar/android/apg/ui/SignKeyActivity.java
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
* 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.Iterator;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.PGPMain;
|
||||
import org.thialfihar.android.apg.helper.Preferences;
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
import org.thialfihar.android.apg.service.ApgIntentService;
|
||||
import org.thialfihar.android.apg.service.ApgIntentServiceHandler;
|
||||
import org.thialfihar.android.apg.service.PassphraseCacheService;
|
||||
import org.thialfihar.android.apg.ui.dialog.PassphraseDialogFragment;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
|
||||
import org.thialfihar.android.apg.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 SherlockFragmentActivity {
|
||||
|
||||
public static final String EXTRA_KEY_ID = "keyId";
|
||||
|
||||
private long mPubKeyId = 0;
|
||||
private long mMasterKeyId = 0;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// check we havent already signed it
|
||||
setContentView(R.layout.sign_key_layout);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
final Spinner keyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
||||
.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 (mPubKeyId != 0) {
|
||||
initiateSigning();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mPubKeyId = getIntent().getLongExtra(EXTRA_KEY_ID, 0);
|
||||
if (mPubKeyId == 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, SelectSecretKeyActivity.class);
|
||||
startActivityForResult(intent, Id.request.secret_keys);
|
||||
}
|
||||
}
|
||||
|
||||
private void showPassphraseDialog(final long secretKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
startSigning();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
|
||||
messenger, secretKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PGPMain.ApgGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handles the UI bits of the signing process on the UI thread
|
||||
*/
|
||||
private void initiateSigning() {
|
||||
PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this, mPubKeyId);
|
||||
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(mPubKeyId).getSignatures();
|
||||
while (itr.hasNext()) {
|
||||
PGPSignature sig = itr.next();
|
||||
if (sig.getKeyID() == mMasterKeyId) {
|
||||
alreadySigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadySigned) {
|
||||
/*
|
||||
* get the user's passphrase for this key (if required)
|
||||
*/
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
|
||||
if (passphrase == null) {
|
||||
showPassphraseDialog(mMasterKeyId);
|
||||
return; // bail out; need to wait until the user has entered the passphrase
|
||||
// before trying again
|
||||
} else {
|
||||
startSigning();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "Key has already been signed", Toast.LENGTH_SHORT).show();
|
||||
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kicks off the actual signing process on a background thread
|
||||
*/
|
||||
private void startSigning() {
|
||||
// Send all information needed to service to sign key in other thread
|
||||
Intent intent = new Intent(this, ApgIntentService.class);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_SIGN_KEY);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putLong(ApgIntentService.SIGN_KEY_MASTER_KEY_ID, mMasterKeyId);
|
||||
data.putLong(ApgIntentService.SIGN_KEY_PUB_KEY_ID, mPubKeyId);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after signing is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(this, R.string.progress_signing,
|
||||
ProgressDialog.STYLE_SPINNER) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
Toast.makeText(SignKeyActivity.this, R.string.keySignSuccess,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
// check if we need to send the key to the server or not
|
||||
CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey);
|
||||
if (sendKey.isChecked()) {
|
||||
/*
|
||||
* upload the newly signed key to the key server
|
||||
*/
|
||||
uploadKey();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
private void uploadKey() {
|
||||
// Send all information needed to service to upload key in other thread
|
||||
Intent intent = new Intent(this, ApgIntentService.class);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_UPLOAD_KEY);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putLong(ApgIntentService.UPLOAD_KEY_KEYRING_ROW_ID, mPubKeyId);
|
||||
|
||||
Spinner keyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
String server = (String) keyServer.getSelectedItem();
|
||||
data.putString(ApgIntentService.UPLOAD_KEY_SERVER, server);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after uploading is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(this, R.string.progress_exporting,
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
Toast.makeText(SignKeyActivity.this, R.string.keySendSuccess,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
finish();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.secret_keys: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
mMasterKeyId = data.getLongExtra(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.dialog;
|
||||
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.service.ApgIntentServiceHandler;
|
||||
import org.thialfihar.android.apg.service.ApgIntentService;
|
||||
|
||||
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.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class DeleteFileDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String ARG_DELETE_FILE = "delete_file";
|
||||
|
||||
/**
|
||||
* Creates new instance of this delete file dialog fragment
|
||||
*/
|
||||
public static DeleteFileDialogFragment newInstance(String deleteFile) {
|
||||
DeleteFileDialogFragment frag = new DeleteFileDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putString(ARG_DELETE_FILE, deleteFile);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
|
||||
final String deleteFile = getArguments().getString(ARG_DELETE_FILE);
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(this.getString(R.string.fileDeleteConfirmation, deleteFile));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(activity, ApgIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_DELETE_FILE_SECURELY);
|
||||
data.putString(ApgIntentService.DELETE_FILE, deleteFile);
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance(
|
||||
R.string.progress_deletingSecurely, ProgressDialog.STYLE_HORIZONTAL);
|
||||
|
||||
// Message is received after deleting is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(activity, deletingDialog) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Toast.makeText(activity, R.string.fileDeleteSuccessful,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
deletingDialog.show(activity.getSupportFragmentManager(), "deletingDialog");
|
||||
|
||||
// start service with intent
|
||||
activity.startService(intent);
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.dialog;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_DELETE_KEY_RING_ROW_ID = "delete_file";
|
||||
private static final String ARG_KEY_TYPE = "key_type";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
|
||||
/**
|
||||
* Creates new instance of this delete file dialog fragment
|
||||
*/
|
||||
public static DeleteKeyDialogFragment newInstance(Messenger messenger, long deleteKeyRingRowId,
|
||||
int keyType) {
|
||||
DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
args.putLong(ARG_DELETE_KEY_RING_ROW_ID, deleteKeyRingRowId);
|
||||
args.putInt(ARG_KEY_TYPE, keyType);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
|
||||
final long deleteKeyRingRowId = getArguments().getLong(ARG_DELETE_KEY_RING_ROW_ID);
|
||||
final int keyType = getArguments().getInt(ARG_KEY_TYPE);
|
||||
|
||||
// TODO: better way to do this?
|
||||
String userId = activity.getString(R.string.unknownUserId);
|
||||
|
||||
if (keyType == Id.type.public_key) {
|
||||
PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByRowId(activity,
|
||||
deleteKeyRingRowId);
|
||||
userId = PGPHelper.getMainUserIdSafe(activity, PGPHelper.getMasterKey(keyRing));
|
||||
} else {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByRowId(activity,
|
||||
deleteKeyRingRowId);
|
||||
userId = PGPHelper.getMainUserIdSafe(activity, PGPHelper.getMasterKey(keyRing));
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(R.string.warning);
|
||||
builder.setMessage(getString(
|
||||
keyType == 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) {
|
||||
if (keyType == Id.type.public_key) {
|
||||
ProviderHelper.deletePublicKeyRing(activity, deleteKeyRingRowId);
|
||||
} else {
|
||||
ProviderHelper.deleteSecretKeyRing(activity, deleteKeyRingRowId);
|
||||
}
|
||||
|
||||
dismiss();
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.dialog;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.FileHelper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
public class FileDialogFragment extends DialogFragment {
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_TITLE = "title";
|
||||
private static final String ARG_MESSAGE = "message";
|
||||
private static final String ARG_DEFAULT_FILE = "default_file";
|
||||
private static final String ARG_CHECKBOX_TEXT = "checkbox_text";
|
||||
private static final String ARG_REQUEST_CODE = "request_code";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
|
||||
public static final String MESSAGE_DATA_FILENAME = "filename";
|
||||
public static final String MESSAGE_DATA_CHECKED = "checked";
|
||||
|
||||
/**
|
||||
* Creates new instance of this file dialog fragment
|
||||
*/
|
||||
public static FileDialogFragment newInstance(Messenger messenger, String title, String message,
|
||||
String defaultFile, String checkboxText, int requestCode) {
|
||||
FileDialogFragment frag = new FileDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
args.putString(ARG_TITLE, title);
|
||||
args.putString(ARG_MESSAGE, message);
|
||||
args.putString(ARG_DEFAULT_FILE, defaultFile);
|
||||
args.putString(ARG_CHECKBOX_TEXT, checkboxText);
|
||||
args.putInt(ARG_REQUEST_CODE, requestCode);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
String title = getArguments().getString(ARG_TITLE);
|
||||
String message = getArguments().getString(ARG_MESSAGE);
|
||||
String defaultFile = getArguments().getString(ARG_DEFAULT_FILE);
|
||||
String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT);
|
||||
final int requestCode = getArguments().getInt(ARG_REQUEST_CODE);
|
||||
|
||||
final EditText mFilename;
|
||||
final ImageButton mBrowse;
|
||||
final CheckBox mCheckBox;
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
// only .asc or .gpg files
|
||||
FileHelper.openFile(activity, mFilename.getText().toString(), "text/plain", 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);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
boolean checked = false;
|
||||
if (mCheckBox.isEnabled()) {
|
||||
checked = mCheckBox.isChecked();
|
||||
}
|
||||
|
||||
// return resulting data back to activity
|
||||
Bundle data = new Bundle();
|
||||
data.putString(MESSAGE_DATA_FILENAME, mFilename.getText().toString());
|
||||
data.putBoolean(MESSAGE_DATA_CHECKED, checked);
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY, data);
|
||||
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates filename in dialog, normally called in onActivityResult in activity using the
|
||||
* FileDialog
|
||||
*
|
||||
* @param messageId
|
||||
* @param progress
|
||||
* @param max
|
||||
*/
|
||||
public void setFilename(String filename) {
|
||||
AlertDialog dialog = (AlertDialog) getDialog();
|
||||
EditText filenameEditText = (EditText) dialog.findViewById(R.id.input);
|
||||
|
||||
if (filenameEditText != null) {
|
||||
filenameEditText.setText(filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.dialog;
|
||||
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import org.thialfihar.android.apg.ui.KeyServerQueryActivity;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
public class LookupUnknownKeyDialogFragment extends DialogFragment {
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_UNKNOWN_KEY_ID = "unknown_key_id";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
public static final int MESSAGE_CANCEL = 2;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*
|
||||
* @param messenger
|
||||
* @param unknownKeyId
|
||||
* @return
|
||||
*/
|
||||
public static LookupUnknownKeyDialogFragment newInstance(Messenger messenger, long unknownKeyId) {
|
||||
LookupUnknownKeyDialogFragment frag = new LookupUnknownKeyDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(ARG_UNKNOWN_KEY_ID, unknownKeyId);
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
final long unknownKeyId = getArguments().getLong(ARG_UNKNOWN_KEY_ID);
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.title_unknownSignatureKey);
|
||||
alert.setMessage(getString(R.string.lookupUnknownKey,
|
||||
PGPHelper.getSmallFingerPrint(unknownKeyId)));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY);
|
||||
|
||||
Intent intent = new Intent(activity, KeyServerQueryActivity.class);
|
||||
intent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID);
|
||||
intent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, unknownKeyId);
|
||||
startActivityForResult(intent, Id.request.look_up_key_id);
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
alert.setOnCancelListener(new OnCancelListener() {
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* 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.dialog;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPMain;
|
||||
import org.thialfihar.android.apg.helper.PGPMain.ApgGeneralException;
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
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.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import org.thialfihar.android.apg.provider.ProviderHelper;
|
||||
import org.thialfihar.android.apg.service.PassphraseCacheService;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class PassphraseDialogFragment extends DialogFragment implements OnEditorActionListener {
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_SECRET_KEY_ID = "secret_key_id";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
|
||||
private EditText mPassphraseEditText;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*
|
||||
* @param secretKeyId
|
||||
* secret key id you want to use
|
||||
* @param messenger
|
||||
* to communicate back after caching the passphrase
|
||||
* @return
|
||||
* @throws ApgGeneralException
|
||||
*/
|
||||
public static PassphraseDialogFragment newInstance(Context context, Messenger messenger,
|
||||
long secretKeyId) throws ApgGeneralException {
|
||||
// check if secret key has a passphrase
|
||||
if (!(secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none)) {
|
||||
if (!hasPassphrase(context, secretKeyId)) {
|
||||
throw new PGPMain.ApgGeneralException("No passphrase! No passphrase dialog needed!");
|
||||
}
|
||||
}
|
||||
|
||||
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(ARG_SECRET_KEY_ID, secretKeyId);
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if key has a passphrase
|
||||
*
|
||||
* @param secretKeyId
|
||||
* @return true if it has a passphrase
|
||||
*/
|
||||
private static boolean hasPassphrase(Context context, long secretKeyId) {
|
||||
// check if the key has no passphrase
|
||||
try {
|
||||
PGPSecretKey secretKey = PGPHelper.getMasterKey(ProviderHelper
|
||||
.getPGPSecretKeyRingByKeyId(context, secretKeyId));
|
||||
// PGPSecretKey secretKey =
|
||||
// PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
|
||||
|
||||
Log.d(Constants.TAG, "Check if key has no passphrase...");
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
"SC").build("".toCharArray());
|
||||
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
|
||||
if (testKey != null) {
|
||||
Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
|
||||
|
||||
// cache empty passphrase
|
||||
PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
// silently catch
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
long secretKeyId = getArguments().getLong(ARG_SECRET_KEY_ID);
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setTitle(R.string.title_authentication);
|
||||
|
||||
final PGPSecretKey secretKey;
|
||||
|
||||
if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) {
|
||||
secretKey = null;
|
||||
alert.setMessage(R.string.passPhraseForSymmetricEncryption);
|
||||
} else {
|
||||
// TODO: by master key id???
|
||||
secretKey = PGPHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
|
||||
activity, secretKeyId));
|
||||
// secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
|
||||
|
||||
if (secretKey == null) {
|
||||
alert.setTitle(R.string.title_keyNotFound);
|
||||
alert.setMessage(getString(R.string.keyNotFound, secretKeyId));
|
||||
alert.setPositiveButton(android.R.string.ok, new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
return alert.create();
|
||||
}
|
||||
String userId = PGPHelper.getMainUserIdSafe(activity, secretKey);
|
||||
|
||||
Log.d(Constants.TAG, "User id: '" + userId + "'");
|
||||
alert.setMessage(getString(R.string.passPhraseFor, userId));
|
||||
}
|
||||
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.passphrase, null);
|
||||
alert.setView(view);
|
||||
|
||||
mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
String passPhrase = mPassphraseEditText.getText().toString();
|
||||
long keyId;
|
||||
if (secretKey != null) {
|
||||
try {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(PGPMain.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
passPhrase.toCharArray());
|
||||
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
|
||||
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;
|
||||
}
|
||||
|
||||
// cache the new passphrase
|
||||
Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
|
||||
PassphraseCacheService.addCachedPassphrase(activity, keyId, passPhrase);
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY);
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle arg0) {
|
||||
super.onActivityCreated(arg0);
|
||||
|
||||
// request focus and open soft keyboard
|
||||
mPassphraseEditText.requestFocus();
|
||||
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the "done" button on the soft keyboard with the okay button in the view
|
||||
*/
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (EditorInfo.IME_ACTION_DONE == actionId) {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
|
||||
bt.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnKeyListener;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
public class ProgressDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String ARG_MESSAGE_ID = "message_id";
|
||||
private static final String ARG_STYLE = "style";
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public static ProgressDialogFragment newInstance(int messageId, int style) {
|
||||
ProgressDialogFragment frag = new ProgressDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_MESSAGE_ID, messageId);
|
||||
args.putInt(ARG_STYLE, style);
|
||||
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates progress of dialog
|
||||
*
|
||||
* @param messageId
|
||||
* @param progress
|
||||
* @param max
|
||||
*/
|
||||
public void setProgress(int messageId, int progress, int max) {
|
||||
setProgress(getString(messageId), progress, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates progress of dialog
|
||||
*
|
||||
* @param messageId
|
||||
* @param progress
|
||||
* @param max
|
||||
*/
|
||||
public void setProgress(int progress, int max) {
|
||||
ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
|
||||
dialog.setProgress(progress);
|
||||
dialog.setMax(max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates progress of dialog
|
||||
*
|
||||
* @param messageId
|
||||
* @param progress
|
||||
* @param max
|
||||
*/
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
|
||||
dialog.setMessage(message);
|
||||
dialog.setProgress(progress);
|
||||
dialog.setMax(max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Activity activity = getActivity();
|
||||
|
||||
ProgressDialog dialog = new ProgressDialog(activity);
|
||||
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
dialog.setCancelable(false);
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
|
||||
int messageId = getArguments().getInt(ARG_MESSAGE_ID);
|
||||
int style = getArguments().getInt(ARG_STYLE);
|
||||
|
||||
dialog.setMessage(getString(messageId));
|
||||
dialog.setProgressStyle(style);
|
||||
|
||||
// Disable the back button
|
||||
OnKeyListener keyListener = new OnKeyListener() {
|
||||
|
||||
@Override
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
dialog.setOnKeyListener(keyListener);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.dialog;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
|
||||
public class SetPassphraseDialogFragment extends DialogFragment implements OnEditorActionListener {
|
||||
private Messenger mMessenger;
|
||||
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_TITLE = "title";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
|
||||
public static final String MESSAGE_NEW_PASSPHRASE = "new_passphrase";
|
||||
|
||||
private EditText mPassphraseEditText;
|
||||
private EditText mPassphraseAgainEditText;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*
|
||||
* @param title
|
||||
* title of dialog
|
||||
* @param messenger
|
||||
* to communicate back after setting the passphrase
|
||||
* @return
|
||||
*/
|
||||
public static SetPassphraseDialogFragment newInstance(Messenger messenger, int title) {
|
||||
SetPassphraseDialogFragment frag = new SetPassphraseDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_TITLE, title);
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
int title = getArguments().getInt(ARG_TITLE);
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setTitle(title);
|
||||
alert.setMessage(R.string.enterPassPhraseTwice);
|
||||
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.passphrase_repeat, null);
|
||||
alert.setView(view);
|
||||
|
||||
mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
|
||||
mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
String passPhrase1 = mPassphraseEditText.getText().toString();
|
||||
String passPhrase2 = mPassphraseAgainEditText.getText().toString();
|
||||
if (!passPhrase1.equals(passPhrase2)) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
getString(R.string.errorMessage,
|
||||
getString(R.string.passPhrasesDoNotMatch)), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (passPhrase1.equals("")) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
getString(R.string.errorMessage,
|
||||
getString(R.string.passPhraseMustNotBeEmpty)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// return resulting data back to activity
|
||||
Bundle data = new Bundle();
|
||||
data.putString(MESSAGE_NEW_PASSPHRASE, passPhrase1);
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY, data);
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle arg0) {
|
||||
super.onActivityCreated(arg0);
|
||||
|
||||
// request focus and open soft keyboard
|
||||
mPassphraseEditText.requestFocus();
|
||||
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
|
||||
mPassphraseAgainEditText.setOnEditorActionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the "done" button on the soft keyboard with the okay button in the view
|
||||
*/
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (EditorInfo.IME_ACTION_DONE == actionId) {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
|
||||
bt.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
APG/src/org/thialfihar/android/apg/ui/widget/Editor.java
Normal file
25
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,530 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.thialfihar.android.apg.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnCreateContextMenuListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ExpandableListAdapter;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* @author Khoa Tran
|
||||
*
|
||||
* @see android.support.v4.app.ListFragment
|
||||
* @see android.app.ExpandableListActivity
|
||||
*
|
||||
* ExpandableListFragment for Android < 3.0
|
||||
*
|
||||
* from
|
||||
* http://stackoverflow.com/questions/6051050/expandablelistfragment-with-loadermanager-for-
|
||||
* compatibility-package
|
||||
*
|
||||
*/
|
||||
public class ExpandableListFragment extends Fragment implements OnCreateContextMenuListener,
|
||||
ExpandableListView.OnChildClickListener, ExpandableListView.OnGroupCollapseListener,
|
||||
ExpandableListView.OnGroupExpandListener {
|
||||
|
||||
static final int INTERNAL_EMPTY_ID = 0x00ff0001;
|
||||
static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002;
|
||||
static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003;
|
||||
|
||||
final private Handler mHandler = new Handler();
|
||||
|
||||
final private Runnable mRequestFocus = new Runnable() {
|
||||
public void run() {
|
||||
mExpandableList.focusableViewAvailable(mExpandableList);
|
||||
}
|
||||
};
|
||||
|
||||
final private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||||
onListItemClick((ExpandableListView) parent, v, position, id);
|
||||
}
|
||||
};
|
||||
|
||||
ExpandableListAdapter mAdapter;
|
||||
ExpandableListView mExpandableList;
|
||||
boolean mFinishedStart = false;
|
||||
View mEmptyView;
|
||||
TextView mStandardEmptyView;
|
||||
View mProgressContainer;
|
||||
View mExpandableListContainer;
|
||||
CharSequence mEmptyText;
|
||||
boolean mExpandableListShown;
|
||||
|
||||
public ExpandableListFragment() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide default implementation to return a simple list view. Subclasses can override to
|
||||
* replace with their own layout. If doing so, the returned view hierarchy <em>must</em> have a
|
||||
* ListView whose id is {@link android.R.id#list android.R.id.list} and can optionally have a
|
||||
* sibling view id {@link android.R.id#empty android.R.id.empty} that is to be shown when the
|
||||
* list is empty.
|
||||
*
|
||||
* <p>
|
||||
* If you are overriding this method with your own custom content, consider including the
|
||||
* standard layout {@link android.R.layout#list_content} in your layout file, so that you
|
||||
* continue to retain all of the standard behavior of ListFragment. In particular, this is
|
||||
* currently the only way to have the built-in indeterminant progress state be shown.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final Context context = getActivity();
|
||||
|
||||
FrameLayout root = new FrameLayout(context);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
LinearLayout pframe = new LinearLayout(context);
|
||||
pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID);
|
||||
pframe.setOrientation(LinearLayout.VERTICAL);
|
||||
pframe.setVisibility(View.GONE);
|
||||
pframe.setGravity(Gravity.CENTER);
|
||||
|
||||
ProgressBar progress = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge);
|
||||
pframe.addView(progress, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
root.addView(pframe, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
FrameLayout lframe = new FrameLayout(context);
|
||||
lframe.setId(INTERNAL_LIST_CONTAINER_ID);
|
||||
|
||||
TextView tv = new TextView(getActivity());
|
||||
tv.setId(INTERNAL_EMPTY_ID);
|
||||
tv.setGravity(Gravity.CENTER);
|
||||
lframe.addView(tv, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
ExpandableListView lv = new ExpandableListView(getActivity());
|
||||
lv.setId(android.R.id.list);
|
||||
lv.setDrawSelectorOnTop(false);
|
||||
lframe.addView(lv, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
root.addView(lframe, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
root.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach to list view once the view hierarchy has been created.
|
||||
*/
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
ensureList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach from list view.
|
||||
*/
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
mHandler.removeCallbacks(mRequestFocus);
|
||||
mExpandableList = null;
|
||||
mExpandableListShown = false;
|
||||
mEmptyView = mProgressContainer = mExpandableListContainer = null;
|
||||
mStandardEmptyView = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called when an item in the list is selected. Subclasses should override.
|
||||
* Subclasses can call getListView().getItemAtPosition(position) if they need to access the data
|
||||
* associated with the selected item.
|
||||
*
|
||||
* @param l
|
||||
* The ListView where the click happened
|
||||
* @param v
|
||||
* The view that was clicked within the ListView
|
||||
* @param position
|
||||
* The position of the view in the list
|
||||
* @param id
|
||||
* The row id of the item that was clicked
|
||||
*/
|
||||
public void onListItemClick(ExpandableListView l, View v, int position, long id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the cursor for the list view.
|
||||
*/
|
||||
public void setListAdapter(ExpandableListAdapter adapter) {
|
||||
boolean hadAdapter = mAdapter != null;
|
||||
mAdapter = adapter;
|
||||
if (mExpandableList != null) {
|
||||
mExpandableList.setAdapter(adapter);
|
||||
if (!mExpandableListShown && !hadAdapter) {
|
||||
// The list was hidden, and previously didn't have an
|
||||
// adapter. It is now time to show it.
|
||||
setListShown(true, getView().getWindowToken() != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the currently selected list item to the specified position with the adapter's data
|
||||
*
|
||||
* @param position
|
||||
*/
|
||||
public void setSelection(int position) {
|
||||
ensureList();
|
||||
mExpandableList.setSelection(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position of the currently selected list item.
|
||||
*/
|
||||
public int getSelectedItemPosition() {
|
||||
ensureList();
|
||||
return mExpandableList.getSelectedItemPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cursor row ID of the currently selected list item.
|
||||
*/
|
||||
public long getSelectedItemId() {
|
||||
ensureList();
|
||||
return mExpandableList.getSelectedItemId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activity's list view widget.
|
||||
*/
|
||||
public ExpandableListView getListView() {
|
||||
ensureList();
|
||||
return mExpandableList;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default content for a ListFragment has a TextView that can be shown when the list is
|
||||
* empty. If you would like to have it shown, call this method to supply the text it should use.
|
||||
*/
|
||||
public void setEmptyText(CharSequence text) {
|
||||
ensureList();
|
||||
if (mStandardEmptyView == null) {
|
||||
throw new IllegalStateException("Can't be used with a custom content view");
|
||||
}
|
||||
mStandardEmptyView.setText(text);
|
||||
if (mEmptyText == null) {
|
||||
mExpandableList.setEmptyView(mStandardEmptyView);
|
||||
}
|
||||
mEmptyText = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether the list is being displayed. You can make it not displayed if you are waiting
|
||||
* for the initial data to show in it. During this time an indeterminant progress indicator will
|
||||
* be shown instead.
|
||||
*
|
||||
* <p>
|
||||
* Applications do not normally need to use this themselves. The default behavior of
|
||||
* ListFragment is to start with the list not being shown, only showing it once an adapter is
|
||||
* given with {@link #setListAdapter(ListAdapter)}. If the list at that point had not been
|
||||
* shown, when it does get shown it will be do without the user ever seeing the hidden state.
|
||||
*
|
||||
* @param shown
|
||||
* If true, the list view is shown; if false, the progress indicator. The initial
|
||||
* value is true.
|
||||
*/
|
||||
public void setListShown(boolean shown) {
|
||||
setListShown(shown, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #setListShown(boolean)}, but no animation is used when transitioning from the
|
||||
* previous state.
|
||||
*/
|
||||
public void setListShownNoAnimation(boolean shown) {
|
||||
setListShown(shown, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether the list is being displayed. You can make it not displayed if you are waiting
|
||||
* for the initial data to show in it. During this time an indeterminant progress indicator will
|
||||
* be shown instead.
|
||||
*
|
||||
* @param shown
|
||||
* If true, the list view is shown; if false, the progress indicator. The initial
|
||||
* value is true.
|
||||
* @param animate
|
||||
* If true, an animation will be used to transition to the new state.
|
||||
*/
|
||||
private void setListShown(boolean shown, boolean animate) {
|
||||
ensureList();
|
||||
if (mProgressContainer == null) {
|
||||
throw new IllegalStateException("Can't be used with a custom content view");
|
||||
}
|
||||
if (mExpandableListShown == shown) {
|
||||
return;
|
||||
}
|
||||
mExpandableListShown = shown;
|
||||
if (shown) {
|
||||
if (animate) {
|
||||
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(),
|
||||
android.R.anim.fade_out));
|
||||
mExpandableListContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(),
|
||||
android.R.anim.fade_in));
|
||||
} else {
|
||||
mProgressContainer.clearAnimation();
|
||||
mExpandableListContainer.clearAnimation();
|
||||
}
|
||||
mProgressContainer.setVisibility(View.GONE);
|
||||
mExpandableListContainer.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
if (animate) {
|
||||
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(),
|
||||
android.R.anim.fade_in));
|
||||
mExpandableListContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(),
|
||||
android.R.anim.fade_out));
|
||||
} else {
|
||||
mProgressContainer.clearAnimation();
|
||||
mExpandableListContainer.clearAnimation();
|
||||
}
|
||||
mProgressContainer.setVisibility(View.VISIBLE);
|
||||
mExpandableListContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ListAdapter associated with this activity's ListView.
|
||||
*/
|
||||
public ExpandableListAdapter getListAdapter() {
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
private void ensureList() {
|
||||
if (mExpandableList != null) {
|
||||
return;
|
||||
}
|
||||
View root = getView();
|
||||
if (root == null) {
|
||||
throw new IllegalStateException("Content view not yet created");
|
||||
}
|
||||
if (root instanceof ExpandableListView) {
|
||||
mExpandableList = (ExpandableListView) root;
|
||||
} else {
|
||||
mStandardEmptyView = (TextView) root.findViewById(INTERNAL_EMPTY_ID);
|
||||
if (mStandardEmptyView == null) {
|
||||
mEmptyView = root.findViewById(android.R.id.empty);
|
||||
} else {
|
||||
mStandardEmptyView.setVisibility(View.GONE);
|
||||
}
|
||||
mProgressContainer = root.findViewById(INTERNAL_PROGRESS_CONTAINER_ID);
|
||||
mExpandableListContainer = root.findViewById(INTERNAL_LIST_CONTAINER_ID);
|
||||
View rawExpandableListView = root.findViewById(android.R.id.list);
|
||||
if (!(rawExpandableListView instanceof ExpandableListView)) {
|
||||
if (rawExpandableListView == null) {
|
||||
throw new RuntimeException(
|
||||
"Your content must have a ListView whose id attribute is "
|
||||
+ "'android.R.id.list'");
|
||||
}
|
||||
throw new RuntimeException(
|
||||
"Content has view with id attribute 'android.R.id.list' "
|
||||
+ "that is not a ListView class");
|
||||
}
|
||||
mExpandableList = (ExpandableListView) rawExpandableListView;
|
||||
if (mEmptyView != null) {
|
||||
mExpandableList.setEmptyView(mEmptyView);
|
||||
} else if (mEmptyText != null) {
|
||||
mStandardEmptyView.setText(mEmptyText);
|
||||
mExpandableList.setEmptyView(mStandardEmptyView);
|
||||
}
|
||||
}
|
||||
mExpandableListShown = true;
|
||||
mExpandableList.setOnItemClickListener(mOnClickListener);
|
||||
if (mAdapter != null) {
|
||||
ExpandableListAdapter adapter = mAdapter;
|
||||
mAdapter = null;
|
||||
setListAdapter(adapter);
|
||||
} else {
|
||||
// We are starting without an adapter, so assume we won't
|
||||
// have our data right away and start with the progress indicator.
|
||||
if (mProgressContainer != null) {
|
||||
setListShown(false, false);
|
||||
}
|
||||
}
|
||||
mHandler.post(mRequestFocus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this to populate the context menu when an item is long pressed. menuInfo will
|
||||
* contain an {@link android.widget.ExpandableListView.ExpandableListContextMenuInfo} whose
|
||||
* packedPosition is a packed position that should be used with
|
||||
* {@link ExpandableListView#getPackedPositionType(long)} and the other similar methods.
|
||||
* <p>
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this for receiving callbacks when a child has been clicked.
|
||||
* <p>
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
|
||||
int childPosition, long id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this for receiving callbacks when a group has been collapsed.
|
||||
*/
|
||||
public void onGroupCollapse(int groupPosition) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this for receiving callbacks when a group has been expanded.
|
||||
*/
|
||||
public void onGroupExpand(int groupPosition) {
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Ensures the expandable list view has been created before Activity restores all
|
||||
// * of the view states.
|
||||
// *
|
||||
// *@see Activity#onRestoreInstanceState(Bundle)
|
||||
// */
|
||||
// @Override
|
||||
// protected void onRestoreInstanceState(Bundle state) {
|
||||
// ensureList();
|
||||
// super.onRestoreInstanceState(state);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Updates the screen state (current list and other views) when the content changes.
|
||||
*
|
||||
* @see Activity#onContentChanged()
|
||||
*/
|
||||
|
||||
public void onContentChanged() {
|
||||
// super.onContentChanged();
|
||||
View emptyView = getView().findViewById(android.R.id.empty);
|
||||
mExpandableList = (ExpandableListView) getView().findViewById(android.R.id.list);
|
||||
if (mExpandableList == null) {
|
||||
throw new RuntimeException(
|
||||
"Your content must have a ExpandableListView whose id attribute is "
|
||||
+ "'android.R.id.list'");
|
||||
}
|
||||
if (emptyView != null) {
|
||||
mExpandableList.setEmptyView(emptyView);
|
||||
}
|
||||
mExpandableList.setOnChildClickListener(this);
|
||||
mExpandableList.setOnGroupExpandListener(this);
|
||||
mExpandableList.setOnGroupCollapseListener(this);
|
||||
|
||||
if (mFinishedStart) {
|
||||
setListAdapter(mAdapter);
|
||||
}
|
||||
mFinishedStart = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activity's expandable list view widget. This can be used to get the selection, set
|
||||
* the selection, and many other useful functions.
|
||||
*
|
||||
* @see ExpandableListView
|
||||
*/
|
||||
public ExpandableListView getExpandableListView() {
|
||||
ensureList();
|
||||
return mExpandableList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ExpandableListAdapter associated with this activity's ExpandableListView.
|
||||
*/
|
||||
public ExpandableListAdapter getExpandableListAdapter() {
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the currently selected group or child.
|
||||
*
|
||||
* @return The ID of the currently selected group or child.
|
||||
*/
|
||||
public long getSelectedId() {
|
||||
return mExpandableList.getSelectedId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position (in packed position representation) of the currently selected group or
|
||||
* child. Use {@link ExpandableListView#getPackedPositionType},
|
||||
* {@link ExpandableListView#getPackedPositionGroup}, and
|
||||
* {@link ExpandableListView#getPackedPositionChild} to unpack the returned packed position.
|
||||
*
|
||||
* @return A packed position representation containing the currently selected group or child's
|
||||
* position and type.
|
||||
*/
|
||||
public long getSelectedPosition() {
|
||||
return mExpandableList.getSelectedPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection to the specified child. If the child is in a collapsed group, the group
|
||||
* will only be expanded and child subsequently selected if shouldExpandGroup is set to true,
|
||||
* otherwise the method will return false.
|
||||
*
|
||||
* @param groupPosition
|
||||
* The position of the group that contains the child.
|
||||
* @param childPosition
|
||||
* The position of the child within the group.
|
||||
* @param shouldExpandGroup
|
||||
* Whether the child's group should be expanded if it is collapsed.
|
||||
* @return Whether the selection was successfully set on the child.
|
||||
*/
|
||||
public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) {
|
||||
return mExpandableList.setSelectedChild(groupPosition, childPosition, shouldExpandGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection to the specified group.
|
||||
*
|
||||
* @param groupPosition
|
||||
* The position of the group that should be selected.
|
||||
*/
|
||||
public void setSelectedGroup(int groupPosition) {
|
||||
mExpandableList.setSelectedGroup(groupPosition);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
236
APG/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java
Normal file
236
APG/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* 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.Id;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
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(PGPHelper.getAlgorithmInfo(key));
|
||||
String keyId1Str = PGPHelper.getSmallFingerPrint(key.getKeyID());
|
||||
String keyId2Str = PGPHelper.getSmallFingerPrint(key.getKeyID() >> 32);
|
||||
mKeyId.setText(keyId1Str + " " + keyId2Str);
|
||||
|
||||
Vector<Choice> choices = new Vector<Choice>();
|
||||
boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
|
||||
boolean isDSAKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.DSA);
|
||||
if (!isElGamalKey) {
|
||||
choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString(
|
||||
R.string.choice_signOnly)));
|
||||
}
|
||||
if (!mIsMasterKey && !isDSAKey) {
|
||||
choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString(
|
||||
R.string.choice_encryptOnly)));
|
||||
}
|
||||
if (!isElGamalKey && !isDSAKey) {
|
||||
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 (PGPHelper.isEncryptionKey(key)) {
|
||||
if (PGPHelper.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(PGPHelper.getCreationDate(key));
|
||||
mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime()));
|
||||
cal = new GregorianCalendar();
|
||||
Date date = PGPHelper.getExpiryDate(key);
|
||||
if (date == null) {
|
||||
setExpiryDate(null);
|
||||
} else {
|
||||
cal.setTime(PGPHelper.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();
|
||||
}
|
||||
|
||||
}
|
||||
264
APG/src/org/thialfihar/android/apg/ui/widget/KeyListAdapter.java
Normal file
264
APG/src/org/thialfihar/android/apg/ui/widget/KeyListAdapter.java
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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.widget;
|
||||
|
||||
import org.thialfihar.android.apg.Constants;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.OtherHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.Keys;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIds;
|
||||
import org.thialfihar.android.apg.util.Log;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.MergeCursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorTreeAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class KeyListAdapter extends CursorTreeAdapter {
|
||||
private Context mContext;
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
protected int mKeyType;
|
||||
|
||||
private static final int CHILD_KEY = 0;
|
||||
private static final int CHILD_USER_ID = 1;
|
||||
private static final int CHILD_FINGERPRINT = 2;
|
||||
|
||||
public KeyListAdapter(Context context, Cursor groupCursor, int keyType) {
|
||||
super(groupCursor, context);
|
||||
mContext = context;
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mKeyType = keyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate new view for group items
|
||||
*/
|
||||
@Override
|
||||
public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.key_list_group_item, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds TextViews from group view to results from database group cursor.
|
||||
*/
|
||||
@Override
|
||||
protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
|
||||
int userIdIndex = cursor.getColumnIndex(UserIds.USER_ID);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
|
||||
String userId = cursor.getString(userIdIndex);
|
||||
if (userId != null) {
|
||||
String[] userIdSplit = OtherHelper.splitUserId(userId);
|
||||
|
||||
if (userIdSplit[1] != null) {
|
||||
mainUserIdRest.setText(userIdSplit[1]);
|
||||
}
|
||||
mainUserId.setText(userIdSplit[0]);
|
||||
}
|
||||
|
||||
if (mainUserId.getText().length() == 0) {
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
}
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate new view for child items
|
||||
*/
|
||||
@Override
|
||||
public View newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.key_list_child_item, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind TextViews from view of childs based on query results
|
||||
*/
|
||||
@Override
|
||||
protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
|
||||
LinearLayout keyLayout = (LinearLayout) view.findViewById(R.id.keyLayout);
|
||||
LinearLayout userIdLayout = (LinearLayout) view.findViewById(R.id.userIdLayout);
|
||||
|
||||
// first entry is fingerprint
|
||||
if (cursor.getPosition() == 0) {
|
||||
// show only userId layout
|
||||
keyLayout.setVisibility(View.GONE);
|
||||
userIdLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
String fingerprint = PGPHelper.getFingerPrint(context,
|
||||
cursor.getLong(cursor.getColumnIndex(Keys.KEY_ID)));
|
||||
fingerprint = fingerprint.replace(" ", "\n");
|
||||
|
||||
TextView userId = (TextView) view.findViewById(R.id.userId);
|
||||
if (userId == null) {
|
||||
Log.d(Constants.TAG, "userId is null!");
|
||||
}
|
||||
userId.setText(context.getString(R.string.fingerprint) + ":\n" + fingerprint);
|
||||
} else {
|
||||
// differentiate between keys and userIds in MergeCursor
|
||||
if (cursor.getColumnIndex(Keys.KEY_ID) != -1) {
|
||||
keyLayout.setVisibility(View.VISIBLE);
|
||||
userIdLayout.setVisibility(View.GONE);
|
||||
|
||||
String keyIdStr = PGPHelper.getSmallFingerPrint(cursor.getLong(cursor
|
||||
.getColumnIndex(Keys.KEY_ID)));
|
||||
String algorithmStr = PGPHelper.getAlgorithmInfo(
|
||||
cursor.getInt(cursor.getColumnIndex(Keys.ALGORITHM)),
|
||||
cursor.getInt(cursor.getColumnIndex(Keys.KEY_SIZE)));
|
||||
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(keyIdStr);
|
||||
|
||||
TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
||||
keyDetails.setText("(" + algorithmStr + ")");
|
||||
|
||||
ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
|
||||
if (cursor.getInt(cursor.getColumnIndex(Keys.IS_MASTER_KEY)) != 1) {
|
||||
masterKeyIcon.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
masterKeyIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
||||
if (cursor.getInt(cursor.getColumnIndex(Keys.CAN_ENCRYPT)) != 1) {
|
||||
encryptIcon.setVisibility(View.GONE);
|
||||
} else {
|
||||
encryptIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
||||
if (cursor.getInt(cursor.getColumnIndex(Keys.CAN_SIGN)) != 1) {
|
||||
signIcon.setVisibility(View.GONE);
|
||||
} else {
|
||||
signIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
keyLayout.setVisibility(View.GONE);
|
||||
userIdLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
String userIdStr = cursor.getString(cursor.getColumnIndex(UserIds.USER_ID));
|
||||
|
||||
TextView userId = (TextView) view.findViewById(R.id.userId);
|
||||
userId.setText(userIdStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the group cursor, we start cursors for a fingerprint, keys, and userIds, which are
|
||||
* merged together and build the child cursor
|
||||
*/
|
||||
@Override
|
||||
protected Cursor getChildrenCursor(Cursor groupCursor) {
|
||||
final long keyRingRowId = groupCursor.getLong(groupCursor.getColumnIndex(BaseColumns._ID));
|
||||
|
||||
Cursor fingerprintCursor = getChildCursor(keyRingRowId, CHILD_FINGERPRINT);
|
||||
Cursor keyCursor = getChildCursor(keyRingRowId, CHILD_KEY);
|
||||
Cursor userIdCursor = getChildCursor(keyRingRowId, CHILD_USER_ID);
|
||||
|
||||
MergeCursor mergeCursor = new MergeCursor(new Cursor[] { fingerprintCursor, keyCursor,
|
||||
userIdCursor });
|
||||
Log.d(Constants.TAG, "mergeCursor:" + DatabaseUtils.dumpCursorToString(mergeCursor));
|
||||
|
||||
return mergeCursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* This builds a cursor for a specific type of children
|
||||
*
|
||||
* @param keyRingRowId
|
||||
* foreign row id of the keyRing
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
private Cursor getChildCursor(long keyRingRowId, int type) {
|
||||
Uri uri = null;
|
||||
String[] projection = null;
|
||||
String sortOrder = null;
|
||||
String selection = null;
|
||||
|
||||
switch (type) {
|
||||
case CHILD_FINGERPRINT:
|
||||
projection = new String[] { Keys._ID, Keys.KEY_ID, Keys.IS_MASTER_KEY, Keys.ALGORITHM,
|
||||
Keys.KEY_SIZE, Keys.CAN_SIGN, Keys.CAN_ENCRYPT, };
|
||||
sortOrder = Keys.RANK + " ASC";
|
||||
|
||||
// use only master key for fingerprint
|
||||
selection = Keys.IS_MASTER_KEY + " = 1 ";
|
||||
|
||||
if (mKeyType == Id.type.public_key) {
|
||||
uri = Keys.buildPublicKeysUri(String.valueOf(keyRingRowId));
|
||||
} else {
|
||||
uri = Keys.buildSecretKeysUri(String.valueOf(keyRingRowId));
|
||||
}
|
||||
break;
|
||||
|
||||
case CHILD_KEY:
|
||||
projection = new String[] { Keys._ID, Keys.KEY_ID, Keys.IS_MASTER_KEY, Keys.ALGORITHM,
|
||||
Keys.KEY_SIZE, Keys.CAN_SIGN, Keys.CAN_ENCRYPT, };
|
||||
sortOrder = Keys.RANK + " ASC";
|
||||
|
||||
if (mKeyType == Id.type.public_key) {
|
||||
uri = Keys.buildPublicKeysUri(String.valueOf(keyRingRowId));
|
||||
} else {
|
||||
uri = Keys.buildSecretKeysUri(String.valueOf(keyRingRowId));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CHILD_USER_ID:
|
||||
projection = new String[] { UserIds._ID, UserIds.USER_ID, UserIds.RANK, };
|
||||
sortOrder = UserIds.RANK + " ASC";
|
||||
|
||||
// not the main user id
|
||||
selection = UserIds.RANK + " > 0 ";
|
||||
|
||||
if (mKeyType == Id.type.public_key) {
|
||||
uri = UserIds.buildPublicUserIdsUri(String.valueOf(keyRingRowId));
|
||||
} else {
|
||||
uri = UserIds.buildSecretUserIdsUri(String.valueOf(keyRingRowId));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return mContext.getContentResolver().query(uri, projection, selection, null, sortOrder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
327
APG/src/org/thialfihar/android/apg/ui/widget/SectionView.java
Normal file
327
APG/src/org/thialfihar/android/apg/ui/widget/SectionView.java
Normal file
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* 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.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.helper.PGPConversionHelper;
|
||||
import org.thialfihar.android.apg.service.ApgIntentServiceHandler;
|
||||
import org.thialfihar.android.apg.service.ApgIntentService;
|
||||
import org.thialfihar.android.apg.service.PassphraseCacheService;
|
||||
import org.thialfihar.android.apg.ui.dialog.ProgressDialogFragment;
|
||||
import org.thialfihar.android.apg.ui.widget.Editor.EditorListener;
|
||||
import org.thialfihar.android.apg.util.Choice;
|
||||
import org.thialfihar.android.apg.R;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
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.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Vector;
|
||||
|
||||
public class SectionView extends LinearLayout implements OnClickListener, EditorListener {
|
||||
private LayoutInflater mInflater;
|
||||
private View mAdd;
|
||||
private ViewGroup mEditors;
|
||||
private TextView mTitle;
|
||||
private int mType = 0;
|
||||
|
||||
private Choice mNewKeyAlgorithmChoice;
|
||||
private int mNewKeySize;
|
||||
|
||||
private SherlockFragmentActivity mActivity;
|
||||
|
||||
private ProgressDialogFragment mGeneratingDialog;
|
||||
|
||||
public SectionView(Context context) {
|
||||
super(context);
|
||||
mActivity = (SherlockFragmentActivity) context;
|
||||
}
|
||||
|
||||
public SectionView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mActivity = (SherlockFragmentActivity) context;
|
||||
}
|
||||
|
||||
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() {
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(mActivity, ApgIntentService.class);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_ACTION, ApgIntentService.ACTION_GENERATE_KEY);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
String passPhrase;
|
||||
if (mEditors.getChildCount() > 0) {
|
||||
PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
|
||||
passPhrase = PassphraseCacheService
|
||||
.getCachedPassphrase(mActivity, masterKey.getKeyID());
|
||||
|
||||
data.putByteArray(ApgIntentService.MASTER_KEY,
|
||||
PGPConversionHelper.PGPSecretKeyToBytes(masterKey));
|
||||
} else {
|
||||
passPhrase = "";
|
||||
}
|
||||
data.putString(ApgIntentService.SYMMETRIC_PASSPHRASE, passPhrase);
|
||||
data.putInt(ApgIntentService.ALGORITHM, mNewKeyAlgorithmChoice.getId());
|
||||
data.putInt(ApgIntentService.KEY_SIZE, mNewKeySize);
|
||||
|
||||
intent.putExtra(ApgIntentService.EXTRA_DATA, data);
|
||||
|
||||
// show progress dialog
|
||||
mGeneratingDialog = ProgressDialogFragment.newInstance(R.string.progress_generating,
|
||||
ProgressDialog.STYLE_SPINNER);
|
||||
|
||||
// Message is received after generating is done in ApgService
|
||||
ApgIntentServiceHandler saveHandler = new ApgIntentServiceHandler(mActivity, mGeneratingDialog) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == ApgIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get new key from data bundle returned from service
|
||||
Bundle data = message.getData();
|
||||
PGPSecretKeyRing newKeyRing = (PGPSecretKeyRing) PGPConversionHelper
|
||||
.BytesToPGPKeyRing(data.getByteArray(ApgIntentService.RESULT_NEW_KEY));
|
||||
|
||||
boolean isMasterKey = (mEditors.getChildCount() == 0);
|
||||
|
||||
// take only the key from this ring
|
||||
PGPSecretKey newKey = null;
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> it = newKeyRing.getSecretKeys();
|
||||
|
||||
if (isMasterKey) {
|
||||
newKey = it.next();
|
||||
} else {
|
||||
// first one is the master key
|
||||
it.next();
|
||||
newKey = it.next();
|
||||
}
|
||||
|
||||
// add view with new key
|
||||
KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
|
||||
mEditors, false);
|
||||
view.setEditorListener(SectionView.this);
|
||||
view.setValue(newKey, isMasterKey, -1);
|
||||
mEditors.addView(view);
|
||||
SectionView.this.updateEditorsVisible();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(ApgIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
mGeneratingDialog.show(mActivity.getSupportFragmentManager(), "dialog");
|
||||
|
||||
// start service with intent
|
||||
mActivity.startService(intent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.widget;
|
||||
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
import org.thialfihar.android.apg.helper.OtherHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.ApgContract.UserIds;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectKeyCursorAdapter extends CursorAdapter {
|
||||
|
||||
protected int mKeyType;
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
private ListView mListView;
|
||||
|
||||
public final static String PROJECTION_ROW_AVAILABLE = "available";
|
||||
public final static String PROJECTION_ROW_VALID = "valid";
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public SelectKeyCursorAdapter(Context context, ListView listView, Cursor c, int keyType) {
|
||||
super(context, c);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mListView = listView;
|
||||
mKeyType = keyType;
|
||||
}
|
||||
|
||||
public String getUserId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getString(mCursor.getColumnIndex(UserIds.USER_ID));
|
||||
}
|
||||
|
||||
public long getMasterKeyId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(mCursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
boolean valid = cursor.getInt(cursor
|
||||
.getColumnIndex(PROJECTION_ROW_VALID)) > 0;
|
||||
|
||||
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 = cursor.getString(cursor.getColumnIndex(UserIds.USER_ID));
|
||||
if (userId != null) {
|
||||
String[] userIdSplit = OtherHelper.splitUserId(userId);
|
||||
|
||||
if (userIdSplit[1] != null) {
|
||||
mainUserIdRest.setText(userIdSplit[1]);
|
||||
}
|
||||
mainUserId.setText(userIdSplit[0]);
|
||||
}
|
||||
|
||||
long masterKeyId = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
keyId.setText(PGPHelper.getSmallFingerPrint(masterKeyId));
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
if (mKeyType == Id.type.public_key) {
|
||||
status.setText(R.string.canEncrypt);
|
||||
} else {
|
||||
status.setText(R.string.canSign);
|
||||
}
|
||||
} else {
|
||||
if (cursor.getInt(cursor
|
||||
.getColumnIndex(PROJECTION_ROW_AVAILABLE)) > 0) {
|
||||
// has some CAN_ENCRYPT keys, but col(ROW_VALID) = 0, so must be revoked or
|
||||
// expired
|
||||
status.setText(R.string.expired);
|
||||
} else {
|
||||
status.setText(R.string.noKey);
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox selected = (CheckBox) view.findViewById(R.id.selected);
|
||||
if (mKeyType == Id.type.public_key) {
|
||||
selected.setVisibility(View.VISIBLE);
|
||||
|
||||
if (!valid) {
|
||||
mListView.setItemChecked(cursor.getPosition(), false);
|
||||
}
|
||||
|
||||
selected.setChecked(mListView.isItemChecked(cursor.getPosition()));
|
||||
selected.setEnabled(valid);
|
||||
} else {
|
||||
selected.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
status.setText(status.getText() + " ");
|
||||
|
||||
view.setEnabled(valid);
|
||||
mainUserId.setEnabled(valid);
|
||||
mainUserIdRest.setEnabled(valid);
|
||||
keyId.setEnabled(valid);
|
||||
status.setEnabled(valid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.select_key_item, null);
|
||||
}
|
||||
|
||||
}
|
||||
196
APG/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java
Normal file
196
APG/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java
Normal file
@@ -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.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
|
||||
// android.util.Patterns.EMAIL_ADDRESS is only available as of Android 2.2+
|
||||
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;
|
||||
}
|
||||
}
|
||||
45
APG/src/org/thialfihar/android/apg/util/Choice.java
Normal file
45
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;
|
||||
}
|
||||
}
|
||||
94
APG/src/org/thialfihar/android/apg/util/Compatibility.java
Normal file
94
APG/src/org/thialfihar/android/apg/util/Compatibility.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 org.thialfihar.android.apg.util.Log;
|
||||
|
||||
public class Compatibility {
|
||||
|
||||
private static final String clipboardLabel = "APG";
|
||||
|
||||
/**
|
||||
* Wrapper around ClipboardManager based on Android version using Reflection API
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
257
APG/src/org/thialfihar/android/apg/util/HkpKeyServer.java
Normal file
257
APG/src/org/thialfihar/android/apg/util/HkpKeyServer.java
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
* 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.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.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 org.thialfihar.android.apg.helper.PGPHelper;
|
||||
import org.thialfihar.android.apg.helper.PGPMain;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<KeyInfo> search(String query) throws QueryException, TooManyResponses,
|
||||
InsufficientQuery {
|
||||
ArrayList<KeyInfo> results = new ArrayList<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 = PGPHelper.keyFromHex(matcher.group(3));
|
||||
info.fingerPrint = PGPHelper.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 ArrayList<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" + PGPHelper.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 = PGPMain.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
|
||||
public 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
40
APG/src/org/thialfihar/android/apg/util/InputData.java
Normal file
40
APG/src/org/thialfihar/android/apg/util/InputData.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
97
APG/src/org/thialfihar/android/apg/util/KeyServer.java
Normal file
97
APG/src/org/thialfihar/android/apg/util/KeyServer.java
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
* 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.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
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, Parcelable {
|
||||
private static final long serialVersionUID = -7797972113284992662L;
|
||||
public ArrayList<String> userIds;
|
||||
public String revoked;
|
||||
public Date date;
|
||||
public String fingerPrint;
|
||||
public long keyId;
|
||||
public int size;
|
||||
public String algorithm;
|
||||
|
||||
public KeyInfo() {
|
||||
userIds = new ArrayList<String>();
|
||||
}
|
||||
|
||||
public KeyInfo(Parcel in) {
|
||||
this();
|
||||
|
||||
in.readStringList(this.userIds);
|
||||
this.revoked = in.readString();
|
||||
this.date = (Date) in.readSerializable();
|
||||
this.fingerPrint = in.readString();
|
||||
this.keyId = in.readLong();
|
||||
this.size = in.readInt();
|
||||
this.algorithm = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeStringList(userIds);
|
||||
dest.writeString(revoked);
|
||||
dest.writeSerializable(date);
|
||||
dest.writeString(fingerPrint);
|
||||
dest.writeLong(keyId);
|
||||
dest.writeInt(size);
|
||||
dest.writeString(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;
|
||||
}
|
||||
83
APG/src/org/thialfihar/android/apg/util/Log.java
Normal file
83
APG/src/org/thialfihar/android/apg/util/Log.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 org.thialfihar.android.apg.Constants;
|
||||
|
||||
/**
|
||||
* Wraps Android Logging to enable or disable debug output using Constants
|
||||
*
|
||||
*/
|
||||
public final class Log {
|
||||
|
||||
public static void v(String tag, String msg) {
|
||||
if (Constants.DEBUG) {
|
||||
android.util.Log.v(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void v(String tag, String msg, Throwable tr) {
|
||||
if (Constants.DEBUG) {
|
||||
android.util.Log.v(tag, msg, tr);
|
||||
}
|
||||
}
|
||||
|
||||
public static void d(String tag, String msg) {
|
||||
if (Constants.DEBUG) {
|
||||
android.util.Log.d(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void d(String tag, String msg, Throwable tr) {
|
||||
if (Constants.DEBUG) {
|
||||
android.util.Log.d(tag, msg, tr);
|
||||
}
|
||||
}
|
||||
|
||||
public static void i(String tag, String msg) {
|
||||
if (Constants.DEBUG) {
|
||||
android.util.Log.i(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void i(String tag, String msg, Throwable tr) {
|
||||
if (Constants.DEBUG) {
|
||||
android.util.Log.i(tag, msg, tr);
|
||||
}
|
||||
}
|
||||
|
||||
public static void w(String tag, String msg) {
|
||||
android.util.Log.w(tag, msg);
|
||||
}
|
||||
|
||||
public static void w(String tag, String msg, Throwable tr) {
|
||||
android.util.Log.w(tag, msg, tr);
|
||||
}
|
||||
|
||||
public static void w(String tag, Throwable tr) {
|
||||
android.util.Log.w(tag, tr);
|
||||
}
|
||||
|
||||
public static void e(String tag, String msg) {
|
||||
android.util.Log.e(tag, msg);
|
||||
}
|
||||
|
||||
public static void e(String tag, String msg, Throwable tr) {
|
||||
android.util.Log.e(tag, msg, tr);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.util;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
188
APG/src/org/thialfihar/android/apg/util/Primes.java
Normal file
188
APG/src/org/thialfihar/android/apg/util/Primes.java
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Primes for ElGamal
|
||||
*/
|
||||
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.util;
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user