From 65a993446c8905ca5e77fefe4b7f1c4d0aee74d8 Mon Sep 17 00:00:00 2001 From: Adithya Abraham Philip Date: Mon, 10 Aug 2015 06:17:29 +0530 Subject: reworked keyserversyncadapterservice flow --- .../keychain/KeychainApplication.java | 20 +- .../keychain/operations/ImportOperation.java | 10 +- .../service/KeyserverSyncAdapterService.java | 368 +++++++++++++++------ .../keychain/ui/KeyListFragment.java | 33 +- .../keychain/ui/MainActivity.java | 19 -- .../keychain/ui/OrbotRequiredDialogActivity.java | 60 +++- .../keychain/ui/base/BaseActivity.java | 2 + .../keychain/util/orbot/OrbotHelper.java | 36 +- 8 files changed, 385 insertions(+), 163 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index cd24394d7..1d6de260d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -28,6 +28,7 @@ import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.Bundle; import android.os.Environment; import android.provider.ContactsContract; import android.widget.Toast; @@ -44,6 +45,7 @@ import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; import java.security.Security; +import java.util.Arrays; import java.util.HashMap; @@ -136,17 +138,31 @@ public class KeychainApplication extends Application { } /** - * Add OpenKeychain account to Android to link contacts with keys + * Add OpenKeychain account to Android to link contacts with keys and keyserver sync */ public static void setupAccountAsNeeded(Context context) { try { AccountManager manager = AccountManager.get(context); Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE); - if (accounts == null || accounts.length == 0) { + + if (accounts.length == 0) { Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE); if (manager.addAccountExplicitly(account, null, null)) { + // for contact sync ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1); ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); + // for keyserver sync + ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1); + ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, + true); + // TODO: Increase periodic update time after testing + Log.e("PHILIP", "enabled periodic keyserversync"); + ContentResolver.addPeriodicSync( + new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), + Constants.PROVIDER_AUTHORITY, + new Bundle(), + 60 + ); } else { Log.e(Constants.TAG, "Adding account failed!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java index 9513f58ee..bde161db7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -503,7 +503,7 @@ public class ImportOperation extends BaseOperation { /** * Used to accumulate the results of individual key imports */ - private class KeyImportAccumulator { + public static class KeyImportAccumulator { private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog(); Progressable mProgressable; private int mTotalKeys; @@ -531,14 +531,6 @@ public class ImportOperation extends BaseOperation { } } - public int getTotalKeys() { - return mTotalKeys; - } - - public int getImportedKeys() { - return mImportedKeys; - } - public synchronized void accumulateKeyImport(ImportKeyResult result) { mImportedKeys++; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java index a08797e6d..11d543f49 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java @@ -1,12 +1,14 @@ package org.sufficientlysecure.keychain.service; import android.accounts.Account; +import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SyncResult; @@ -18,6 +20,10 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.PowerManager; +import android.os.SystemClock; import android.support.v4.app.NotificationCompat; import org.sufficientlysecure.keychain.Constants; @@ -25,6 +31,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -33,62 +40,100 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.ArrayList; import java.util.GregorianCalendar; +import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class KeyserverSyncAdapterService extends Service { private static final String ACTION_IGNORE_TOR = "ignore_tor"; + private static final String ACTION_UPDATE_ALL = "update_all"; + private static final String ACTION_SYNC_NOW = "sync_now"; private static final String ACTION_DISMISS_NOTIFICATION = "cancel_sync"; private static final String ACTION_START_ORBOT = "start_orbot"; + private static final String ACTION_CANCEL = "cancel"; + + private AtomicBoolean mCancelled = new AtomicBoolean(false); @Override - public int onStartCommand(Intent intent, int flags, final int startId) { - Log.e("PHILIP", "Sync adapter service starting"); + public int onStartCommand(final Intent intent, int flags, final int startId) { + Log.e("PHILIP", "Sync adapter service starting" + intent.getAction()); + NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); switch (intent.getAction()) { + case ACTION_CANCEL: { + mCancelled.set(true); + break; + } + // the reason for the separation betweyeen SYNC_NOW and UPDATE_ALL is so that starting + // the sync directly from the notification is possible while the screen is on with + // UPDATE_ALL, but a postponed sync is only started if screen is off + case ACTION_SYNC_NOW: { + // this checks for screen on/off before sync, and postpones the sync if on + ContentResolver.requestSync( + new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), + Constants.PROVIDER_AUTHORITY, + new Bundle() + ); + break; + } + case ACTION_UPDATE_ALL: { + // does not check for screen on/off + asyncKeyUpdate(this, new CryptoInputParcel()); + break; + } case ACTION_IGNORE_TOR: { - updateKeysFromKeyserver(this, - new CryptoInputParcel(ParcelableProxy.getForNoProxy())); - manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); - stopSelf(startId); + asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy())); break; } case ACTION_START_ORBOT: { Intent startOrbot = new Intent(this, OrbotRequiredDialogActivity.class); startOrbot.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startOrbot.setAction(OrbotRequiredDialogActivity.ACTION_START_ORBOT); - startActivity(startOrbot); - new Handler().postDelayed( - new Runnable() { + startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true); + Messenger messenger = new Messenger( + new Handler() { @Override - public void run() { - updateKeysFromKeyserver(KeyserverSyncAdapterService.this, - new CryptoInputParcel()); - stopSelf(startId); + public void handleMessage(Message msg) { + switch (msg.what) { + case OrbotRequiredDialogActivity.MESSAGE_ORBOT_STARTED: { + Log.e("PHILIP", "orbot activity returned"); + asyncKeyUpdate(KeyserverSyncAdapterService.this, + new CryptoInputParcel()); + break; + } + case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE: { + asyncKeyUpdate(KeyserverSyncAdapterService.this, + new CryptoInputParcel( + ParcelableProxy.getForNoProxy())); + break; + } + case OrbotRequiredDialogActivity.MESSAGE_DIALOG_CANCEL: { + // just stop service + stopSelf(); + break; + } + } } - }, - 30000 // shouldn't take longer for orbot to start + } ); - - manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); + startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_MESSENGER, messenger); + startActivity(startOrbot); break; } case ACTION_DISMISS_NOTIFICATION: { - manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); + // notification is dismissed at the beginning stopSelf(startId); break; } } - // TODO: correct flag? - return START_REDELIVER_INTENT; + return START_NOT_STICKY; } - private static AtomicBoolean sCancelled = new AtomicBoolean(false); - private class KeyserverSyncAdapter extends AbstractThreadedSyncAdapter { public KeyserverSyncAdapter() { @@ -100,7 +145,19 @@ public class KeyserverSyncAdapterService extends Service { ContentProviderClient provider, SyncResult syncResult) { Log.d(Constants.TAG, "Performing a keyserver sync!"); - updateKeysFromKeyserver(KeyserverSyncAdapterService.this, new CryptoInputParcel()); + PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this + .getSystemService(Context.POWER_SERVICE); + @SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20 + boolean isScreenOn = pm.isScreenOn(); + + if (!isScreenOn) { + Intent serviceIntent = new Intent(KeyserverSyncAdapterService.this, + KeyserverSyncAdapterService.class); + serviceIntent.setAction(ACTION_UPDATE_ALL); + startService(serviceIntent); + } else { + postponeSync(); + } } } @@ -109,23 +166,178 @@ public class KeyserverSyncAdapterService extends Service { return new KeyserverSyncAdapter().getSyncAdapterBinder(); } - public static void updateKeysFromKeyserver(Context context, - CryptoInputParcel cryptoInputParcel) { - /** - * 1. Get keys which have been updated recently and therefore do not need to - * be updated now - * 2. Get list of all keys and filter out ones that don't need to be updated - * 3. Update the remaining keys. - * At any time, if the operation is to be cancelled, the sCancelled AtomicBoolean may be set - */ + private void handleUpdateResult(ImportKeyResult result) { + if (result.isPending()) { + Log.e(Constants.TAG, "Keyserver sync pending result: " + + result.getRequiredInputParcel().mType); + // result is pending due to Orbot not being started + // try to start it silently, if disabled show notificationaa + new OrbotHelper.SilentStartManager() { + @Override + protected void onOrbotStarted() { + // retry the update + asyncKeyUpdate(KeyserverSyncAdapterService.this, + new CryptoInputParcel()); + } + + @Override + protected void onSilentStartDisabled() { + // show notification + NotificationManager manager = + (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT, + getOrbotNoification(KeyserverSyncAdapterService.this)); + } + }.startOrbotAndListen(this, false); + } else if (isUpdateCancelled()) { + Log.d(Constants.TAG, "Keyserver sync cancelled"); + postponeSync(); + } else { + Log.d(Constants.TAG, "Keyserver sync completed: Updated: " + result.mUpdatedKeys + + " Failed: " + result.mBadKeys); + stopSelf(); + } + } + + private void postponeSync() { + AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + Intent serviceIntent = new Intent(this, KeyserverSyncAdapterService.class); + serviceIntent.setAction(ACTION_SYNC_NOW); + PendingIntent pi = PendingIntent.getService(this, 0, serviceIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.set( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + 30 * 1000, + pi + ); + } + + private void asyncKeyUpdate(final Context context, + final CryptoInputParcel cryptoInputParcel) { + Log.e("PHILIP", "async key update starting"); + new Thread(new Runnable() { + @Override + public void run() { + ImportKeyResult result = updateKeysFromKeyserver(context, cryptoInputParcel); + handleUpdateResult(result); + } + }).start(); + } + + private synchronized ImportKeyResult updateKeysFromKeyserver(final Context context, + final CryptoInputParcel cryptoInputParcel) { + mCancelled.set(false); + + ArrayList keyList = getKeysToUpdate(context); + + if (isUpdateCancelled()) { // if we've already been cancelled + return new ImportKeyResult(OperationResult.RESULT_CANCELLED, + new OperationResult.OperationLog()); + } + + if (cryptoInputParcel.getParcelableProxy() == null) { + // no explicit proxy, retrieve from preferences. Check if we should do a staggered sync + if (Preferences.getPreferences(context).getProxyPrefs().torEnabled) { + return staggeredUpdate(context, keyList, cryptoInputParcel); + } else { + return directUpdate(context, keyList, cryptoInputParcel); + } + } else { + return directUpdate(context, keyList, cryptoInputParcel); + } + } + + private ImportKeyResult directUpdate(Context context, ArrayList keyList, + CryptoInputParcel cryptoInputParcel) { + Log.d(Constants.TAG, "Starting normal update"); + ImportOperation importOp = new ImportOperation(context, new ProviderHelper(context), null); + return importOp.execute( + new ImportKeyringParcel(keyList, + Preferences.getPreferences(context).getPreferredKeyserver()), + cryptoInputParcel + ); + } + + + /** + * will perform a staggered update of user's keys using delays to ensure new Tor circuits, as + * performed by parcimonie. Relevant issue and method at: + * https://github.com/open-keychain/open-keychain/issues/1337 + * + * @return result of the sync + */ + private ImportKeyResult staggeredUpdate(Context context, ArrayList keyList, + CryptoInputParcel cryptoInputParcel) { + Log.d(Constants.TAG, "Starting staggered update"); + // assuming maxCircuitDirtiness is 10min in Tor + // final int MAX_CIRCUIT_DIRTINESS = (int) TimeUnit.MINUTES.toSeconds(10); + // TODO: PHILIP remove after testing + final int MAX_CIRCUIT_DIRTINESS = (int) TimeUnit.MINUTES.toSeconds(1); + // final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7); + final int WEEK_IN_SECONDS = 0; + ImportOperation.KeyImportAccumulator accumulator + = new ImportOperation.KeyImportAccumulator(keyList.size(), null); + for (ParcelableKeyRing keyRing : keyList) { + int waitTime; + int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size())); + if (staggeredTime >= MAX_CIRCUIT_DIRTINESS) { + waitTime = staggeredTime; + } else { + waitTime = MAX_CIRCUIT_DIRTINESS + new Random().nextInt(MAX_CIRCUIT_DIRTINESS); + } + Log.d(Constants.TAG, "Updating key with fingerprint " + keyRing.mExpectedFingerprint + + " with a wait time of " + waitTime + "s"); + try { + Thread.sleep(waitTime * 1000); + } catch (InterruptedException e) { + Log.e(Constants.TAG, "Exception during sleep between key updates", e); + // skip this one + continue; + } + ArrayList keyWrapper = new ArrayList<>(); + keyWrapper.add(keyRing); + if (isUpdateCancelled()) { + return new ImportKeyResult(ImportKeyResult.RESULT_CANCELLED, + new OperationResult.OperationLog()); + } + ImportKeyResult result = + new ImportOperation(context, new ProviderHelper(context), null, mCancelled) + .execute( + new ImportKeyringParcel( + keyWrapper, + Preferences.getPreferences(context) + .getPreferredKeyserver() + ), + cryptoInputParcel + ); + if (result.isPending()) { + return result; + } + accumulator.accumulateKeyImport(result); + } + return accumulator.getConsolidatedResult(); + } + + /** + * 1. Get keys which have been updated recently and therefore do not need to + * be updated now + * 2. Get list of all keys and filter out ones that don't need to be updated + * 3. Return keys to be updated + * + * @return list of keys that require update + */ + private ArrayList getKeysToUpdate(Context context) { // 1. Get keys which have been updated recently and don't need to updated now final int INDEX_UPDATED_KEYS_MASTER_KEY_ID = 0; final int INDEX_LAST_UPDATED = 1; - final long TIME_MAX = TimeUnit.DAYS.toSeconds(7); - final long CURRENT_TIME = GregorianCalendar.getInstance().get(GregorianCalendar.SECOND); - Log.e("PHILIP", "week: " + TIME_MAX); + // all time in seconds not milliseconds + // TODO: PHILIP correct TIME_MAX after testing + // final long TIME_MAX = TimeUnit.DAYS.toSeconds(7); + final long TIME_MAX = 1; + final long CURRENT_TIME = GregorianCalendar.getInstance().getTimeInMillis() / 1000; + Log.e("PHILIP", "week: " + TIME_MAX + " current: " + CURRENT_TIME); Cursor updatedKeysCursor = context.getContentResolver().query( KeychainContract.UpdatedKeys.CONTENT_URI, new String[]{ @@ -161,7 +373,7 @@ public class KeyserverSyncAdapterService extends Service { ); if (keyCursor == null) { - return; + return new ArrayList<>(); } ArrayList keyList = new ArrayList<>(); @@ -179,49 +391,39 @@ public class KeyserverSyncAdapterService extends Service { } keyCursor.close(); - if (sCancelled.get()) { - // if we've already been cancelled - return; - } - - // 3. Actually update the keys - Log.e("PHILIP", keyList.toString()); - ImportOperation importOp = new ImportOperation(context, new ProviderHelper(context), null); - ImportKeyResult result = importOp.execute( - new ImportKeyringParcel(keyList, - Preferences.getPreferences(context).getPreferredKeyserver()), - cryptoInputParcel - ); - if (result.isPending()) { - NotificationManager manager = - (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); - manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT, - getOrbotNoification(context)); + return keyList; + } - Log.d(Constants.TAG, "Keyserver sync failed due to pending result " + - result.getRequiredInputParcel().mType); - } else { - Log.d(Constants.TAG, "Background keyserver sync completed: new " + result.mNewKeys - + " updated " + result.mUpdatedKeys + " bad " + result.mBadKeys); - } + private boolean isUpdateCancelled() { + return mCancelled.get(); } - public static void preventAndCancelUpdates() { - // TODO: PHILIP uncomment! - // sCancelled.set(true); + /** + * will cancel an update already in progress + * + * @param context used to send an Intent to the service requesting cancellation. + */ + public static void cancelUpdates(Context context) { + Intent intent = new Intent(context, KeyserverSyncAdapterService.class); + intent.setAction(ACTION_CANCEL); + context.startService(intent); } - public static void allowUpdates() { - sCancelled.set(false); + // TODO: PHILIP remove! + @Override + public void onDestroy() { + Log.e("PHILIP", "onDestroy"); + super.onDestroy(); } - private static Notification getOrbotNoification(Context context) { + private Notification getOrbotNoification(Context context) { // TODO: PHILIP work in progress NotificationCompat.Builder builder = new NotificationCompat.Builder(context); builder.setSmallIcon(R.drawable.ic_stat_notify_24dp) .setLargeIcon(getBitmap(R.drawable.ic_launcher, context)) .setContentTitle(context.getString(R.string.keyserver_sync_orbot_notif_title)) - .setContentText(context.getString(R.string.keyserver_sync_orbot_notif_msg)); + .setContentText(context.getString(R.string.keyserver_sync_orbot_notif_msg)) + .setAutoCancel(true); // In case the user decides to not use tor Intent ignoreTorIntent = new Intent(context, KeyserverSyncAdapterService.class); @@ -237,21 +439,6 @@ public class KeyserverSyncAdapterService extends Service { context.getString(R.string.keyserver_sync_orbot_notif_ignore), ignoreTorPi); - // not enough space to show it - Intent dismissIntent = new Intent(context, KeyserverSyncAdapterService.class); - dismissIntent.setAction(ACTION_DISMISS_NOTIFICATION); - PendingIntent dismissPi = PendingIntent.getService( - context, - 0, // security not issue since we're giving this pending intent to Notification Manager - dismissIntent, - PendingIntent.FLAG_CANCEL_CURRENT - ); - - /*builder.addAction(R.drawable.abc_ic_clear_mtrl_alpha, - context.getString(android.R.string.cancel), - dismissPi - );*/ - Intent startOrbotIntent = new Intent(context, KeyserverSyncAdapterService.class); startOrbotIntent.setAction(ACTION_START_ORBOT); PendingIntent startOrbotPi = PendingIntent.getService( @@ -261,7 +448,7 @@ public class KeyserverSyncAdapterService extends Service { PendingIntent.FLAG_CANCEL_CURRENT ); - builder.addAction(R.drawable.abc_ic_clear_mtrl_alpha, + builder.addAction(R.drawable.abc_ic_commit_search_api_mtrl_alpha, context.getString(R.string.keyserver_sync_orbot_notif_start), startOrbotPi ); @@ -270,19 +457,8 @@ public class KeyserverSyncAdapterService extends Service { return builder.build(); } - /** - * will perform a staggered update of user's keys using delays to ensure new Tor circuits, as - * performed by parcimonie - * - * @return result of the sync - */ - private static ImportKeyResult staggeredSync() { - // TODO: WIP - return null; - } - // from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android - private static Bitmap getBitmap(int resId, Context context) { + private Bitmap getBitmap(int resId, Context context) { int mLargeIconWidth = (int) context.getResources().getDimension( android.R.dimen.notification_large_icon_width); int mLargeIconHeight = (int) context.getResources().getDimension( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 4997d3fab..9630463fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -18,13 +18,9 @@ package org.sufficientlysecure.keychain.ui; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; - import android.animation.ObjectAnimator; import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -64,13 +60,14 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; @@ -78,6 +75,10 @@ import org.sufficientlysecure.keychain.util.Preferences; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + /** * Public key list with sticky list headers. It does _not_ extend ListFragment because it uses * StickyListHeaders library which does not extend upon ListView. @@ -520,9 +521,7 @@ public class KeyListFragment extends LoaderFragment } private void updateAllKeys() { - // TODO: PHILIP just for testing, remove! - KeyserverSyncAdapterService.updateKeysFromKeyserver(getActivity(), new CryptoInputParcel()); - /*Activity activity = getActivity(); + Activity activity = getActivity(); if (activity == null) { return; } @@ -536,7 +535,7 @@ public class KeyListFragment extends LoaderFragment ); if (cursor == null) { - Notify.create(activity, R.string.error_loading_keys, Style.ERROR); + Notify.create(activity, R.string.error_loading_keys, Notify.Style.ERROR); return; } @@ -563,7 +562,7 @@ public class KeyListFragment extends LoaderFragment mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_updating); - mImportOpHelper.cryptoOperation();*/ + mImportOpHelper.cryptoOperation(); } private void consolidate() { @@ -647,18 +646,6 @@ public class KeyListFragment extends LoaderFragment } } - @Override - public void onResume() { - super.onResume(); - KeyserverSyncAdapterService.preventAndCancelUpdates(); - } - - @Override - public void onPause() { - super.onPause(); - KeyserverSyncAdapterService.allowUpdates(); - } - @Override public void fabMoveUp(int height) { ObjectAnimator anim = ObjectAnimator.ofFloat(mFab, "translationY", 0, -height); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index 0fa6566f8..6f5d98afd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -19,9 +19,6 @@ package org.sufficientlysecure.keychain.ui; -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.ContentResolver; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -40,14 +37,11 @@ import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.remote.ui.AppsListFragment; -import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.util.FabContainer; -import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; public class MainActivity extends BaseNfcActivity implements FabContainer, OnBackStackChangedListener { @@ -170,19 +164,6 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac } } - enablePeriodicKeyserverSync(); - - } - - private void enablePeriodicKeyserverSync() { - // TODO: Increase periodic update time after testing - Log.e("PHILIP", "enabled periodic keyserversybc"); - ContentResolver.addPeriodicSync( - new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), - Constants.PROVIDER_AUTHORITY, - new Bundle(), - 2*60 - ); } private void setFragment(Fragment fragment, boolean addToBackStack) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java index d1c247a76..d12b3fc22 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java @@ -18,12 +18,20 @@ package org.sufficientlysecure.keychain.ui; +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 android.os.RemoteException; import android.support.v4.app.FragmentActivity; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; @@ -34,8 +42,14 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; public class OrbotRequiredDialogActivity extends FragmentActivity implements OrbotHelper.DialogActions { + public static final int MESSAGE_ORBOT_STARTED = 1; + public static final int MESSAGE_ORBOT_IGNORE = 2; + public static final int MESSAGE_DIALOG_CANCEL = 3; + // if suppplied and true will start Orbot directly without showing dialog public static final String EXTRA_START_ORBOT = "start_orbot"; + // used for communicating results when triggered from a service + public static final String EXTRA_MESSENGER = "messenger"; // to provide any previous crypto input into which proxy preference is merged public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input"; @@ -43,6 +57,9 @@ public class OrbotRequiredDialogActivity extends FragmentActivity public static final String RESULT_CRYPTO_INPUT = "result_crypto_input"; private CryptoInputParcel mCryptoInputParcel; + private Messenger mMessenger; + + private ProgressDialog mShowOrbotProgressDialog; @Override protected void onCreate(Bundle savedInstanceState) { @@ -54,9 +71,15 @@ public class OrbotRequiredDialogActivity extends FragmentActivity mCryptoInputParcel = new CryptoInputParcel(); } + mMessenger = getIntent().getParcelableExtra(EXTRA_MESSENGER); + boolean startOrbotDirect = getIntent().getBooleanExtra(EXTRA_START_ORBOT, false); if (startOrbotDirect) { - OrbotHelper.bestPossibleOrbotStart(this, this); + mShowOrbotProgressDialog = new ProgressDialog(this); + mShowOrbotProgressDialog.setTitle(R.string.progress_starting_orbot); + mShowOrbotProgressDialog.setCancelable(false); + mShowOrbotProgressDialog.show(); + OrbotHelper.bestPossibleOrbotStart(this, this, false); } else { showDialog(); } @@ -84,13 +107,32 @@ public class OrbotRequiredDialogActivity extends FragmentActivity super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case OrbotHelper.START_TOR_RESULT: { - onOrbotStarted(); // assumption that orbot was started, no way to tell for sure + dismissOrbotProgressDialog(); + // unfortunately, this result is returned immediately and not when Orbot is started + // 10s is approximately the longest time Orbot has taken to start + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + onOrbotStarted(); // assumption that orbot was started + } + }, 10000); } } } + /** + * for when Orbot is started without showing the dialog by the EXTRA_START_ORBOT intent extra + */ + private void dismissOrbotProgressDialog() { + if (mShowOrbotProgressDialog != null) { + mShowOrbotProgressDialog.dismiss(); + } + } + @Override public void onOrbotStarted() { + dismissOrbotProgressDialog(); + sendMessage(MESSAGE_ORBOT_STARTED); Intent intent = new Intent(); // send back unmodified CryptoInputParcel for a retry intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); @@ -100,6 +142,7 @@ public class OrbotRequiredDialogActivity extends FragmentActivity @Override public void onNeutralButton() { + sendMessage(MESSAGE_ORBOT_IGNORE); Intent intent = new Intent(); mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy()); intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); @@ -109,6 +152,19 @@ public class OrbotRequiredDialogActivity extends FragmentActivity @Override public void onCancel() { + sendMessage(MESSAGE_DIALOG_CANCEL); finish(); } + + private void sendMessage(int what) { + if (mMessenger != null) { + Message msg = Message.obtain(); + msg.what = what; + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "Could not deliver message", e); + } + } + } } \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index fcf5dc11e..aa4e7d840 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -30,6 +30,7 @@ import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; /** @@ -51,6 +52,7 @@ public abstract class BaseActivity extends AppCompatActivity { @Override protected void onResume() { super.onResume(); + KeyserverSyncAdapterService.cancelUpdates(this); if (mThemeChanger.changeTheme()) { Intent intent = getIntent(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java index d5ee75f9b..6305d2033 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java @@ -361,7 +361,7 @@ public class OrbotHelper { .show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotStartDialog"); } - }.startOrbotAndListen(fragmentActivity); + }.startOrbotAndListen(fragmentActivity, true); return false; } else { @@ -385,7 +385,8 @@ public class OrbotHelper { * activities wishing to respond to a change in Orbot state. */ public static void bestPossibleOrbotStart(final DialogActions dialogActions, - final Activity activity) { + final Activity activity, + boolean showProgress) { new SilentStartManager() { @Override @@ -397,23 +398,23 @@ public class OrbotHelper { protected void onSilentStartDisabled() { requestShowOrbotStart(activity); } - }.startOrbotAndListen(activity); + }.startOrbotAndListen(activity, showProgress); } /** * base class for listening to silent orbot starts. Also handles display of progress dialog. */ - private static abstract class SilentStartManager { + public static abstract class SilentStartManager { private ProgressDialog mProgressDialog; - public void startOrbotAndListen(Context context) { - mProgressDialog = new ProgressDialog(ThemeChanger.getDialogThemeWrapper(context)); - mProgressDialog.setMessage(context.getString(R.string.progress_starting_orbot)); - mProgressDialog.setCancelable(false); - mProgressDialog.show(); + public void startOrbotAndListen(final Context context, final boolean showProgress) { + Log.e("PHILIP", "starting orbot listener"); + if (showProgress) { + showProgressDialog(context); + } - BroadcastReceiver receiver = new BroadcastReceiver() { + final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getStringExtra(OrbotHelper.EXTRA_STATUS)) { @@ -423,14 +424,18 @@ public class OrbotHelper { new Handler().postDelayed(new Runnable() { @Override public void run() { - mProgressDialog.dismiss(); + if (showProgress) { + mProgressDialog.dismiss(); + } onOrbotStarted(); } }, 1000); break; case OrbotHelper.STATUS_STARTS_DISABLED: context.unregisterReceiver(this); - mProgressDialog.dismiss(); + if (showProgress) { + mProgressDialog.dismiss(); + } onSilentStartDisabled(); break; @@ -444,6 +449,13 @@ public class OrbotHelper { requestStartTor(context); } + private void showProgressDialog(Context context) { + mProgressDialog = new ProgressDialog(ThemeChanger.getDialogThemeWrapper(context)); + mProgressDialog.setMessage(context.getString(R.string.progress_starting_orbot)); + mProgressDialog.setCancelable(false); + mProgressDialog.show(); + } + protected abstract void onOrbotStarted(); protected abstract void onSilentStartDisabled(); -- cgit v1.2.3