drop ContactSync feature

This commit is contained in:
Vincent Breitmoser
2020-09-05 14:32:59 +02:00
parent aa6ff03545
commit 2d9edf3832
39 changed files with 23 additions and 624 deletions

View File

@@ -30,6 +30,8 @@ import android.app.Application;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -37,7 +39,6 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
import org.sufficientlysecure.keychain.network.TlsCertificatePinning;
import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.util.PRNGFixes;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;
@@ -85,15 +86,9 @@ public class KeychainApplication extends Application {
}
*/
// Add OpenKeychain account to Android to link contacts with keys and keyserver sync
createAccountIfNecessary(this);
Preferences preferences = Preferences.getPreferences(this);
if (preferences.isAppExecutedFirstTime()) {
preferences.setAppExecutedFirstTime(false);
ContactSyncAdapterService.enableContactsSync(this);
preferences.setPrefVersionToCurrentVersion();
}
@@ -115,33 +110,6 @@ public class KeychainApplication extends Application {
TemporaryFileProvider.scheduleCleanupImmediately(getApplicationContext());
}
/**
* @return the OpenKeychain contact/keyserver sync account if it exists or was successfully
* created, null otherwise
*/
public static @Nullable Account createAccountIfNecessary(Context context) {
try {
AccountManager manager = AccountManager.get(context);
Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE);
Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
if (accounts.length == 0) {
if (!manager.addAccountExplicitly(account, null, null)) {
Timber.d("error when adding account via addAccountExplicitly");
return null;
} else {
return account;
}
} else {
return accounts[0];
}
} catch (SecurityException e) {
Timber.e(e, "SecurityException when adding the account");
Toast.makeText(context, R.string.reinstall_openkeychain, Toast.LENGTH_LONG).show();
return null;
}
}
public static HashMap<String,Bitmap> qrCodeCache = new HashMap<>();
@Override

View File

