From c5bcbce28fa45d8d1e5714d111f3cc24a2c99d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 23 Nov 2015 09:06:12 +0100 Subject: Show notification when READ_CONTACTS permission is denied in sync service, hide linked contact card if permission is denied --- .../keychain/operations/CertifyOperation.java | 6 +-- .../keychain/operations/DeleteOperation.java | 2 +- .../keychain/operations/EditKeyOperation.java | 2 +- .../keychain/operations/ImportOperation.java | 6 +-- .../service/ContactSyncAdapterService.java | 57 +++++++++++++++++++--- .../keychain/ui/SettingsActivity.java | 12 ++--- .../keychain/ui/ViewKeyActivity.java | 4 +- .../keychain/ui/ViewKeyFragment.java | 31 ++++++++---- .../keychain/util/ContactHelper.java | 55 ++++++++++++++++++--- 9 files changed, 134 insertions(+), 41 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 7d11fa1f1..4ad75fde1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -18,14 +18,12 @@ package org.sufficientlysecure.keychain.operations; -import java.net.Proxy; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.support.annotation.NonNull; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -51,8 +49,6 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Passphrase; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; /** * An operation which implements a high level user id certification operation. @@ -256,7 +252,7 @@ public class CertifyOperation extends BaseOperation { } // since only verified keys are synced to contacts, we need to initiate a sync now - ContactSyncAdapterService.requestSync(); + ContactSyncAdapterService.requestContactsSync(); log.add(LogType.MSG_CRT_SUCCESS, 0); if (uploadError != 0) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java index 56bd3b786..8227fea02 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java @@ -102,7 +102,7 @@ public class DeleteOperation extends BaseOperation { int result = DeleteResult.RESULT_OK; if (success > 0) { // make sure new data is synced into contacts - ContactSyncAdapterService.requestSync(); + ContactSyncAdapterService.requestContactsSync(); log.add(LogType.MSG_DEL_OK, 0, success); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index 3b2c484be..51485a35d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -195,7 +195,7 @@ public class EditKeyOperation extends BaseOperation { updateProgress(R.string.progress_done, 100, 100); // make sure new data is synced into contacts - ContactSyncAdapterService.requestSync(); + ContactSyncAdapterService.requestContactsSync(); log.add(LogType.MSG_ED_SUCCESS, 0); return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId()); 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 19a05790f..70288123f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -124,7 +124,7 @@ public class ImportOperation extends BaseOperation { /** * Since the introduction of multithreaded import, we expect calling functions to handle the - * contact-to-key sync i.e ContactSyncAdapterService.requestSync() + * contact-to-key sync i.e ContactSyncAdapterService.requestContactsSync() * * @param entries keys to import * @param num number of keys to import @@ -325,7 +325,7 @@ public class ImportOperation extends BaseOperation { // Special: make sure new data is synced into contacts // disabling sync right now since it reduces speed while multi-threading // so, we expect calling functions to take care of it. KeychainService handles this - // ContactSyncAdapterService.requestSync(); + // ContactSyncAdapterService.requestContactsSync(); // convert to long array long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()]; @@ -405,7 +405,7 @@ public class ImportOperation extends BaseOperation { result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer, proxy); } - ContactSyncAdapterService.requestSync(); + ContactSyncAdapterService.requestContactsSync(); return result; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index b36d23775..64f06fd15 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Dominik Schürmann + * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,6 +18,9 @@ package org.sufficientlysecure.keychain.service; import android.accounts.Account; +import android.app.Activity; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.Service; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; @@ -26,14 +29,20 @@ import android.content.Intent; import android.content.SyncResult; import android.os.Bundle; import android.os.IBinder; +import android.preference.PreferenceActivity; import android.provider.ContactsContract; +import android.support.v4.app.NotificationCompat; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.SettingsActivity; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; public class ContactSyncAdapterService extends Service { + private static final int NOTIFICATION_ID_SYNC_SETTINGS = 13; + private class ContactSyncAdapter extends AbstractThreadedSyncAdapter { // private final AtomicBoolean importDone = new AtomicBoolean(false); @@ -46,7 +55,44 @@ public class ContactSyncAdapterService extends Service { public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, final SyncResult syncResult) { Log.d(Constants.TAG, "Performing a contact sync!"); - // TODO: Import is currently disabled for 2.8, until we implement proper origin management + + ContactHelper.writeKeysToContacts(ContactSyncAdapterService.this); + + importKeys(); + } + + @Override + public void onSecurityException(Account account, Bundle extras, String authority, SyncResult syncResult) { + super.onSecurityException(account, extras, authority, syncResult); + + // deactivate sync + ContentResolver.setSyncAutomatically(account, authority, false); + + // show notification linking to sync settings + Intent resultIntent = new Intent(ContactSyncAdapterService.this, SettingsActivity.class); + resultIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, + SettingsActivity.SyncPrefsFragment.class.getName()); + PendingIntent resultPendingIntent = + PendingIntent.getActivity( + ContactSyncAdapterService.this, + 0, + resultIntent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(ContactSyncAdapterService.this) + .setSmallIcon(R.drawable.ic_stat_notify_24dp) + .setContentTitle(getString(R.string.sync_notification_permission_required_title)) + .setContentText(getString(R.string.sync_notification_permission_required_text)) + .setContentIntent(resultPendingIntent); + NotificationManager mNotifyMgr = + (NotificationManager) ContactSyncAdapterService.this.getSystemService(Activity.NOTIFICATION_SERVICE); + mNotifyMgr.notify(NOTIFICATION_ID_SYNC_SETTINGS, mBuilder.build()); + } + } + + private static void importKeys() { + // TODO: Import is currently disabled, until we implement proper origin management // importDone.set(false); // KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this); // EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(), @@ -84,14 +130,13 @@ public class ContactSyncAdapterService extends Service { // return; // } // } - ContactHelper.writeKeysToContacts(ContactSyncAdapterService.this); - } } - public static void requestSync() { + public static void requestContactsSync() { Bundle extras = new Bundle(); - // no need to wait for internet connection! + // no need to wait, do it immediately extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); ContentResolver.requestSync( new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), ContactsContract.AUTHORITY, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index 17398911c..9e962fa0d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -133,7 +133,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { super.onCreate(savedInstanceState); // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.cloud_search_prefs); + addPreferencesFromResource(R.xml.cloud_search_preferences); mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS); mKeyServerPreference.setSummary(keyserverSummary(getActivity())); @@ -238,11 +238,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity { if (mFragment != null) { Preferences.setPreferenceManagerFileAndMode(mFragment.getPreferenceManager()); // Load the preferences from an XML resource - mFragment.addPreferencesFromResource(R.xml.proxy_prefs); + mFragment.addPreferencesFromResource(R.xml.proxy_preferences); } else { Preferences.setPreferenceManagerFileAndMode(mActivity.getPreferenceManager()); // Load the preferences from an XML resource - mActivity.addPreferencesFromResource(R.xml.proxy_prefs); + mActivity.addPreferencesFromResource(R.xml.proxy_preferences); } mUseTor = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY); @@ -509,9 +509,9 @@ public class SettingsActivity extends AppCompatPreferenceActivity { // permission granted -> enable contact linking AccountManager manager = AccountManager.get(getActivity()); final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0]; - SwitchPreference pref = (SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER); - ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true); - setSummary(pref, Constants.PROVIDER_AUTHORITY, true); + SwitchPreference pref = (SwitchPreference) findPreference(Constants.Pref.SYNC_CONTACTS); + ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); + setSummary(pref, ContactsContract.AUTHORITY, true); pref.setChecked(true); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index c333ee0ef..e1b796f38 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -855,8 +855,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements AsyncTask photoTask = new AsyncTask() { protected Bitmap doInBackground(Long... mMasterKeyId) { - return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), - mMasterKeyId[0], true); + return ContactHelper.loadPhotoByMasterKeyId(ViewKeyActivity.this, + getContentResolver(), mMasterKeyId[0], true); } protected void onPostExecute(Bitmap photo) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index 7be695de0..dda2a680a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -22,10 +22,12 @@ package org.sufficientlysecure.keychain.ui; import java.io.IOException; import java.util.List; +import android.Manifest; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; @@ -35,6 +37,7 @@ import android.os.Bundle; import android.os.Handler; import android.provider.ContactsContract; import android.support.v4.app.LoaderManager; +import android.support.v4.content.ContextCompat; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v7.widget.CardView; @@ -241,9 +244,9 @@ public class ViewKeyFragment extends LoaderFragment implements Bitmap picture; if (mIsSecret) { - picture = ContactHelper.loadMainProfilePhoto(resolver, false); + picture = ContactHelper.loadMainProfilePhoto(getActivity(), resolver, false); } else { - picture = ContactHelper.loadPhotoByContactId(resolver, contactId, false); + picture = ContactHelper.loadPhotoByContactId(getActivity(), resolver, contactId, false); } if (picture != null) mSystemContactPicture.setImageBitmap(picture); @@ -419,13 +422,7 @@ public class ViewKeyFragment extends LoaderFragment implements getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this); } - - Bundle linkedContactData = new Bundle(); - linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId); - linkedContactData.putBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET, mIsSecret); - - // initialises loader for contact query so we can listen to any updates - getLoaderManager().initLoader(LOADER_ID_LINKED_CONTACT, linkedContactData, this); + initLinkedContactLoader(masterKeyId, mIsSecret); break; } @@ -465,6 +462,22 @@ public class ViewKeyFragment extends LoaderFragment implements } } + private void initLinkedContactLoader(long masterKeyId, boolean isSecret) { + if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_CONTACTS) + == PackageManager.PERMISSION_DENIED) { + Log.w(Constants.TAG, "loading linked system contact not possible READ_CONTACTS permission denied!"); + hideLinkedSystemContact(); + return; + } + + Bundle linkedContactData = new Bundle(); + linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId); + linkedContactData.putBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET, isSecret); + + // initialises loader for contact query so we can listen to any updates + getLoaderManager().initLoader(LOADER_ID_LINKED_CONTACT, linkedContactData, this); + } + /** * 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. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index 77aa1a055..1abe56055 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -17,17 +17,21 @@ package org.sufficientlysecure.keychain.util; +import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; +import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import android.os.Build; import android.provider.ContactsContract; +import android.support.v4.content.ContextCompat; import android.util.Patterns; import org.sufficientlysecure.keychain.Constants; @@ -51,6 +55,11 @@ public class ContactHelper { private static final Map photoCache = new HashMap<>(); public static List getPossibleUserEmails(Context context) { + if (!isContactsPermissionGranted(context)) { + Log.w(Constants.TAG, "getting emails not possible READ_CONTACTS permission denied!"); + return new ArrayList<>(); + } + Set accountMails = getAccountEmails(context); accountMails.addAll(getMainProfileContactEmails(context)); @@ -69,6 +78,11 @@ public class ContactHelper { } public static List getPossibleUserNames(Context context) { + if (!isContactsPermissionGranted(context)) { + Log.w(Constants.TAG, "getting names not possible READ_CONTACTS permission denied!"); + return new ArrayList<>(); + } + Set accountMails = getAccountEmails(context); Set names = getContactNamesFromEmails(context, accountMails); names.addAll(getMainProfileContactName(context)); @@ -242,7 +256,7 @@ public class ContactHelper { * @param highRes true for large image if present, false for thumbnail * @return bitmap of loaded photo */ - public static Bitmap loadMainProfilePhoto(ContentResolver contentResolver, boolean highRes) { + public static Bitmap loadMainProfilePhoto(Context context, ContentResolver contentResolver, boolean highRes) { try { long mainProfileContactId = getMainProfileContactId(contentResolver); @@ -300,6 +314,7 @@ public class ContactHelper { } public static Uri dataUriFromContactUri(Context context, Uri contactUri) { + Cursor contactMasterKey = context.getContentResolver().query(contactUri, new String[]{ContactsContract.Data.DATA2}, null, null, null); if (contactMasterKey != null) { @@ -373,32 +388,43 @@ public class ContactHelper { return contactName; } - public static Bitmap getCachedPhotoByMasterKeyId(ContentResolver contentResolver, long masterKeyId) { + public static Bitmap getCachedPhotoByMasterKeyId(Context context, ContentResolver contentResolver, + long masterKeyId) { if (masterKeyId == -1) { return null; } if (!photoCache.containsKey(masterKeyId)) { - photoCache.put(masterKeyId, loadPhotoByMasterKeyId(contentResolver, masterKeyId, false)); + photoCache.put(masterKeyId, loadPhotoByMasterKeyId(context, contentResolver, masterKeyId, false)); } return photoCache.get(masterKeyId); } - public static Bitmap loadPhotoByMasterKeyId(ContentResolver contentResolver, long masterKeyId, - boolean highRes) { + public static Bitmap loadPhotoByMasterKeyId(Context context, ContentResolver contentResolver, + long masterKeyId, boolean highRes) { + if (!isContactsPermissionGranted(context)) { + Log.w(Constants.TAG, "loading photo not possible READ_CONTACTS permission denied!"); + return null; + } + if (masterKeyId == -1) { return null; } try { long contactId = findContactId(contentResolver, masterKeyId); - return loadPhotoByContactId(contentResolver, contactId, highRes); + return loadPhotoByContactId(context, contentResolver, contactId, highRes); } catch (Throwable ignored) { return null; } } - public static Bitmap loadPhotoByContactId(ContentResolver contentResolver, long contactId, - boolean highRes) { + public static Bitmap loadPhotoByContactId(Context context, ContentResolver contentResolver, + long contactId, boolean highRes) { + if (!isContactsPermissionGranted(context)) { + Log.w(Constants.TAG, "loading photo not possible READ_CONTACTS permission denied!"); + return null; + } + if (contactId == -1) { return null; } @@ -452,6 +478,19 @@ public class ContactHelper { writeKeysToNormalContacts(context, resolver); } + private static boolean isContactsPermissionGranted(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + return false; + } + private static void writeKeysToNormalContacts(Context context, ContentResolver resolver) { // delete raw contacts flagged for deletion by user so they can be reinserted deleteFlaggedNormalRawContacts(resolver); -- cgit v1.2.3