@@ -43,7 +43,6 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -254,9 +253,6 @@ public class CertifyOperation extends BaseReadWriteOperation<CertifyActionsParce
uploadOk, uploadError);
}
// since only verified keys are synced to contacts, we need to initiate a sync now
ContactSyncAdapterService.requestContactsSync();
log.add(LogType.MSG_CRT_SUCCESS, 0);
if (uploadError != 0) {
return new CertifyResult(CertifyResult.RESULT_WARNINGS, log, certifyOk, certifyError, uploadOk,

View File

@@ -30,7 +30,6 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.operations.results.UpdateTrustResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.DeleteKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -101,9 +100,6 @@ public class DeleteOperation extends BaseReadWriteOperation<DeleteKeyringParcel>
int result = DeleteResult.RESULT_OK;
if (success > 0) {
// make sure new data is synced into contacts
ContactSyncAdapterService.requestContactsSync();
log.add(LogType.MSG_DEL_OK, 0, success);
}
if (fail > 0) {

View File

@@ -38,7 +38,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -182,9 +181,6 @@ public class EditKeyOperation extends BaseReadWriteOperation<SaveKeyringParcel>
updateProgress(R.string.progress_done, 100, 100);
// make sure new data is synced into contacts
ContactSyncAdapterService.requestContactsSync();
log.add(LogType.MSG_ED_SUCCESS, 0);
return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId());

View File

@@ -32,9 +32,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
@@ -55,7 +55,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -134,9 +133,6 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
}
/**
* Since the introduction of multithreaded import, we expect calling functions to handle the
* contact-to-key sync i.e ContactSyncAdapterService.requestContactsSync()
*
* @param entries keys to import
* @param numTotalKeys number of keys to import
* @param hkpKeyserver contains uri of keyserver to import from, if it is an import from cloud
@@ -274,11 +270,6 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
}
}
// 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.requestContactsSync();
// convert to long array
long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()];
for (int i = 0; i < importedMasterKeyIds.size(); ++i) {
@@ -476,10 +467,6 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
result = multiThreadedKeyImport(keyList, keyServer, proxy, skipSave, forceReinsert);
}
if (!skipSave) {
ContactSyncAdapterService.requestContactsSync();
}
return result;
}

View File

@@ -1,177 +0,0 @@
/*
* Copyright (C) 2017 Schürmann & Breitmoser GbR
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.accounts.Account;
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;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceActivity;
import android.provider.ContactsContract;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.NotificationChannelManager;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.SettingsActivity;
import org.sufficientlysecure.keychain.util.ContactHelper;
import timber.log.Timber;
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);
public ContactSyncAdapter() {
super(ContactSyncAdapterService.this, true);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
final SyncResult syncResult) {
Timber.d("Performing a contact sync!");
new ContactHelper(ContactSyncAdapterService.this).writeKeysToContacts();
// 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);
NotificationChannelManager.getInstance(getContext()).createNotificationChannelsIfNecessary();
// 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, NotificationChannelManager.PERMISSION_REQUESTS)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_stat_notify_24dp)
.setColor(getResources().getColor(R.color.primary))
.setContentTitle(getString(R.string.sync_notification_permission_required_title))
.setContentText(getString(R.string.sync_notification_permission_required_text))
.setContentIntent(resultPendingIntent);
NotificationManagerCompat.from(ContactSyncAdapterService.this)
.notify(NOTIFICATION_ID_SYNC_SETTINGS, mBuilder.build());
}
}
@Override
public IBinder onBind(Intent intent) {
return new ContactSyncAdapter().getSyncAdapterBinder();
}
public static void requestContactsSync() {
// if user has disabled automatic sync, do nothing
boolean isSyncEnabled = ContentResolver.getSyncAutomatically(new Account
(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), ContactsContract.AUTHORITY);
if (!isSyncEnabled) {
return;
}
Bundle extras = new Bundle();
// 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,
extras);
}
public static void enableContactsSync(Context context) {
Account account = KeychainApplication.createAccountIfNecessary(context);
if (account == null) {
return;
}
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
}
// TODO: Import is currently disabled, until we implement proper origin management
// private static void importKeys() {
// importDone.set(false);
// KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this);
// EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(),
// new Handler.Callback() {
// @Override
// public boolean handleMessage(Message msg) {
// Bundle data = msg.getInputData();
// switch (msg.arg1) {
// case KeychainIntentServiceHandler.MESSAGE_OKAY:
// Log.d(Constants.TAG, "Syncing... Done.");
// synchronized (importDone) {
// importDone.set(true);
// importDone.notifyAll();
// }
// return true;
// case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS:
// if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) &&
// data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) {
// Log.d(Constants.TAG, "Syncing... Progress: " +
// data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" +
// data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX));
// return false;
// }
// default:
// Log.d(Constants.TAG, "Syncing... " + msg.toString());
// return false;
// }
// }
// })));
// synchronized (importDone) {
// try {
// if (!importDone.get()) importDone.wait();
// } catch (InterruptedException e) {
// Log.w(Constants.TAG, e);
// return;
// }
// }
// }
}

View File

@@ -1,133 +0,0 @@
/*
* Copyright (C) 2017 Schürmann & Breitmoser GbR
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.NetworkErrorException;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.widget.Toast;
import org.sufficientlysecure.keychain.R;
import timber.log.Timber;
/**
* This service actually does nothing, it's sole task is to show a Toast if the use tries to create an account.
*/
public class DummyAccountService extends Service {
private class Toaster {
private static final String TOAST_MESSAGE = "toast_message";
private Context context;
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(context, msg.getData().getString(TOAST_MESSAGE), Toast.LENGTH_LONG).show();
return true;
}
});
private Toaster(Context context) {
this.context = context;
}
public void toast(int resourceId) {
toast(context.getString(resourceId));
}
public void toast(String message) {
Message msg = new Message();
Bundle bundle = new Bundle();
bundle.putString(TOAST_MESSAGE, message);
msg.setData(bundle);
handler.sendMessage(msg);
}
}
private class Authenticator extends AbstractAccountAuthenticator {
public Authenticator() {
super(DummyAccountService.this);
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
Timber.d("DummyAccountService.editProperties");
return null;
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
String[] requiredFeatures, Bundle options) throws NetworkErrorException {
response.onResult(new Bundle());
toaster.toast(R.string.account_no_manual_account_creation);
Timber.d("DummyAccountService.addAccount");
return null;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
throws NetworkErrorException {
Timber.d("DummyAccountService.confirmCredentials");
return null;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType,
Bundle options) throws NetworkErrorException {
Timber.d("DummyAccountService.getAuthToken");
return null;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
Timber.d("DummyAccountService.getAuthTokenLabel");
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType,
Bundle options) throws NetworkErrorException {
Timber.d("DummyAccountService.updateCredentials");
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
throws NetworkErrorException {
Timber.d("DummyAccountService.hasFeatures");
return null;
}
}
private Toaster toaster;
@Override
public IBinder onBind(Intent intent) {
toaster = new Toaster(this);
return new Authenticator().getIBinder();
}
}

View File

@@ -23,16 +23,9 @@ import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.List;
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
@@ -40,17 +33,13 @@ import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
@@ -402,11 +391,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.sync_preferences);
findPreference(Constants.Pref.SYNC_KEYSERVER).setOnPreferenceChangeListener(
(preference, newValue) -> {
return true;
});
(preference, newValue) -> true);
}
@Override
@@ -414,130 +400,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
super.onStop();
KeyserverSyncManager.updateKeyserverSyncScheduleAsync(getActivity(), true);
}
@Override
public void onResume() {
super.onResume();
// this needs to be done in onResume since the user can change sync values from Android
// settings and we need to reflect that change when the user navigates back
final Account account = KeychainApplication.createAccountIfNecessary(getActivity());
// for contacts sync
initializeSyncCheckBox(
(SwitchPreference) findPreference(Constants.Pref.SYNC_CONTACTS),
account,
ContactsContract.AUTHORITY
);
}
private void initializeSyncCheckBox(final SwitchPreference syncCheckBox,
final Account account,
final String authority) {
// account is null if it could not be created for some reason
boolean syncEnabled =
account != null
&& ContentResolver.getSyncAutomatically(account, authority)
&& checkContactsPermission(authority);
syncCheckBox.setChecked(syncEnabled);
setSummary(syncCheckBox, authority, syncEnabled);
syncCheckBox.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@TargetApi(Build.VERSION_CODES.M)
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean syncEnabled = (Boolean) newValue;
if (syncEnabled) {
if (checkContactsPermission(authority)) {
ContentResolver.setSyncAutomatically(account, authority, true);
setSummary(syncCheckBox, authority, true);
return true;
} else {
requestPermissions(
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_PERMISSION_READ_CONTACTS);
// don't update preference
return false;
}
} else {
if (account == null) {
// if account could not be created for some reason,
// we can't have our sync
return false;
}
// disable syncs
ContentResolver.setSyncAutomatically(account, authority, false);
// cancel any ongoing/pending syncs
ContentResolver.cancelSync(account, authority);
setSummary(syncCheckBox, authority, false);
return true;
}
}
});
}
private boolean checkContactsPermission(String authority) {
if (!ContactsContract.AUTHORITY.equals(authority)) {
// provides convenience of not using separate checks for keyserver and contact sync
// in initializeSyncCheckBox
return true;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_GRANTED) {
return true;
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode != REQUEST_PERMISSION_READ_CONTACTS) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
return;
}
boolean permissionWasGranted = grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (permissionWasGranted) {
// 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_CONTACTS);
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
setSummary(pref, ContactsContract.AUTHORITY, true);
pref.setChecked(true);
}
}
private void setSummary(SwitchPreference syncCheckBox, String authority,
boolean checked) {
switch (authority) {
case Constants.PROVIDER_AUTHORITY: {
if (checked) {
syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_on);
} else {
syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_off);
}
break;
}
case ContactsContract.AUTHORITY: {
if (checked) {
syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_on);
} else {
syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_off);
}
break;
}
}
}
}
/**