Merge pull request #2586 from open-keychain/drop-more-stuff

Drop more stuff
This commit is contained in:
Vincent Breitmoser 2020-09-08 11:12:44 +02:00 committed by GitHub
commit 258cd4c836
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
96 changed files with 140 additions and 3872 deletions

View file

@ -49,9 +49,6 @@ dependencies {
// Nordpol
implementation 'com.fidesmo:nordpol-android:0.1.22'
// piwik
implementation 'org.piwik.sdk:piwik-sdk:3.0.3'
// libs as submodules
implementation project(':openpgp-api-lib')
implementation project(':nfcsweetspot')

View file

@ -54,21 +54,6 @@
<!-- CAMERA permission requested by ZXing library -->
<!-- contact group -->
<!--
AUTHENTICATE_ACCOUNTS and MANAGE_ACCOUNTS removed in Android >= 6,
see https://code.google.com/p/android-developer-preview/issues/detail?id=2592
also READ_PROFILE, WRITE_PROFILE?
-->
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.WRITE_PROFILE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<!-- storage group -->
<!--
No need on >= Android 4.4 for WRITE_EXTERNAL_STORAGE, because we use Storage Access Framework,
@ -1012,36 +997,6 @@
</intent-filter>
</service>
<!-- Contact Sync services -->
<service
android:name=".service.DummyAccountService"
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/account_desc" />
</service>
<service
android:name=".service.ContactSyncAdapterService"
android:exported="true"
android:process=":sync"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_adapter_contacts" />
<meta-data
android:name="android.provider.CONTACTS_STRUCTURE"
android:resource="@xml/sync_adapter_contacts_structure" />
</service>
<!-- Storage Provider for temporary decrypted files.
For security considerations, read class! -->
<provider

View file

@ -20,8 +20,6 @@ package org.sufficientlysecure.keychain;
import java.io.File;
import java.net.Proxy;
import java.util.Arrays;
import java.util.List;
import android.os.Environment;
@ -159,10 +157,6 @@ public final class Constants {
public static final String KEY_SIGNATURES_TABLE_INITIALIZED = "key_signatures_table_initialized";
public static final String KEY_ANALYTICS_ASKED_POLITELY = "analytics_asked";
public static final String KEY_ANALYTICS_CONSENT = "analytics_consent";
public static final String KEY_ANALYTICS_LAST_ASKED = "analytics_last_asked";
public static final class Theme {
public static final String LIGHT = "light";
public static final String DARK = "dark";
@ -173,14 +167,6 @@ public final class Constants {
public static final String TYPE_HTTP = "proxyHttp";
public static final String TYPE_SOCKS = "proxySocks";
}
// we generally only track booleans. never snoop around in the user's string settings!!
public static final List<String> ANALYTICS_PREFS = Arrays.asList(USE_NORMAL_PROXY, USE_TOR_PROXY,
SYNC_CONTACTS, SYNC_KEYSERVER, ENABLE_WIFI_SYNC_ONLY,
EXPERIMENTAL_USB_ALLOW_UNTESTED,
PASSPHRASE_CACHE_SUBS, SEARCH_KEYSERVER, SEARCH_WEB_KEY_DIRECTORY,
TEXT_USE_COMPRESSION, TEXT_SELF_ENCRYPT, FILE_USE_COMPRESSION, FILE_SELF_ENCRYPT, USE_ARMOR,
USE_NUMKEYPAD_FOR_SECURITY_TOKEN_PIN, ENCRYPT_FILENAMES);
}
/**

View file

@ -30,15 +30,15 @@ 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;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
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;
@ -46,8 +46,6 @@ import timber.log.Timber.DebugTree;
public class KeychainApplication extends Application {
AnalyticsManager analyticsManager;
/**
* Called when the application is starting, before any activity, service, or receiver objects
* (excluding content providers) have been created.
@ -88,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();
}
@ -116,36 +108,6 @@ public class KeychainApplication extends Application {
KeyserverSyncManager.updateKeyserverSyncScheduleAsync(this, false);
TemporaryFileProvider.scheduleCleanupImmediately(getApplicationContext());
analyticsManager = AnalyticsManager.getInstance(getApplicationContext());
analyticsManager.initialize(this);
}
/**
* @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<>();
@ -167,10 +129,6 @@ public class KeychainApplication extends Application {
}
}
public AnalyticsManager getAnalyticsManager() {
return analyticsManager;
}
public static String getProcessName() {
if (Build.VERSION.SDK_INT >= 28)
return Application.getProcessName();

View file

@ -1,95 +0,0 @@
package org.sufficientlysecure.keychain.analytics;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.preference.PreferenceActivity;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.SettingsActivity;
import org.sufficientlysecure.keychain.ui.SettingsActivity.ExperimentalPrefsFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.Preferences;
public class AnalyticsConsentRequester {
private final Activity activity;
public static AnalyticsConsentRequester getInstance(Activity activity) {
return new AnalyticsConsentRequester(activity);
}
private AnalyticsConsentRequester(Activity activity) {
this.activity = activity;
}
public void maybeAskForAnalytics() {
Preferences preferences = Preferences.getPreferences(activity);
if (preferences.isAnalyticsHasConsent()) {
return;
}
boolean askedBeforeAndWasRejected =
preferences.isAnalyticsAskedPolitely() && !preferences.isAnalyticsHasConsent();
if (askedBeforeAndWasRejected) {
return;
}
try {
long firstInstallTime =
activity.getPackageManager().getPackageInfo(BuildConfig.APPLICATION_ID, 0).firstInstallTime;
long threeDaysAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(3);
boolean installedLessThanThreeDaysAgo = firstInstallTime > threeDaysAgo;
if (installedLessThanThreeDaysAgo) {
return;
}
} catch (NameNotFoundException e) {
return;
}
long twentyFourHoursAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
boolean askedLessThan24HoursAgo = preferences.getAnalyticsLastAsked() > twentyFourHoursAgo;
if (askedLessThan24HoursAgo) {
return;
}
preferences.setAnalyticsLastAskedNow();
AnalyticsManager analyticsManager = ((KeychainApplication) activity.getApplication()).getAnalyticsManager();
AlertDialog alertDialog = new Builder(activity)
.setMessage(R.string.dialog_analytics_consent)
.setPositiveButton(R.string.button_analytics_yes, (dialog, which) -> {
preferences.setAnalyticsAskedPolitely();
preferences.setAnalyticsGotUserConsent(true);
analyticsManager.refreshSettings(activity);
Notify.create(activity, R.string.snack_analytics_accept, Style.OK,
this::startExperimentalSettingsActivity, R.string.snackbutton_analytics_settings).show();
})
.setNegativeButton(R.string.button_analytics_no, (dialog, which) -> {
preferences.setAnalyticsAskedPolitely();
preferences.setAnalyticsGotUserConsent(false);
analyticsManager.refreshSettings(activity);
Notify.create(activity, R.string.snack_analytics_reject, Style.OK,
this::startExperimentalSettingsActivity, R.string.snackbutton_analytics_settings).show();
})
.show();
alertDialog.<TextView>findViewById(android.R.id.message).setMovementMethod(LinkMovementMethod.getInstance());
}
private void startExperimentalSettingsActivity() {
Intent resultIntent = new Intent(activity, SettingsActivity.class);
String experimentalPrefsName = ExperimentalPrefsFragment.class.getName();
resultIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, experimentalPrefsName);
activity.startActivity(resultIntent);
}
}

View file

@ -1,180 +0,0 @@
package org.sufficientlysecure.keychain.analytics;
import android.app.Activity;
import android.app.Application;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import org.piwik.sdk.Piwik;
import org.piwik.sdk.Tracker;
import org.piwik.sdk.TrackerConfig;
import org.piwik.sdk.extra.DownloadTracker.Extra.ApkChecksum;
import org.piwik.sdk.extra.TrackHelper;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.Defaults;
import org.sufficientlysecure.keychain.Constants.Pref;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;
public class AnalyticsManager implements OnSharedPreferenceChangeListener {
private Tracker piwikTracker;
public static AnalyticsManager getInstance(Context context) {
return new AnalyticsManager(context);
}
private AnalyticsManager(Context context) {
refreshSettings(context);
}
public void initialize(Application application) {
if (piwikTracker != null) {
TrackHelper.track().download().identifier(new ApkChecksum(application)).with(piwikTracker);
}
application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
if (piwikTracker == null) {
return;
}
TrackHelper.track().screen(activity.getClass().getSimpleName()).with(piwikTracker);
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
Preferences preferences = Preferences.getPreferences(application);
preferences.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
// we generally only track booleans. never snoop around in the user's string settings!!
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (piwikTracker == null) {
return;
}
// small exception: check if the user uses a custom keyserver, or one of the well-known ones
if (Pref.KEY_SERVERS.equals(key)) {
Timber.d("Tracking pref %s", key);
String keyServers = sharedPreferences.getString(Pref.KEY_SERVERS, Defaults.KEY_SERVERS);
String current = keyServers.substring(keyServers.indexOf(','));
String coarseGranularityKeyserver;
if (current.contains("keyserver.ubuntu.com")) {
coarseGranularityKeyserver = "ubuntu";
} else if (current.contains("pgp.mit.edu")) {
coarseGranularityKeyserver = "mit";
} else if (current.contains("pool.sks-keyservers.net")) {
coarseGranularityKeyserver = "pool";
} else {
coarseGranularityKeyserver = "custom";
}
TrackHelper.track().interaction("pref_" + Pref.KEY_SERVERS, coarseGranularityKeyserver).with(piwikTracker);
return;
}
// unpack an enum
if (Pref.THEME.equals(key)) {
String value = sharedPreferences.getString(Pref.THEME, "empty");
TrackHelper.track().interaction("pref_" + Pref.THEME, value).with(piwikTracker);
return;
}
// all other values we track are individual booleans
if (Pref.ANALYTICS_PREFS.contains(key)) {
Timber.d("Tracking pref %s", key);
if (!sharedPreferences.contains(key)) {
TrackHelper.track().interaction("pref_" + key, "empty").with(piwikTracker);
return;
}
boolean value = sharedPreferences.getBoolean(key, false);
TrackHelper.track().interaction("pref_" + key, value ? "true" : "false").with(piwikTracker);
}
}
public void trackFragmentImpression(String opClassName, String fragmentName) {
if (piwikTracker == null) {
return;
}
TrackHelper.track().screen(opClassName + "/" + fragmentName).with(piwikTracker);
}
public void trackInternalServiceCall(String opClassName) {
if (piwikTracker == null) {
return;
}
TrackHelper.track()
.interaction("internalApiCall", opClassName)
.with(piwikTracker);
}
public void trackApiServiceCall(String opClassName, String currentCallingPackage) {
if (piwikTracker == null) {
return;
}
TrackHelper.track()
.interaction("externalApiCall", opClassName)
.piece(currentCallingPackage.replace(".", "/"))
.with(piwikTracker);
}
public synchronized void refreshSettings(Context context) {
boolean shouldEnableAnalytics = shouldEnableAnalytics(context);
boolean analyticsEnabled = piwikTracker != null;
if (shouldEnableAnalytics != analyticsEnabled) {
if (shouldEnableAnalytics) {
TrackerConfig trackerConfig;
if (Constants.DEBUG) {
trackerConfig = new TrackerConfig("https://piwik.openkeychain.org/", 3, "OpenKeychainDebug");
} else {
trackerConfig = new TrackerConfig("https://piwik.openkeychain.org/", 2, "OpenKeychain");
}
piwikTracker = Piwik.getInstance(context).newTracker(trackerConfig);
piwikTracker.setDispatchInterval(60000);
piwikTracker.setOptOut(false);
} else {
piwikTracker.setOptOut(true);
piwikTracker = null;
}
}
}
private boolean shouldEnableAnalytics(Context context) {
Preferences preferences = Preferences.getPreferences(context);
return preferences.isAnalyticsHasConsent() && !preferences.getUseTorProxy();
}
}

View file

@ -18,7 +18,6 @@
package org.sufficientlysecure.keychain.keysync;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -29,11 +28,10 @@ import android.os.Build.VERSION_CODES;
import androidx.annotation.WorkerThread;
import androidx.work.Constraints.Builder;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkInfo.State;
import androidx.work.WorkManager;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;
@ -43,7 +41,8 @@ public class KeyserverSyncManager {
private static final long SYNC_INTERVAL = 3;
private static final TimeUnit SYNC_INTERVAL_UNIT = TimeUnit.DAYS;
private static final String PERIODIC_WORK_TAG = "keyserverSync";
private static final String LEGACY_PERIODIC_WORK_TAG = "keyserverSync";
private static final String WORK_UNIQUE_NAME = "periodicKeyserverSync";
public static void updateKeyserverSyncScheduleAsync(Context context, boolean forceReschedule) {
new AsyncTask<Void,Void,Void>() {
@ -60,27 +59,12 @@ public class KeyserverSyncManager {
Preferences prefs = Preferences.getPreferences(context);
WorkManager workManager = WorkManager.getInstance(context);
UUID workUuid = prefs.getKeyserverSyncWorkUuid();
try {
WorkInfo info = workUuid != null ? workManager.getWorkInfoById(workUuid).get() : null;
boolean workIsScheduled = info != null && info.getState() != State.CANCELLED;
if (workIsScheduled == prefs.isKeyserverSyncEnabled()) {
if (!forceReschedule) {
Timber.d("Key sync already scheduled, no changes necessary");
return;
}
Timber.d("Key sync already scheduled, but forcing reschedule");
}
} catch (ExecutionException | InterruptedException e) {
Timber.e(e, "Error getting info for scheduled key sync work?");
}
Timber.d("Cancelling sync tasks…");
workManager.cancelAllWorkByTag(PERIODIC_WORK_TAG);
// Cancel work that was scheduled by tag, as we used to do.
workManager.cancelAllWorkByTag(LEGACY_PERIODIC_WORK_TAG);
if (!prefs.isKeyserverSyncEnabled()) {
Timber.d("Key sync disabled");
workManager.cancelUniqueWork(WORK_UNIQUE_NAME);
return;
}
@ -96,10 +80,12 @@ public class KeyserverSyncManager {
PeriodicWorkRequest workRequest =
new PeriodicWorkRequest.Builder(KeyserverSyncWorker.class, SYNC_INTERVAL, SYNC_INTERVAL_UNIT)
.setConstraints(constraints.build())
.addTag(PERIODIC_WORK_TAG)
.build();
try {
workManager.enqueue(workRequest).getResult().get();
ExistingPeriodicWorkPolicy policy = forceReschedule
? ExistingPeriodicWorkPolicy.REPLACE
: ExistingPeriodicWorkPolicy.KEEP;
workManager.enqueueUniquePeriodicWork(WORK_UNIQUE_NAME, policy, workRequest).getResult().get();
Timber.d("Work id: %s", workRequest.getId());
prefs.setKeyserverSyncScheduled(workRequest.getId());
} catch (InterruptedException | ExecutionException e) {

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

@ -26,7 +26,6 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.app.Application;
import android.content.ClipDescription;
import android.content.ContentProvider;
import android.content.ContentResolver;
@ -41,6 +40,7 @@ import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import androidx.annotation.NonNull;
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
@ -77,6 +77,7 @@ public class TemporaryFileProvider extends ContentProvider {
public static final String AUTHORITY = Constants.TEMP_FILE_PROVIDER_AUTHORITY;
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
private static final int DB_VERSION = 3;
public static final String WORK_NAME_CLEANUP = "cleanup";
interface TemporaryFileColumns {
String COLUMN_UUID = "id";
@ -315,20 +316,22 @@ public class TemporaryFileProvider extends ContentProvider {
public static void scheduleCleanupAfterTtl(Context context) {
OneTimeWorkRequest cleanupWork = new OneTimeWorkRequest.Builder(CleanupWorker.class)
.setInitialDelay(Constants.TEMPFILE_TTL, TimeUnit.MILLISECONDS).build();
workManagerEnqueue(context, cleanupWork);
workManagerEnqueueCleanup(context, cleanupWork);
}
public static void scheduleCleanupImmediately(Context context) {
OneTimeWorkRequest cleanupWork = new OneTimeWorkRequest.Builder(CleanupWorker.class).build();
workManagerEnqueue(context, cleanupWork);
workManagerEnqueueCleanup(context, cleanupWork);
}
private static void workManagerEnqueue(Context context, OneTimeWorkRequest cleanupWork) {
private static void workManagerEnqueueCleanup(Context context, OneTimeWorkRequest cleanupWork) {
// work manager is only available on the main thread
if (!BuildConfig.APPLICATION_ID.equals(KeychainApplication.getProcessName())) {
return;
}
WorkManager.getInstance(context).enqueue(cleanupWork);
WorkManager
.getInstance(context)
.enqueueUniqueWork(TemporaryFileProvider.WORK_NAME_CLEANUP, ExistingWorkPolicy.REPLACE, cleanupWork);
}
public static class CleanupWorker extends Worker {

View file

@ -38,10 +38,10 @@ import android.os.Messenger;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.openintents.openpgp.AutocryptPeerUpdate;
import org.openintents.openpgp.IOpenPgpService;
@ -52,9 +52,12 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.OpenPgpSignatureResult.AutocryptPeerResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.daos.OverriddenWarningsDao;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
import org.sufficientlysecure.keychain.operations.BackupOperation;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
@ -69,12 +72,7 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SecurityProblem;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus;
import org.sufficientlysecure.keychain.daos.OverriddenWarningsDao;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus;
import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
@ -101,7 +99,6 @@ public class OpenPgpService extends Service {
private ApiAppDao mApiAppDao;
private OpenPgpServiceKeyIdExtractor mKeyIdExtractor;
private ApiPendingIntentFactory mApiPendingIntentFactory;
private AnalyticsManager analyticsManager;
@Override
public void onCreate() {
@ -111,8 +108,6 @@ public class OpenPgpService extends Service {
mApiPermissionHelper = new ApiPermissionHelper(this, mApiAppDao);
mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext());
mKeyIdExtractor = OpenPgpServiceKeyIdExtractor.getInstance(getContentResolver(), mApiPendingIntentFactory);
analyticsManager = ((KeychainApplication) getApplication()).getAnalyticsManager();
}
private Intent signImpl(Intent data, InputStream inputStream,
@ -1032,8 +1027,6 @@ public class OpenPgpService extends Service {
return errorResult;
}
analyticsManager.trackApiServiceCall(data.getAction(), mApiPermissionHelper.getCurrentCallingPackage());
Progressable progressable = null;
if (data.hasExtra(OpenPgpApi.EXTRA_PROGRESS_MESSENGER)) {
Messenger messenger = data.getParcelableExtra(OpenPgpApi.EXTRA_PROGRESS_MESSENGER);

View file

@ -40,17 +40,15 @@ import org.openintents.ssh.authentication.response.PublicKeyResponse;
import org.openintents.ssh.authentication.response.SigningResponse;
import org.openintents.ssh.authentication.response.SshPublicKeyResponse;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.SshPublicKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ssh.AuthenticationData;
@ -67,8 +65,6 @@ public class SshAuthenticationService extends Service {
private ApiAppDao mApiAppDao;
private ApiPendingIntentFactory mApiPendingIntentFactory;
private AnalyticsManager analyticsManager;
private static final List<Integer> SUPPORTED_VERSIONS = Collections.unmodifiableList(Collections.singletonList(1));
private static final int INVALID_API_VERSION = -1;
@ -82,8 +78,6 @@ public class SshAuthenticationService extends Service {
mApiAppDao = ApiAppDao.getInstance(this);
mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext());
analyticsManager = ((KeychainApplication) getApplication()).getAnalyticsManager();
}
private final ISshAuthenticationService.Stub mSSHAgent = new ISshAuthenticationService.Stub() {
@ -109,8 +103,6 @@ public class SshAuthenticationService extends Service {
}
private Intent executeInternal(Intent intent) {
analyticsManager.trackApiServiceCall(intent.getAction(), mApiPermissionHelper.getCurrentCallingPackage());
switch (intent.getAction()) {
case SshAuthenticationApi.ACTION_SIGN:
return authenticate(intent);

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

@ -25,10 +25,8 @@ import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Parcelable;
import androidx.core.os.CancellationSignal;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
import androidx.core.os.CancellationSignal;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.operations.BackupOperation;
import org.sufficientlysecure.keychain.operations.BaseOperation;
@ -54,20 +52,15 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
public class KeychainServiceTask {
private final AnalyticsManager analyticsManager;
public static KeychainServiceTask create(Activity activity) {
Context context = activity.getApplicationContext();
KeyWritableRepository keyRepository = KeyWritableRepository.create(context);
AnalyticsManager analyticsManager = ((KeychainApplication) activity.getApplication()).getAnalyticsManager();
return new KeychainServiceTask(context, keyRepository, analyticsManager);
return new KeychainServiceTask(context, keyRepository);
}
private KeychainServiceTask(Context context, KeyWritableRepository keyRepository, AnalyticsManager analyticsManager) {
private KeychainServiceTask(Context context, KeyWritableRepository keyRepository) {
this.context = context;
this.keyRepository = keyRepository;
this.analyticsManager = analyticsManager;
}
private final Context context;
@ -128,8 +121,6 @@ public class KeychainServiceTask {
return null;
}
analyticsManager.trackInternalServiceCall(op.getClass().getSimpleName());
// noinspection unchecked, we make sure it's the correct op above
return op.execute(inputParcel, cryptoInput);
}

View file

@ -17,17 +17,16 @@
package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -37,18 +36,20 @@ import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.dialog.AddEmailDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.widget.EmailEditText;
import java.util.ArrayList;
import java.util.List;
public class CreateKeyEmailFragment extends Fragment {
private CreateKeyActivity mCreateKeyActivity;
private EmailEditText mEmailEdit;
private AppCompatEditText mEmailEdit;
private ArrayList<EmailAdapter.ViewModel> mAdditionalEmailModels = new ArrayList<>();
private EmailAdapter mEmailAdapter;

View file

@ -20,20 +20,20 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.fragment.app.Fragment;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.widget.NameEditText;
public class CreateKeyNameFragment extends Fragment {
CreateKeyActivity mCreateKeyActivity;
NameEditText mNameEdit;
AppCompatEditText mNameEdit;
View mBackButton;
View mNextButton;

View file

@ -17,19 +17,13 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.MatrixCursor;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.provider.BaseColumns;
import androidx.fragment.app.Fragment;
import androidx.core.view.MenuItemCompat;
import androidx.core.view.MenuItemCompat.OnActionExpandListener;
import androidx.cursoradapter.widget.CursorAdapter;
import androidx.cursoradapter.widget.SimpleCursorAdapter;
import androidx.appcompat.widget.SearchView;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -38,18 +32,17 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import androidx.appcompat.widget.SearchView;
import androidx.core.view.MenuItemCompat;
import androidx.core.view.MenuItemCompat.OnActionExpandListener;
import androidx.fragment.app.Fragment;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.Preferences.CloudSearchPrefs;
import java.util.ArrayList;
import java.util.List;
import static androidx.appcompat.widget.SearchView.OnQueryTextListener;
import static androidx.appcompat.widget.SearchView.OnSuggestionListener;
/**
* Consists of the search bar, search button, and search settings button
@ -59,16 +52,9 @@ public class ImportKeysSearchFragment extends Fragment {
public static final String ARG_QUERY = "query";
public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs";
private static final String CURSOR_SUGGESTION = "suggestion";
private Activity mActivity;
private ImportKeysListener mCallback;
private List<String> mNamesAndEmails;
private SimpleCursorAdapter mSearchAdapter;
private List<String> mCurrentSuggestions = new ArrayList<>();
/**
* Creates new instance of this fragment
*
@ -92,14 +78,6 @@ public class ImportKeysSearchFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater i, ViewGroup c, Bundle savedInstanceState) {
ContactHelper contactHelper = new ContactHelper(mActivity);
mNamesAndEmails = contactHelper.getContactNames();
mNamesAndEmails.addAll(contactHelper.getContactMails());
mSearchAdapter = new SimpleCursorAdapter(mActivity,
R.layout.import_keys_cloud_suggestions_item, null, new String[]{CURSOR_SUGGESTION},
new int[]{android.R.id.text1}, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
setHasOptionsMenu(true);
// no view, just search view
@ -126,21 +104,7 @@ public class ImportKeysSearchFragment extends Fragment {
MenuItem searchItem = menu.findItem(R.id.menu_import_keys_cloud_search);
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setSuggestionsAdapter(mSearchAdapter);
searchView.setOnSuggestionListener(new OnSuggestionListener() {
@Override
public boolean onSuggestionSelect(int position) {
searchView.setQuery(mCurrentSuggestions.get(position), true);
return true;
}
@Override
public boolean onSuggestionClick(int position) {
searchView.setQuery(mCurrentSuggestions.get(position), true);
return true;
}
});
searchView.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
searchView.setOnQueryTextListener(new OnQueryTextListener() {
@Override
@ -152,7 +116,6 @@ public class ImportKeysSearchFragment extends Fragment {
@Override
public boolean onQueryTextChange(String newText) {
updateAdapter(newText);
return false;
}
});
@ -180,20 +143,6 @@ public class ImportKeysSearchFragment extends Fragment {
super.onCreateOptionsMenu(menu, inflater);
}
private void updateAdapter(String query) {
mCurrentSuggestions.clear();
MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, CURSOR_SUGGESTION});
for (int i = 0; i < mNamesAndEmails.size(); i++) {
String s = mNamesAndEmails.get(i);
if (s.toLowerCase().startsWith(query.toLowerCase())) {
mCurrentSuggestions.add(s);
c.addRow(new Object[]{i, s});
}
}
mSearchAdapter.changeCursor(c);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();

View file

@ -54,7 +54,6 @@ import eu.davidea.flexibleadapter.SelectableAdapter.Mode;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.analytics.AnalyticsConsentRequester;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.daos.KeyRepository;
@ -254,8 +253,6 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class);
LiveData<List<FlexibleKeyItem>> liveData = viewModel.getGenericLiveData(requireContext(), this::loadFlexibleKeyItems);
liveData.observe(this, this::onLoadKeyItems);
AnalyticsConsentRequester.getInstance(activity).maybeAskForAnalytics();
}
@WorkerThread

View file

@ -17,18 +17,18 @@
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.appcompat.widget.Toolbar;
import android.view.View;
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
@ -37,10 +37,7 @@ import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.model.DividerDrawerItem;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment;
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
@ -58,8 +55,6 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
public static final int ID_TRANSFER = 5;
static final int ID_SETTINGS = 6;
static final int ID_HELP = 7;
static final int ID_SHOP = 8;
static final int ID_AUTOCRYPT = 9;
// both of these are used for instrumentation testing only
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
@ -67,7 +62,6 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
public Drawer mDrawer;
private Toolbar mToolbar;
private AnalyticsManager analyticsManager;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -78,15 +72,11 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
mToolbar.setTitle(R.string.app_name);
setSupportActionBar(mToolbar);
analyticsManager = ((KeychainApplication) getApplication()).getAnalyticsManager();
mDrawer = new DrawerBuilder()
.withActivity(this)
.withHeader(R.layout.main_drawer_header)
.withToolbar(mToolbar)
.addDrawerItems(
new PrimaryDrawerItem().withName(R.string.nav_shop).withIcon(CommunityMaterial.Icon.cmd_shopping)
.withIdentifier(ID_SHOP).withSelectable(false).withTypeface(Typeface.DEFAULT_BOLD),
new PrimaryDrawerItem().withName(R.string.nav_keys).withIcon(CommunityMaterial.Icon.cmd_key)
.withIdentifier(ID_KEYS).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock)
@ -102,10 +92,7 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
.withIdentifier(ID_TRANSFER).withSelectable(false),
new DividerDrawerItem(),
new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false),
new DividerDrawerItem(),
new PrimaryDrawerItem().withName(R.string.nav_autocrypt).withIcon(GoogleMaterial.Icon.gmd_blur_on)
.withTypeface(Typeface.DEFAULT_BOLD).withSelectable(false).withIdentifier(ID_AUTOCRYPT)
new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false)
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
@ -135,12 +122,6 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
case ID_HELP:
intent = new Intent(MainActivity.this, HelpActivity.class);
break;
case ID_SHOP:
onShopSelected();
break;
case ID_AUTOCRYPT:
onAutocryptSelected();
break;
}
if (intent != null) {
MainActivity.this.startActivity(intent);
@ -219,8 +200,6 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
private void setFragment(Fragment frag) {
FragmentManager fragmentManager = getSupportFragmentManager();
analyticsManager.trackFragmentImpression(getClass().getSimpleName(), frag.getClass().getSimpleName());
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.main_fragment_container, frag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
@ -267,20 +246,6 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
}
}
private void onShopSelected() {
mToolbar.setTitle(R.string.shop_title);
mDrawer.setSelection(ID_SHOP, false);
Fragment frag = new SecurityKeyShopFragment();
setFragment(frag);
}
private void onAutocryptSelected() {
String url = "https://addons.thunderbird.net/en-US/thunderbird/addon/autocrypt/";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// add the values which need to be saved from the drawer to the bundle

View file

@ -1,77 +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.ui;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import java.util.HashMap;
import java.util.Map;
public class SecurityKeyShopFragment extends Fragment {
public static final String webShopURL = "https://shop.cotech.de/";
public static final String referer = "https://openkeychain.shop.cotech.de";
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.security_key_shop_fragment, container, false);
WebView webView = view.findViewById(R.id.shop_webView);
webView.setWebViewClient(new SecurityKeyShopWebViewClient(
view.findViewById(R.id.shop_progressbar),
view.findViewById(R.id.shop_progressbar_label)
));
webView.getSettings().setJavaScriptEnabled(true);
Map<String, String> headers = new HashMap<>();
headers.put("Referer", referer);
webView.loadUrl(webShopURL, headers);
return view;
}
class SecurityKeyShopWebViewClient extends WebViewClient {
private ProgressBar progressBar;
private TextView progressBarLabel;
SecurityKeyShopWebViewClient(ProgressBar progressBar, TextView progressBarLabel) {
this.progressBar = progressBar;
this.progressBarLabel = progressBarLabel;
progressBar.setVisibility(View.VISIBLE);
progressBarLabel.setVisibility(View.VISIBLE);
}
@Override
public void onPageCommitVisible(WebView view, String url) {
super.onPageCommitVisible(view, url);
progressBar.setVisibility(View.GONE);
progressBarLabel.setVisibility(View.GONE);
}
}
}

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 androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.appcompat.widget.Toolbar;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.appcompat.widget.Toolbar;
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;
}
}
}
}
/**
@ -579,14 +441,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
});
}
@Override
public void onPause() {
super.onPause();
Activity activity = getActivity();
((KeychainApplication) activity.getApplication()).getAnalyticsManager().refreshSettings(activity);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {

View file

@ -22,17 +22,9 @@ import java.util.List;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager.widget.ViewPager.OnPageChangeListener;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
@ -41,16 +33,22 @@ import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.OvershootInterpolator;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProviders;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager.widget.ViewPager.OnPageChangeListener;
import com.astuetz.PagerSlidingTabStrip;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.model.UserPacket.UserId;
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
@ -89,8 +87,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
}
}
private AnalyticsManager analyticsManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -98,7 +94,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
setFullScreenDialogClose(v -> finish());
keyRepository = KeyRepository.create(this);
analyticsManager = ((KeychainApplication) getApplication()).getAnalyticsManager();
viewPager = findViewById(R.id.pager);
slidingTabLayout = findViewById(R.id.sliding_tab_layout);
@ -300,9 +295,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
actionMode = null;
}
invalidateOptionsMenu();
String fragmentName = tabAdapter.getItem(position).getClass().getSimpleName();
analyticsManager.trackFragmentImpression(getClass().getSimpleName(), fragmentName);
}
@Override

View file

@ -27,6 +27,8 @@ import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.fragment.app.DialogFragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@ -38,7 +40,6 @@ import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.widget.EmailEditText;
import timber.log.Timber;
@ -51,7 +52,7 @@ public class AddEmailDialogFragment extends DialogFragment implements OnEditorAc
public static final String MESSAGE_DATA_EMAIL = "email";
private Messenger mMessenger;
private EmailEditText mEmail;
private AppCompatEditText mEmail;
public static AddEmailDialogFragment newInstance(Messenger messenger) {

View file

@ -17,8 +17,8 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Activity;
import androidx.appcompat.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
@ -27,7 +27,6 @@ import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import androidx.fragment.app.DialogFragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@ -37,11 +36,12 @@ import android.widget.Button;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.fragment.app.DialogFragment;
import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.ui.widget.EmailEditText;
import org.sufficientlysecure.keychain.ui.widget.NameEditText;
import timber.log.Timber;
@ -55,8 +55,8 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA
public static final String MESSAGE_DATA_USER_ID = "user_id";
private Messenger mMessenger;
private NameEditText mName;
private EmailEditText mEmail;
private AppCompatEditText mName;
private AppCompatEditText mEmail;
public static AddUserIdDialogFragment newInstance(Messenger messenger, String predefinedName) {

View file

@ -16,14 +16,11 @@ import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao;
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo;
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao;
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus;
import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao;
import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo;
public class KeyFragmentViewModel extends ViewModel {
private LiveData<List<IdentityInfo>> identityInfo;
private LiveData<KeySubkeyStatus> subkeyStatus;
private LiveData<SystemContactInfo> systemContactInfo;
private LiveData<KeyMetadata> keyserverStatus;
LiveData<List<IdentityInfo>> getIdentityInfo(Context context, LiveData<UnifiedKeyInfo> unifiedKeyInfoLiveData) {
@ -46,17 +43,6 @@ public class KeyFragmentViewModel extends ViewModel {
return subkeyStatus;
}
LiveData<SystemContactInfo> getSystemContactInfo(Context context, LiveData<UnifiedKeyInfo> unifiedKeyInfoLiveData) {
if (systemContactInfo == null) {
SystemContactDao systemContactDao = SystemContactDao.getInstance(context);
systemContactInfo = Transformations.switchMap(unifiedKeyInfoLiveData,
(unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(context,
() -> systemContactDao.getSystemContactInfo(unifiedKeyInfo.master_key_id(),
unifiedKeyInfo.has_any_secret())));
}
return systemContactInfo;
}
LiveData<KeyMetadata> getKeyserverStatus(Context context, LiveData<UnifiedKeyInfo> unifiedKeyInfoLiveData) {
if (keyserverStatus == null) {
KeyMetadataDao keyMetadataDao = KeyMetadataDao.create(context);

View file

@ -28,11 +28,9 @@ import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityOptions;
import androidx.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@ -40,17 +38,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.provider.ContactsContract;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentManager;
import androidx.core.content.ContextCompat;
import androidx.cardview.widget.CardView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@ -58,13 +45,22 @@ import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProviders;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.daos.KeyRepository;
@ -102,10 +98,8 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.ShareKeyHelper;
import timber.log.Timber;
public class ViewKeyActivity extends BaseSecurityTokenActivity {
@ -121,7 +115,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity {
public static final String EXTRA_MASTER_KEY_ID = "master_key_id";
public static final String EXTRA_DISPLAY_RESULT = "display_result";
public static final String EXTRA_LINKED_TRANSITION = "linked_transition";
KeyRepository keyRepository;
@ -140,8 +133,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity {
private ImageButton actionShare;
private ImageButton actionShareClipboard;
private FloatingActionButton floatingActionButton;
private ImageView photoView;
private FrameLayout photoLayout;
private ImageView qrCodeView;
private CardView qrCodeLayout;
@ -181,8 +172,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity {
actionShare= findViewById(R.id.view_key_action_share);
actionShareClipboard = findViewById(R.id.view_key_action_share_clipboard);
floatingActionButton = findViewById(R.id.fab);
photoView = findViewById(R.id.view_key_photo);
photoLayout = findViewById(R.id.view_key_photo_layout);
qrCodeView = findViewById(R.id.view_key_qr_code);
qrCodeLayout = findViewById(R.id.view_key_qr_code_layout);
@ -243,20 +232,10 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity {
long masterKeyId;
Intent intent = getIntent();
Uri dataUri = intent.getData();
if (intent.hasExtra(EXTRA_MASTER_KEY_ID)) {
masterKeyId = intent.getLongExtra(EXTRA_MASTER_KEY_ID, 0L);
} else if (dataUri != null && dataUri.getHost().equals(ContactsContract.AUTHORITY)) {
Long contactMasterKeyId = new ContactHelper(this).masterKeyIdFromContactsDataUri(dataUri);
if (contactMasterKeyId == null) {
Timber.e("Contact Data missing. Should be uri of key!");
Toast.makeText(this, R.string.error_contacts_key_id_missing, Toast.LENGTH_LONG).show();
finish();
return;
}
masterKeyId = contactMasterKeyId;
} else {
throw new IllegalArgumentException("Missing required extra master_key_id or contact uri");
throw new IllegalArgumentException("Missing required extra master_key_id");
}
actionEncryptFile.setOnClickListener(v -> encrypt(false));
@ -660,25 +639,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity {
// this is done at the end of the animation otherwise
}
AsyncTask<Long, Void, Bitmap> photoTask =
new AsyncTask<Long, Void, Bitmap>() {
protected Bitmap doInBackground(Long... mMasterKeyId) {
return new ContactHelper(ViewKeyActivity.this)
.loadPhotoByMasterKeyId(mMasterKeyId[0], true);
}
protected void onPostExecute(Bitmap photo) {
if (photo == null) {
return;
}
photoView.setImageBitmap(photo);
photoView.setColorFilter(ContextCompat.getColor(ViewKeyActivity.this, R.color.toolbar_photo_tint),
PorterDuff.Mode.SRC_ATOP);
photoLayout.setVisibility(View.VISIBLE);
}
};
boolean showStatusText = unifiedKeyInfo.is_secure() && !unifiedKeyInfo.is_expired() && !unifiedKeyInfo.is_revoked();
if (showStatusText) {
statusText.setVisibility(View.VISIBLE);
@ -733,7 +693,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity {
if (!Arrays.equals(unifiedKeyInfo.fingerprint(), qrCodeLoaded)) {
loadQrCode(unifiedKeyInfo.fingerprint());
}
photoTask.execute(unifiedKeyInfo.master_key_id());
qrCodeLayout.setVisibility(View.VISIBLE);
// and place leftOf qr code
@ -775,7 +734,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity {
KeyFormattingUtils.setStatusImage(this, statusImage, statusText,
State.VERIFIED, R.color.icons, true);
color = ContextCompat.getColor(this, R.color.key_flag_green);
photoTask.execute(unifiedKeyInfo.master_key_id());
hideFab();
} else {

View file

@ -20,25 +20,21 @@ package org.sufficientlysecure.keychain.ui.keyview;
import java.util.List;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
@ -55,18 +51,15 @@ import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.UserIdInfo;
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeyHealthStatus;
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus;
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.SubKeyItem;
import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo;
import org.sufficientlysecure.keychain.ui.keyview.view.IdentitiesCardView;
import org.sufficientlysecure.keychain.ui.keyview.view.KeyHealthView;
import org.sufficientlysecure.keychain.ui.keyview.view.KeyStatusList.KeyDisplayStatus;
import org.sufficientlysecure.keychain.ui.keyview.view.KeyserverStatusView;
import org.sufficientlysecure.keychain.ui.keyview.view.SystemContactCardView;
import timber.log.Timber;
public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener {
private IdentitiesCardView identitiesCardView;
private SystemContactCardView systemContactCard;
private KeyHealthView keyStatusHealth;
private KeyserverStatusView keyserverStatusView;
private View keyStatusCardView;
@ -87,7 +80,6 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
View view = inflater.inflate(R.layout.view_key_fragment, viewGroup, false);
identitiesCardView = view.findViewById(R.id.card_identities);
systemContactCard = view.findViewById(R.id.linked_system_contact_card);
keyStatusCardView = view.findViewById(R.id.subkey_status_card);
keyStatusHealth = view.findViewById(R.id.key_status_health);
keyserverStatusView = view.findViewById(R.id.key_status_keyserver);
@ -118,17 +110,16 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
Context context = requireContext();
UnifiedKeyInfoViewModel viewKeyViewModel = ViewModelProviders.of(requireActivity()).get(UnifiedKeyInfoViewModel.class);
UnifiedKeyInfoViewModel viewKeyViewModel = new ViewModelProvider(requireActivity()).get(UnifiedKeyInfoViewModel.class);
LiveData<UnifiedKeyInfo> unifiedKeyInfoLiveData = viewKeyViewModel.getUnifiedKeyInfoLiveData(requireContext());
unifiedKeyInfoLiveData.observe(this, this::onLoadUnifiedKeyInfo);
unifiedKeyInfoLiveData.observe(getViewLifecycleOwner(), this::onLoadUnifiedKeyInfo);
KeyFragmentViewModel model = ViewModelProviders.of(this).get(KeyFragmentViewModel.class);
KeyFragmentViewModel model = new ViewModelProvider(this).get(KeyFragmentViewModel.class);
model.getIdentityInfo(context, unifiedKeyInfoLiveData).observe(this, this::onLoadIdentityInfo);
model.getKeyserverStatus(context, unifiedKeyInfoLiveData).observe(this, this::onLoadKeyMetadata);
model.getSystemContactInfo(context, unifiedKeyInfoLiveData).observe(this, this::onLoadSystemContact);
model.getSubkeyStatus(context, unifiedKeyInfoLiveData).observe(this, this::onLoadSubkeyStatus);
model.getIdentityInfo(context, unifiedKeyInfoLiveData).observe(getViewLifecycleOwner(), this::onLoadIdentityInfo);
model.getKeyserverStatus(context, unifiedKeyInfoLiveData).observe(getViewLifecycleOwner(), this::onLoadKeyMetadata);
model.getSubkeyStatus(context, unifiedKeyInfoLiveData).observe(getViewLifecycleOwner(), this::onLoadSubkeyStatus);
}
private void onLoadSubkeyStatus(KeySubkeyStatus subkeyStatus) {
@ -274,16 +265,6 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
identitiesAdapter.setData(identityInfos);
}
private void onLoadSystemContact(SystemContactInfo systemContactInfo) {
if (systemContactInfo == null) {
systemContactCard.hideLinkedSystemContact();
return;
}
systemContactCard.showLinkedSystemContact(systemContactInfo.contactName, systemContactInfo.contactPicture);
systemContactCard.setSystemContactClickListener((v) -> launchAndroidContactActivity(systemContactInfo.contactId));
}
private void onLoadKeyMetadata(KeyMetadata keyMetadata) {
if (keyMetadata == null) {
keyserverStatusView.setDisplayStatusUnknown();
@ -299,14 +280,6 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
}
}
public void switchToFragment(final Fragment frag, final String backStackName) {
new Handler().post(() -> requireFragmentManager().beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.view_key_fragment, frag)
.addToBackStack(backStackName)
.commit());
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// if a result has been returned, display a notify
@ -320,7 +293,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
public void showDialogFragment(final DialogFragment dialogFragment, final String tag) {
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(
() -> dialogFragment.show(requireFragmentManager(), tag));
() -> dialogFragment.show(getParentFragmentManager(), tag));
}
public void showContextMenu(int position, View anchor) {
@ -349,11 +322,4 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
return false;
}
private void launchAndroidContactActivity(long contactId) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactId));
intent.setData(uri);
startActivity(intent);
}
}

View file

@ -1,137 +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.ui.keyview.loader;
import java.util.List;
import android.Manifest;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.ContactsContract;
import androidx.core.content.ContextCompat;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.ContactHelper;
import timber.log.Timber;
public class SystemContactDao {
private static final String[] PROJECTION = {
ContactsContract.RawContacts.CONTACT_ID
};
private static final int INDEX_CONTACT_ID = 0;
private static final String CONTACT_NOT_DELETED = "0";
private final Context context;
private final ContentResolver contentResolver;
private final ContactHelper contactHelper;
public static SystemContactDao getInstance(Context context) {
ContactHelper contactHelper = new ContactHelper(context);
ContentResolver contentResolver = context.getContentResolver();
return new SystemContactDao(context, contactHelper, contentResolver);
}
private SystemContactDao(Context context, ContactHelper contactHelper, ContentResolver contentResolver) {
this.context = context;
this.contactHelper = contactHelper;
this.contentResolver = contentResolver;
}
public SystemContactInfo getSystemContactInfo(long masterKeyId, boolean isSecret) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_DENIED) {
Timber.w(Constants.TAG, "loading linked system contact not possible READ_CONTACTS permission denied!");
return null;
}
Uri baseUri = isSecret ? ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI :
ContactsContract.RawContacts.CONTENT_URI;
Cursor cursor = contentResolver.query(baseUri, PROJECTION,
ContactsContract.RawContacts.ACCOUNT_TYPE + " = ? AND " +
ContactsContract.RawContacts.SOURCE_ID + " = ? AND " +
ContactsContract.RawContacts.DELETED + " = ?",
new String[] {
Constants.ACCOUNT_TYPE,
Long.toString(masterKeyId),
CONTACT_NOT_DELETED
}, null);
if (cursor == null) {
Timber.e("Error loading key items!");
return null;
}
try {
if (!cursor.moveToFirst()) {
return null;
}
long contactId = cursor.getLong(INDEX_CONTACT_ID);
if (contactId == -1) {
return null;
}
String contactName = null;
if (isSecret) { //all secret keys are linked to "me" profile in contacts
List<String> mainProfileNames = contactHelper.getMainProfileContactName();
if (mainProfileNames != null && mainProfileNames.size() > 0) {
contactName = mainProfileNames.get(0);
}
} else {
contactName = contactHelper.getContactName(contactId);
}
if (contactName == null) {
return null;
}
Bitmap contactPicture;
if (isSecret) {
contactPicture = contactHelper.loadMainProfilePhoto(false);
} else {
contactPicture = contactHelper.loadPhotoByContactId(contactId, false);
}
return new SystemContactInfo(masterKeyId, contactId, contactName, contactPicture);
} finally {
cursor.close();
}
}
public static class SystemContactInfo {
final long masterKeyId;
public final long contactId;
public final String contactName;
public final Bitmap contactPicture;
SystemContactInfo(long masterKeyId, long contactId, String contactName, Bitmap contactPicture) {
this.masterKeyId = masterKeyId;
this.contactId = contactId;
this.contactName = contactName;
this.contactPicture = contactPicture;
}
}
}

View file

@ -1,67 +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.ui.keyview.view;
import android.content.Context;
import android.graphics.Bitmap;
import androidx.cardview.widget.CardView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
public class SystemContactCardView extends CardView {
private LinearLayout vSystemContactLayout;
private ImageView vSystemContactPicture;
private TextView vSystemContactName;
public SystemContactCardView(Context context, AttributeSet attrs) {
super(context, attrs);
View view = LayoutInflater.from(context).inflate(R.layout.system_contact_card, this, true);
vSystemContactLayout = view.findViewById(R.id.system_contact_layout);
vSystemContactName = view.findViewById(R.id.system_contact_name);
vSystemContactPicture = view.findViewById(R.id.system_contact_picture);
}
public void setSystemContactClickListener(OnClickListener onClickListener) {
vSystemContactLayout.setOnClickListener(onClickListener);
}
public void hideLinkedSystemContact() {
setVisibility(View.GONE);
}
public void showLinkedSystemContact(String contactName, Bitmap picture) {
vSystemContactName.setText(contactName);
if (picture != null) {
vSystemContactPicture.setImageBitmap(picture);
} else {
vSystemContactPicture.setImageResource(R.drawable.ic_person_grey_48dp);
}
setVisibility(View.VISIBLE);
}
}

View file

@ -1,88 +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.ui.widget;
import android.content.Context;
import androidx.appcompat.widget.AppCompatAutoCompleteTextView;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import org.sufficientlysecure.keychain.util.ContactHelper;
public class EmailEditText extends AppCompatAutoCompleteTextView {
public EmailEditText(Context context) {
super(context);
init();
}
public EmailEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public EmailEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
reenableKeyboardSuggestions();
addTextChangedListener(textWatcher);
initAdapter();
}
TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable editable) {
}
};
private void initAdapter() {
setThreshold(1); // Start working from first character
setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item,
new ContactHelper(getContext()).getPossibleUserEmails()));
}
/**
* Hack to re-enable keyboard auto correction in AutoCompleteTextView.
* From http://stackoverflow.com/a/22512858
*/
private void reenableKeyboardSuggestions() {
int inputType = getInputType();
inputType &= ~EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
setRawInputType(inputType);
}
}

View file

@ -1,65 +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.ui.widget;
import android.content.Context;
import androidx.appcompat.widget.AppCompatAutoCompleteTextView;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import org.sufficientlysecure.keychain.util.ContactHelper;
public class NameEditText extends AppCompatAutoCompleteTextView {
public NameEditText(Context context) {
super(context);
init();
}
public NameEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public NameEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
reenableKeyboardSuggestions();
initAdapter();
}
private void initAdapter() {
setThreshold(1); // Start working from first character
setAdapter(new ArrayAdapter<>(
getContext(), android.R.layout.simple_spinner_dropdown_item,
new ContactHelper(getContext()).getPossibleUserNames()));
}
/**
* Hack to re-enable keyboard suggestions in AutoCompleteTextView.
* From http://stackoverflow.com/a/22512858
*/
private void reenableKeyboardSuggestions() {
int inputType = getInputType();
inputType &= ~EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
setRawInputType(inputType);
}
}

View file

@ -1,850 +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.util;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
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.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.Data;
import androidx.core.content.ContextCompat;
import android.util.Patterns;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.model.UserPacket.UserId;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import timber.log.Timber;
public class ContactHelper {
private final KeyRepository keyRepository;
private Context mContext;
private ContentResolver mContentResolver;
public ContactHelper(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
keyRepository = KeyRepository.create(context);
}
public List<String> getPossibleUserEmails() {
Set<String> accountMails = getAccountEmails();
accountMails.addAll(getMainProfileContactEmails());
// remove items that are not an email
Iterator<String> it = accountMails.iterator();
while (it.hasNext()) {
String email = it.next();
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
if (!emailMatcher.matches()) {
it.remove();
}
}
// now return the Set (without duplicates) as a List
return new ArrayList<>(accountMails);
}
public List<String> getPossibleUserNames() {
Set<String> accountMails = getAccountEmails();
Set<String> names = getContactNamesFromEmails(accountMails);
names.addAll(getMainProfileContactName());
// remove items that are an email
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String email = it.next();
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
if (emailMatcher.matches()) {
it.remove();
}
}
return new ArrayList<>(names);
}
/**
* Get emails from AccountManager
*/
private Set<String> getAccountEmails() {
final Account[] accounts = AccountManager.get(mContext).getAccounts();
final Set<String> emailSet = new HashSet<>();
for (Account account : accounts) {
emailSet.add(account.name);
}
return emailSet;
}
/**
* Search for contact names based on a list of emails (to find out the names of the
* device owner based on the email addresses from AccountsManager)
*/
private Set<String> getContactNamesFromEmails(Set<String> emails) {
if (!isContactsPermissionGranted()) {
return new HashSet<>();
}
Set<String> names = new HashSet<>();
for (String email : emails) {
Cursor profileCursor = mContentResolver.query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
new String[]{
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.Contacts.DISPLAY_NAME
},
ContactsContract.CommonDataKinds.Email.ADDRESS + "=?",
new String[]{email}, null
);
if (profileCursor == null) {
return null;
}
Set<String> currNames = new HashSet<>();
while (profileCursor.moveToNext()) {
String name = profileCursor.getString(1);
if (name != null) {
currNames.add(name);
}
}
profileCursor.close();
names.addAll(currNames);
}
return names;
}
/**
* Retrieves the emails of the primary profile contact
* http://developer.android.com/reference/android/provider/ContactsContract.Profile.html
*/
private Set<String> getMainProfileContactEmails() {
if (!isContactsPermissionGranted()) {
return new HashSet<>();
}
Cursor profileCursor = mContentResolver.query(
Uri.withAppendedPath(
ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY),
new String[]{
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY
},
// Selects only email addresses
ContactsContract.Contacts.Data.MIMETYPE + "=?",
new String[]{
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
},
// Show primary rows first. Note that there won't be a primary email address if the
// user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC"
);
if (profileCursor == null) {
return new HashSet<>();
}
Set<String> emails = new HashSet<>();
while (profileCursor.moveToNext()) {
String email = profileCursor.getString(0);
if (email != null) {
emails.add(email);
}
}
profileCursor.close();
return emails;
}
/**
* Retrieves the name of the primary profile contact
* http://developer.android.com/reference/android/provider/ContactsContract.Profile.html
*/
public List<String> getMainProfileContactName() {
if (!isContactsPermissionGranted()) {
return new ArrayList<>();
}
Cursor profileCursor = mContentResolver.query(
ContactsContract.Profile.CONTENT_URI,
new String[]{
ContactsContract.Profile.DISPLAY_NAME
},
null, null, null);
if (profileCursor == null) {
return new ArrayList<>();
}
Set<String> names = new HashSet<>();
// should only contain one entry!
while (profileCursor.moveToNext()) {
String name = profileCursor.getString(0);
if (name != null) {
names.add(name);
}
}
profileCursor.close();
return new ArrayList<>(names);
}
/**
* returns the CONTACT_ID of the main ("me") contact
* http://developer.android.com/reference/android/provider/ContactsContract.Profile.html
*/
private long getMainProfileContactId() {
Cursor profileCursor = mContentResolver.query(ContactsContract.Profile.CONTENT_URI,
new String[]{ContactsContract.Profile._ID}, null, null, null);
if (profileCursor != null && profileCursor.getCount() != 0 && profileCursor.moveToNext()) {
long contactId = profileCursor.getLong(0);
profileCursor.close();
return contactId;
} else {
if (profileCursor != null) {
profileCursor.close();
}
return -1;
}
}
/**
* loads the profile picture of the main ("me") contact
* http://developer.android.com/reference/android/provider/ContactsContract.Profile.html
*
* @param highRes true for large image if present, false for thumbnail
* @return bitmap of loaded photo
*/
public Bitmap loadMainProfilePhoto(boolean highRes) {
if (!isContactsPermissionGranted()) {
return null;
}
try {
long mainProfileContactId = getMainProfileContactId();
Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI,
Long.toString(mainProfileContactId));
InputStream photoInputStream = ContactsContract.Contacts.openContactPhotoInputStream(
mContentResolver,
contactUri,
highRes
);
if (photoInputStream == null) {
return null;
}
return BitmapFactory.decodeStream(photoInputStream);
} catch (Throwable ignored) {
return null;
}
}
public List<String> getContactMails() {
if (!isContactsPermissionGranted()) {
return new ArrayList<>();
}
Cursor mailCursor = mContentResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,
new String[]{ContactsContract.CommonDataKinds.Email.DATA},
null, null, null);
if (mailCursor == null) {
return new ArrayList<>();
}
Set<String> mails = new HashSet<>();
while (mailCursor.moveToNext()) {
String email = mailCursor.getString(0);
if (email != null) {
mails.add(email);
}
}
mailCursor.close();
return new ArrayList<>(mails);
}
public List<String> getContactNames() {
if (!isContactsPermissionGranted()) {
return new ArrayList<>();
}
Cursor cursor = mContentResolver.query(ContactsContract.Contacts.CONTENT_URI,
new String[]{ContactsContract.Contacts.DISPLAY_NAME},
null, null, null);
if (cursor == null) {
return new ArrayList<>();
}
Set<String> names = new HashSet<>();
while (cursor.moveToNext()) {
String name = cursor.getString(0);
if (name != null) {
names.add(name);
}
}
cursor.close();
return new ArrayList<>(names);
}
public Long masterKeyIdFromContactsDataUri(Uri contactUri) {
if (!isContactsPermissionGranted()) {
return null;
}
Cursor contactMasterKey = mContentResolver.query(contactUri,
new String[]{ContactsContract.Data.DATA2}, null, null, null);
if (contactMasterKey != null) {
try {
if (contactMasterKey.moveToNext()) {
return contactMasterKey.getLong(0);
}
} finally {
contactMasterKey.close();
}
}
return null;
}
/**
* returns the CONTACT_ID of the raw contact to which a masterKeyId is associated, if the
* raw contact has not been marked for deletion.
*
* @return CONTACT_ID (id of aggregated contact) linked to masterKeyId
*/
private long findContactId(long masterKeyId) {
long contactId = -1;
Cursor raw = mContentResolver.query(ContactsContract.RawContacts.CONTENT_URI,
new String[]{
ContactsContract.RawContacts.CONTACT_ID
},
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
ContactsContract.RawContacts.SOURCE_ID + "=? AND " +
ContactsContract.RawContacts.DELETED + "=?",
new String[]{//"0" for "not deleted"
Constants.ACCOUNT_TYPE,
Long.toString(masterKeyId),
"0"
}, null);
if (raw != null) {
if (raw.moveToNext()) {
contactId = raw.getLong(0);
}
raw.close();
}
return contactId;
}
/**
* Returns the display name of the system contact associated with contactId, null if the
* contact does not exist
*
* @return primary display name of system contact associated with contactId, null if it does
* not exist
*/
public String getContactName(long contactId) {
String contactName = null;
Cursor raw = mContentResolver.query(ContactsContract.Contacts.CONTENT_URI,
new String[]{
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
},
ContactsContract.Contacts._ID + "=?",
new String[]{//"0" for "not deleted"
Long.toString(contactId)
}, null);
if (raw != null) {
if (raw.moveToNext()) {
contactName = raw.getString(0);
}
raw.close();
}
return contactName;
}
public Bitmap loadPhotoByMasterKeyId(long masterKeyId, boolean highRes) {
if (!isContactsPermissionGranted()) {
return null;
}
if (masterKeyId == -1) {
return null;
}
try {
long contactId = findContactId(masterKeyId);
return loadPhotoByContactId(contactId, highRes);
} catch (Throwable ignored) {
return null;
}
}
public Bitmap loadPhotoByContactId(long contactId, boolean highRes) {
if (!isContactsPermissionGranted()) {
return null;
}
if (contactId == -1) {
return null;
}
Uri contactUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
// older android versions (tested on API level 15) fail on lookupuris being passed to
// openContactPhotoInputStream
// http://stackoverflow.com/a/21214524/3000919
// Uri lookupUri = ContactsContract.Contacts.getLookupUri(contentResolver, contactUri);
// Also, we don't need a permanent shortcut to the contact since we load it afresh each time
InputStream photoInputStream = ContactsContract.Contacts.openContactPhotoInputStream(
mContentResolver,
contactUri,
highRes);
if (photoInputStream == null) {
return null;
}
return BitmapFactory.decodeStream(photoInputStream);
}
/**
* Write/Update the current OpenKeychain keys to the contact db
*/
public void writeKeysToContacts() {
if (Constants.DEBUG_SYNC_REMOVE_CONTACTS) {
deleteAllContacts();
}
writeKeysToMainProfileContact();
writeKeysToNormalContacts();
}
private boolean isContactsPermissionGranted() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_GRANTED) {
return true;
}
Timber.w("READ_CONTACTS permission denied!");
return false;
}
private void writeKeysToNormalContacts() {
// delete raw contacts flagged for deletion by user so they can be reinserted
deleteFlaggedNormalRawContacts();
Set<Long> deletedKeys = getRawContactMasterKeyIds();
// Load all public Keys from OK
for (UnifiedKeyInfo keyInfo : keyRepository.getAllUnifiedKeyInfo()) {
if (keyInfo.has_any_secret()) {
continue;
}
long masterKeyId = keyInfo.master_key_id();
String name = keyInfo.name();
deletedKeys.remove(masterKeyId);
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
// Do not store expired or revoked or unverified keys in contact db - and
// remove them if they already exist. Secret keys do not reach this point
if (keyInfo.is_expired() || keyInfo.is_revoked() || !keyInfo.is_verified()) {
Timber.d("Expired or revoked or unverified: Deleting masterKeyId "
+ masterKeyId);
if (masterKeyId != -1) {
deleteRawContactByMasterKeyId(masterKeyId);
}
} else {
if (name != null) {
// get raw contact to this master key id
long rawContactId = findRawContactId(masterKeyId);
Timber.d("rawContactId: " + rawContactId);
// Create a new rawcontact with corresponding key if it does not exist yet
if (rawContactId == -1) {
Timber.d("Insert new raw contact with masterKeyId " + masterKeyId);
insertContact(ops, masterKeyId);
writeContactKey(ops, rawContactId, masterKeyId, name);
}
// We always update the display name (which is derived from primary user id)
// and email addresses from user id
writeContactDisplayName(ops, rawContactId, name);
writeContactEmail(ops, rawContactId, masterKeyId);
try {
mContentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
Timber.w(e);
}
}
}
}
// Delete master key ids that are no longer present in OK
for (Long masterKeyId : deletedKeys) {
Timber.d("Delete raw contact with masterKeyId " + masterKeyId);
deleteRawContactByMasterKeyId(masterKeyId);
}
}
/**
* Links all keys with secrets to the main ("me") contact
* http://developer.android.com/reference/android/provider/ContactsContract.Profile.html
*/
private void writeKeysToMainProfileContact() {
// deletes contacts hidden by the user so they can be reinserted if necessary
deleteFlaggedMainProfileRawContacts();
Set<Long> keysToDelete = getMainProfileMasterKeyIds();
// get all keys which have associated secret keys
// TODO: figure out why using selectionArgs does not work in this case
for (UnifiedKeyInfo keyInfo : keyRepository.getAllUnifiedKeyInfoWithSecret()) {
long masterKeyId = keyInfo.master_key_id();
if (!keyInfo.is_expired() && !keyInfo.is_revoked() && keyInfo.name() != null) {
// if expired or revoked will not be removed from keysToDelete or inserted
// into main profile ("me" contact)
boolean existsInMainProfile = keysToDelete.remove(masterKeyId);
if (!existsInMainProfile) {
long rawContactId = -1;//new raw contact
Timber.d("masterKeyId with secret " + masterKeyId);
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
insertMainProfileRawContact(ops, masterKeyId);
writeContactKey(ops, rawContactId, masterKeyId, keyInfo.name());
try {
mContentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
Timber.w(e);
}
}
}
}
for (long masterKeyId : keysToDelete) {
deleteMainProfileRawContactByMasterKeyId(masterKeyId);
Timber.d("Delete main profile raw contact with masterKeyId " + masterKeyId);
}
}
/**
* Inserts a raw contact into the table defined by ContactsContract.Profile
* http://developer.android.com/reference/android/provider/ContactsContract.Profile.html
*/
private void insertMainProfileRawContact(ArrayList<ContentProviderOperation> ops,
long masterKeyId) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE)
.withValue(ContactsContract.RawContacts.SOURCE_ID, Long.toString(masterKeyId))
.build());
}
/**
* deletes a raw contact from the main profile table ("me" contact)
* http://developer.android.com/reference/android/provider/ContactsContract.Profile.html
*
* @return number of rows deleted
*/
private int deleteMainProfileRawContactByMasterKeyId(long masterKeyId) {
// CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise
// would be just flagged for deletion
Uri deleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon().
appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
return mContentResolver.delete(deleteUri,
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
ContactsContract.RawContacts.SOURCE_ID + "=?",
new String[]{
Constants.ACCOUNT_TYPE, Long.toString(masterKeyId)
});
}
/**
* deletes all raw contact entries in the "me" contact flagged for deletion ('hidden'),
* presumably by the user
*
* @return number of raw contacts deleted
*/
private int deleteFlaggedMainProfileRawContacts() {
// CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise
// would be just flagged for deletion
Uri deleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon().
appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
return mContentResolver.delete(deleteUri,
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
ContactsContract.RawContacts.DELETED + "=?",
new String[]{
Constants.ACCOUNT_TYPE,
"1"
});
}
/**
* Delete all raw contacts associated to OpenKeychain, including those from "me" contact
* defined by ContactsContract.Profile
*
* @return number of rows deleted
*/
public int deleteAllContacts() {
// CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise
// would be just flagged for deletion
Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().
appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
Timber.d("Deleting all raw contacts associated to OK...");
int delete = mContentResolver.delete(deleteUri,
ContactsContract.RawContacts.ACCOUNT_TYPE + "=?",
new String[]{
Constants.ACCOUNT_TYPE
});
Uri mainProfileDeleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon()
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
delete += mContentResolver.delete(mainProfileDeleteUri,
ContactsContract.RawContacts.ACCOUNT_TYPE + "=?",
new String[]{
Constants.ACCOUNT_TYPE
});
return delete;
}
/**
* Deletes raw contacts from ContactsContract.RawContacts based on masterKeyId. Does not
* delete contacts from the "me" contact defined in ContactsContract.Profile
*
* @return number of rows deleted
*/
private int deleteRawContactByMasterKeyId(long masterKeyId) {
// CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise
// would be just flagged for deletion
Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().
appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
return mContentResolver.delete(deleteUri,
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
ContactsContract.RawContacts.SOURCE_ID + "=?",
new String[]{
Constants.ACCOUNT_TYPE, Long.toString(masterKeyId)
});
}
private int deleteFlaggedNormalRawContacts() {
// CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise
// would be just flagged for deletion
Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().
appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
return mContentResolver.delete(deleteUri,
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
ContactsContract.RawContacts.DELETED + "=?",
new String[]{
Constants.ACCOUNT_TYPE,
"1"
});
}
/**
* @return a set of all key master key ids currently present in the contact db
*/
private Set<Long> getRawContactMasterKeyIds() {
HashSet<Long> result = new HashSet<>();
Cursor masterKeyIds = mContentResolver.query(ContactsContract.RawContacts.CONTENT_URI,
new String[]{
ContactsContract.RawContacts.SOURCE_ID
},
ContactsContract.RawContacts.ACCOUNT_TYPE + "=?",
new String[]{
Constants.ACCOUNT_TYPE
}, null);
if (masterKeyIds != null) {
while (masterKeyIds.moveToNext()) {
result.add(masterKeyIds.getLong(0));
}
masterKeyIds.close();
}
return result;
}
/**
* @return a set of all key master key ids currently present in the contact db
*/
private Set<Long> getMainProfileMasterKeyIds() {
HashSet<Long> result = new HashSet<>();
Cursor masterKeyIds = mContentResolver.query(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI,
new String[]{
ContactsContract.RawContacts.SOURCE_ID
},
ContactsContract.RawContacts.ACCOUNT_TYPE + "=?",
new String[]{
Constants.ACCOUNT_TYPE
}, null);
if (masterKeyIds != null) {
while (masterKeyIds.moveToNext()) {
result.add(masterKeyIds.getLong(0));
}
masterKeyIds.close();
}
return result;
}
/**
* This will search the contact db for a raw contact with a given master key id
*
* @return raw contact id or -1 if not found
*/
private long findRawContactId(long masterKeyId) {
long rawContactId = -1;
Cursor raw = mContentResolver.query(ContactsContract.RawContacts.CONTENT_URI,
new String[]{
ContactsContract.RawContacts._ID
},
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?",
new String[]{
Constants.ACCOUNT_TYPE, Long.toString(masterKeyId)
}, null);
if (raw != null) {
if (raw.moveToNext()) {
rawContactId = raw.getLong(0);
}
raw.close();
}
return rawContactId;
}
/**
* Creates a empty raw contact with a given masterKeyId
*/
private void insertContact(ArrayList<ContentProviderOperation> ops, long masterKeyId) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE)
.withValue(ContactsContract.RawContacts.SOURCE_ID, Long.toString(masterKeyId))
.build());
}
/**
* Adds a key id to the given raw contact.
* <p/>
* This creates the link to OK in contact details
*/
private void writeContactKey(ArrayList<ContentProviderOperation> ops, long rawContactId,
long masterKeyId, String keyName) {
ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), rawContactId)
.withValue(ContactsContract.Data.MIMETYPE, Constants.CUSTOM_CONTACT_DATA_MIME_TYPE)
.withValue(ContactsContract.Data.DATA1, mContext.getString(R.string.contact_show_key, keyName))
.withValue(ContactsContract.Data.DATA2, masterKeyId)
.build());
}
/**
* Write all known email addresses of a key (derived from user ids) to a given raw contact
*/
private void writeContactEmail(ArrayList<ContentProviderOperation> ops,
long rawContactId, long masterKeyId) {
ContentProviderOperation deleteEmailOp = selectByRawContactAndItemType(
ContentProviderOperation.newDelete(Data.CONTENT_URI), rawContactId, Email.CONTENT_ITEM_TYPE).build();
ops.add(deleteEmailOp);
ContentProviderOperation deleteJabberOp = selectByRawContactAndItemType(
ContentProviderOperation.newDelete(Data.CONTENT_URI), rawContactId, Im.CONTENT_ITEM_TYPE).build();
ops.add(deleteJabberOp);
for (UserId userId : keyRepository.getUserIds(masterKeyId)) {
if (userId.email() != null) {
ContentProviderOperation.Builder builder = referenceRawContact(
ContentProviderOperation.newInsert(Data.CONTENT_URI), rawContactId);
if (userId.email().startsWith("xmpp:")) {
builder = builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE)
.withValue(Im.PROTOCOL, Im.PROTOCOL_JABBER)
.withValue(Im.DATA, userId.email().substring(5));
} else {
builder = builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.DATA, userId.email());
}
ops.add(builder.build());
}
}
}
private void writeContactDisplayName(ArrayList<ContentProviderOperation> ops, long rawContactId,
String displayName) {
if (displayName != null) {
ops.add(insertOrUpdateForRawContact(ContactsContract.Data.CONTENT_URI, rawContactId,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
.build());
}
}
private ContentProviderOperation.Builder referenceRawContact(ContentProviderOperation.Builder builder,
long rawContactId) {
return rawContactId == -1 ?
builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) :
builder.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
}
private ContentProviderOperation.Builder insertOrUpdateForRawContact(Uri uri, long rawContactId,
String itemType) {
if (rawContactId == -1) {
return referenceRawContact(ContentProviderOperation.newInsert(uri), rawContactId).withValue(
ContactsContract.Data.MIMETYPE, itemType);
} else {
return selectByRawContactAndItemType(ContentProviderOperation.newUpdate(uri), rawContactId, itemType);
}
}
private ContentProviderOperation.Builder selectByRawContactAndItemType(
ContentProviderOperation.Builder builder, long rawContactId, String itemType) {
return builder.withSelection(
ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?",
new String[]{
Long.toString(rawContactId), itemType
});
}
}

View file

@ -359,30 +359,6 @@ public class Preferences {
mSharedPreferences.edit().putString(Pref.SYNC_WORK_UUID, value).apply();
}
public boolean isAnalyticsAskedPolitely() {
return mSharedPreferences.getBoolean(Pref.KEY_ANALYTICS_ASKED_POLITELY, false);
}
public void setAnalyticsAskedPolitely() {
mSharedPreferences.edit().putBoolean(Pref.KEY_ANALYTICS_ASKED_POLITELY, true).apply();
}
public boolean isAnalyticsHasConsent() {
return mSharedPreferences.getBoolean(Pref.KEY_ANALYTICS_CONSENT, false);
}
public void setAnalyticsGotUserConsent(boolean hasUserConsent) {
mSharedPreferences.edit().putBoolean(Pref.KEY_ANALYTICS_CONSENT, hasUserConsent).apply();
}
public void setAnalyticsLastAskedNow() {
mSharedPreferences.edit().putLong(Pref.KEY_ANALYTICS_LAST_ASKED, System.currentTimeMillis()).apply();
}
public long getAnalyticsLastAsked() {
return mSharedPreferences.getLong(Pref.KEY_ANALYTICS_LAST_ASKED, 0);
}
@AutoValue
public static abstract class CloudSearchPrefs implements Parcelable {
public abstract boolean isKeyserverEnabled();

View file

@ -14,13 +14,14 @@
android:text="@string/create_key_add_email_text"
android:textAppearance="?android:textAppearanceMedium" />
<org.sufficientlysecure.keychain.ui.widget.EmailEditText
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/add_email_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="@string/label_email"
android:imeOptions="actionNext"
android:inputType="textEmailAddress"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>

View file

@ -8,15 +8,16 @@
android:paddingLeft="24dp"
android:paddingRight="24dp">
<org.sufficientlysecure.keychain.ui.widget.EmailEditText
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/add_user_id_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/label_email"
android:imeOptions="actionNext"
android:inputType="textEmailAddress"
android:textAppearance="?android:attr/textAppearanceMedium" />
<org.sufficientlysecure.keychain.ui.widget.NameEditText
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/add_user_id_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -25,13 +25,14 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/create_key_email_text" />
<org.sufficientlysecure.keychain.ui.widget.EmailEditText
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/create_key_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:imeOptions="actionNext"
android:inputType="textEmailAddress"
android:hint="@string/label_email"
android:ems="10" />

View file

@ -24,7 +24,7 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/create_key_name_text" />
<org.sufficientlysecure.keychain.ui.widget.NameEditText
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/create_key_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/shop_webView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ProgressBar
android:id="@+id/shop_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="@+id/shop_webView"
app:layout_constraintEnd_toEndOf="@+id/shop_webView"
app:layout_constraintStart_toStartOf="@+id/shop_webView"
app:layout_constraintTop_toTopOf="@+id/shop_webView" />
<TextView
android:id="@+id/shop_progressbar_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/shop_loading_label"
app:layout_constraintEnd_toEndOf="@+id/shop_progressbar"
app:layout_constraintStart_toStartOf="@+id/shop_progressbar"
app:layout_constraintTop_toBottomOf="@+id/shop_progressbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -34,37 +34,6 @@
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax">
<FrameLayout
android:id="@+id/view_key_photo_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="false"
android:fitsSystemWindows="true"
android:visibility="gone">
<ImageView
android:id="@+id/view_key_photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="false"
android:baselineAlignBottom="false"
android:cropToPadding="false"
android:fitsSystemWindows="true"
android:focusable="false"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher" />
<!-- text protection scrim -->
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:background="@drawable/scrim_bottom" />
</FrameLayout>
<TextView
android:id="@+id/view_key_status"
android:layout_width="match_parent"

View file

@ -1,6 +1,5 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@ -63,16 +62,4 @@
card_view:cardUseCompatPadding="true"
/>
<org.sufficientlysecure.keychain.ui.keyview.view.SystemContactCardView
android:id="@+id/linked_system_contact_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
card_view:cardBackgroundColor="?attr/colorCardViewBackground"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true"
tools:visibility="visible" />
</LinearLayout>

View file

@ -1,128 +0,0 @@
able
angry
bad
bent
bitter
black
blue
boiling
bright
broken
brown
certain
cheap
clean
clear
cold
common
complex
cruel
dark
dead
dear
deep
dirty
dry
early
elastic
equal
false
fat
feeble
female
fertile
first
fixed
flat
foolish
free
full
future
general
good
great
green
grey
hanging
happy
hard
healthy
high
hollow
kind
last
late
lazy
left
like
living
long
loose
loud
low
male
married
medical
mixed
narrow
natural
new
normal
old
open
past
poor
present
pretty
private
public
quick
quiet
ready
rare
red
regular
right
rough
round
sad
safe
same
second
secret
serious
sharp
short
shut
sick
simple
slow
small
smooth
soft
solid
sour
special
sticky
stiff
strange
strong
sudden
sweet
tall
thick
thin
tight
tired
true
unknown
violent
waiting
warm
wet
white
wide
wise
wrong
yellow
young

View file

@ -1,64 +0,0 @@
ably
angrily
badly
bitterly
brightly
brokenly
cheaply
clearly
coldly
commonly
cruelly
darkly
dearly
deeply
drily
equally
falsely
feebly
fixedly
flatly
freely
fully
greatly
happily
hardly
kindly
lately
lazily
loosely
loudly
narrowly
newly
normally
openly
poorly
prettily
publicly
quickly
quietly
readily
rarely
roughly
sadly
safely
secretly
sharply
simply
slowly
smoothly
softly
solidly
sourly
stiffly
strongly
suddenly
sweetly
thickly
thinly
tightly
tiredly
truly
warmly
widely
wisely

View file

@ -1,8 +0,0 @@
her
his
my
our
that
the
this
your

View file

@ -1,512 +0,0 @@
account
air
amount
angle
animal
answer
ant
apple
arch
arm
army
attack
attempt
baby
back
bag
ball
band
base
basin
basket
bath
bed
bee
belief
bell
berry
bird
birth
bite
blade
blood
blow
board
boat
body
bone
book
boot
bottle
box
boy
brain
brake
branch
bread
breath
brick
bridge
brother
brush
bucket
bulb
burn
butter
button
cake
camera
canvas
car
card
cat
cause
chain
chalk
chance
change
cheese
chest
chin
church
circle
clock
cloth
cloud
coal
coat
collar
colour
comb
comfort
company
control
cook
copper
copy
cord
cork
cotton
cough
country
cover
cow
crack
credit
crime
crush
cry
cup
current
curtain
curve
cushion
damage
danger
day
debt
degree
design
desire
detail
disease
disgust
dog
door
doubt
drain
drawer
dress
drink
drop
dust
ear
earth
edge
effect
egg
end
engine
error
event
example
expert
eye
face
fact
fall
family
farm
father
fear
feather
feeling
fiction
field
fight
finger
fire
fish
flag
flame
flight
floor
flower
fly
fold
food
foot
force
fork
form
fowl
frame
friend
front
fruit
garden
girl
glass
glove
goat
gold
grain
grass
grip
group
growth
guide
gun
hair
hammer
hand
harbour
harmony
hat
hate
head
heart
heat
history
hole
hook
hope
horn
horse
hour
house
humour
ice
idea
impulse
ink
insect
iron
island
jelly
jewel
join
journey
judge
jump
kettle
key
kick
kiss
knee
knife
knot
land
laugh
law
lead
leaf
leather
leg
letter
level
library
lift
light
limit
line
linen
lip
liquid
list
lock
look
loss
love
machine
man
manager
map
mark
market
mass
match
meal
measure
meat
meeting
memory
metal
middle
milk
mind
mine
minute
mist
money
monkey
month
moon
morning
mother
motion
mouth
move
muscle
music
nail
name
nation
neck
need
needle
nerve
net
news
night
noise
nose
note
number
nut
offer
office
oil
opinion
orange
order
oven
owner
page
pain
paint
paper
parcel
part
paste
payment
peace
pen
pencil
person
picture
pig
pin
pipe
place
plane
plant
plate
play
plough
pocket
point
poison
polish
porter
pot
potato
powder
power
price
print
prison
process
produce
profit
prose
protest
pull
pump
purpose
push
quality
rail
rain
range
rat
rate
ray
reason
receipt
record
regret
request
respect
rest
reward
rhythm
rice
ring
river
road
rod
roll
roof
room
root
rub
rule
run
sail
salt
sand
scale
school
science
screw
sea
seat
seed
self
sense
servant
sex
shade
shake
shame
sheep
shelf
ship
shirt
shock
shoe
side
sign
silk
silver
sister
size
skin
skirt
sky
sleep
slip
slope
smash
smell
smile
smoke
snake
sneeze
snow
soap
society
sock
son
song
sort
sound
soup
space
spade
sponge
spoon
spring
square
stage
stamp
star
start
station
steam
steel
stem
step
stick
stitch
stomach
stone
stop
store
story
street
stretch
sugar
summer
sun
support
swim
system
table
tail
talk
taste
tax
test
theory
thing
thought
thread
throat
thumb
thunder
ticket
time
tin
toe
tongue
tooth
top
touch
town
trade
train
tray
tree
trick
trouble
turn
twist
unit
value
verse
vessel
view
voice
walk
wall
war
wash
waste
watch
water
wave
wax
way
weather
week
weight
wheel
whip
whistle
wind
window
wine
wing
winter
wire
woman
wood
wool
word
work
worm
wound
writing
year

View file

@ -1,32 +0,0 @@
above
across
after
against
along
among
around
at
before
behind
below
beneath
beside
between
beyond
by
from
in
inside
into
near
on
outside
over
past
round
through
to
towards
under
upon
with

View file

@ -1,128 +0,0 @@
agrees
allows
answers
arrives
asks
is
becomes
begins
believes
brings
burns
buys
calls
changes
chooses
cleans
closes
comes
compares
continues
cooks
costs
counts
cries
cuts
dances
decides
describes
destroys
dies
does
drinks
drives
eats
ends
explains
falls
feels
fights
fills
finds
finishes
forgets
forgives
gets
gives
goes
grows
hates
has
hears
helps
hides
holds
hurts
improves
jumps
keeps
kills
knows
laughs
learns
leaves
lets
lies
listens
lives
looks
loses
loves
makes
meets
moves
needs
occurs
offers
opens
pays
plays
prefers
prepares
presses
promises
pulls
pushes
puts
reads
receives
remembers
repeats
rests
returns
runs
sees
sells
sends
shouts
shows
sings
sits
sleeps
smiles
speaks
starts
stays
stops
studies
suggests
supports
takes
talks
teaches
tells
thinks
throws
touches
travels
treats
tries
turns
uses
visits
walks
wants
washes
wins
works
writes

View file

@ -1,128 +0,0 @@
agrees with
allows
answers
arrives at
asks
is
becomes
begins
believes
brings
burns
buys
calls
changes
chooses
cleans
closes
comes to
compares
continues
cooks
costs
counts
cries for
cuts
dances with
decides on
describes
destroys
dies for
does
drinks
drives
eats
ends
explains
falls on
feels
fights
fills
finds
finishes
forgets
forgives
gets
gives
goes to
grows
hates
has
hears
helps
hides
holds
hurts
improves
jumps over
keeps
kills
knows
laughs at
learns
leaves
lets
lies to
listens to
lives with
looks at
loses
loves
makes
meets
moves
needs
occurs to
offers
opens
pays
plays
prefers
prepares
presses
promises
pulls
pushes
puts
reads
receives
remembers
repeats
rests by
returns
runs to
sees
sells
sends
shouts at
shows
sings to
sits by
sleeps by
smiles at
speaks to
starts
stays with
stops
studies
suggests
supports
takes
talks to
teaches
tells
thinks of
throws
touches
travels to
treats
tries
turns
uses
visits
walks to
wants
washes
wins
works for
writes to

View file

@ -178,8 +178,7 @@
<string name="label_sync_settings_contacts_title">ربط المفاتيح بجهات الإتصال</string>
<string name="label_sync_settings_contacts_summary_off">لن يتم ربط المفاتيح الجديدة بجهات الإتصال</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">تحديثات تلقائية للمفاتيح</string>
<string name="label_experimental_settings_desc_title">تحذير</string>
<string name="label_experimental_settings_desc_title">تحذير</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">فعّل تور</string>
<string name="pref_proxy_tor_summary">يتطلب نتصيب تطبيق أوربوت</string>
@ -686,7 +685,5 @@
<string name="notify_content_keysync">مفتاح %d / %d</string>
<string name="keylist_header_anonymous">مجهول</string>
<string name="keylist_header_special">#</string>
<string name="button_analytics_no">لا، شكراً</string>
<string name="snackbutton_analytics_settings">الإعدادات</string>
<string name="share_key">شارك المفتاح</string>
</resources>

View file

@ -167,6 +167,4 @@
<string name="transfer_confirm_ok">Kas</string>
<string name="token_action_import">Emporzhiañ</string>
<string name="keylist_header_anonymous">Anonymous</string>
<string name="button_analytics_no">Nann, avat</string>
<string name="snackbutton_analytics_settings">Arventennoù</string>
</resources>

View file

@ -208,13 +208,10 @@
<string name="label_sync_settings_contacts_summary_on">Enllaça claus amb contactes basant-se amb noms i adreces de correu. Això succeeix completament fora de línia en el vostre dispositiu.</string>
<string name="label_sync_settings_contacts_summary_off">Les noves claus no s\'enllaçaran amb els contactes</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Actualització de claus automàticament</string>
<string name="label_experimental_settings_desc_title">Advertència</string>
<string name="label_experimental_settings_keybase_title">Proves de Keybase.io</string>
<string name="label_experimental_settings_keybase_summary">Contacta amb keybase.io per proves de clau i mostra-les cada vegada que es mostri una clau</string>
<string name="label_experimental_settings_theme_summary">(Les icones i moltes pantalles no estan encara ben ajustades per aquest tema fosc)</string>
<string name="label_settings_analytics_title">Permet estadístiques d\'ús anònim</string>
<string name="label_settings_analytics_summary">Si està habilitat, envia estadístiques d\'ús anònim per ajudar en la millora de la app</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Activa Tor</string>
<string name="pref_proxy_tor_summary">Requereix Orbot per a ser instal·lat</string>
@ -700,8 +697,6 @@
<string name="my_keys">Les meves claus</string>
<string name="nav_backup">Còpia de seguretat/Restaura</string>
<string name="nav_transfer">Transferència Wifi segura</string>
<string name="nav_shop">Botiga</string>
<string name="nav_autocrypt">Proveu Autocrypt amb Thunderbird</string>
<!--hints-->
<string name="encrypt_content_edit_text_hint">Escriviu text</string>
<!--certs-->
@ -822,6 +817,4 @@
<string name="snack_keylist_clipboard_action">Visualitza</string>
<string name="notify_title_keysync">Actualitzant claus...</string>
<string name="keylist_header_anonymous">Anònim </string>
<string name="button_analytics_no">No, gràcies</string>
<string name="snackbutton_analytics_settings">Configuració</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Protojit klíče s kontakty na základě jmen a emailových adres. Odehrává se kompletně offline na vašem zařízení.</string>
<string name="label_sync_settings_contacts_summary_off">Nové klíče nebudou propojeny s kontakty</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Automaticky aktualizovat klíč</string>
<string name="label_experimental_settings_desc_title">Varovnání</string>
<string name="label_experimental_settings_desc_summary">Tyto funkce nejsou ještě dokončené nebo neprošli uživatelským testováním/bezpečnostním auditem. Prosím, nespoléhejte na jejich zabezpečení a nehlašte problémy s nimi spojené, pokud na ně narazíte.</string>
<string name="label_experimental_settings_keybase_title">Keybase.io Proofs</string>
<string name="label_experimental_settings_keybase_summary">Kontaktovat keybase.io pro proofs klíče a ukázat je pokaždé když je klíč zobrazen</string>
<string name="label_experimental_settings_theme_summary">(Ikony a mnoho obrazovek ještě nejsou uzpůsobené pro temné téma vzhledu)</string>
<string name="label_settings_analytics_title">Povolit anonymní statistiku využití</string>
<string name="label_settings_analytics_summary">Pokud povoleno, odešle anonymní statistiku o využívání za účelem vylepšení applikace</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Zapnout Tor</string>
<string name="pref_proxy_tor_summary">Vyžaduje instalovaný Orbot </string>
@ -816,8 +813,6 @@
<string name="my_keys">Moje Klíče</string>
<string name="nav_backup">Záloha/Obnova</string>
<string name="nav_transfer">Bezpečný přenos přes WiFi</string>
<string name="nav_shop">Obchod</string>
<string name="nav_autocrypt">Vyzkoušejte Autocrypt s Thunderbird</string>
<!--hints-->
<string name="encrypt_content_edit_text_hint">Napsat text</string>
<!--certs-->
@ -1874,12 +1869,6 @@
<string name="keylist_item_key_id">Klíč ID: %s</string>
<string name="keylist_header_anonymous">Anonymní</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">K zlepšení zkušenosti všech uživatelů, smí OpenKeychain sbírat anonymní statistiku o používání?\n\nVíce informací v <a href="https://openkeychain.org/help/privacy-policy">Ochraně údajů</a>.</string>
<string name="button_analytics_yes">Ano, chci pomoci!</string>
<string name="button_analytics_no">Ne, děkuji</string>
<string name="snack_analytics_accept">Díky za pomoc! Tuto volbu můžete změnit v nastavení.</string>
<string name="snack_analytics_reject">To je v pořádku, už se nezeptáme. Své rozhodnutí mužete změnit v nastavení.</string>
<string name="snackbutton_analytics_settings">Nastavení</string>
<string name="subkey_action_create">Založím podklíč</string>
<string name="subkey_action_revoke">Podklíč bude zrušen</string>
<string name="subkey_action_strip">Podklíč bude vyjmut</string>
@ -1887,6 +1876,4 @@
<string name="subkey_action_expiry_date">Doba platnosti se změni do %s</string>
<string name="share_key_clipboard">Sdílet klíč skrze schránku</string>
<string name="share_key">Sdílet klíč</string>
<string name="shop_loading_label">Otevírám obchod...</string>
<string name="shop_title">COTECH Security Keys</string>
</resources>

View file

@ -227,5 +227,4 @@
<string name="key_gen_back">Tilbage</string>
<string name="key_gen_finish">Afslut</string>
<string name="snack_keylist_clipboard_action">Gennemse</string>
<string name="snackbutton_analytics_settings">Indstillinger</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Schlüssel basierend auf Namen und E-Mail-Adressen mit Kontakten verknüpfen. Das alles findet komplett offline auf deinem Gerät statt.</string>
<string name="label_sync_settings_contacts_summary_off">Neue Schlüssel werden nicht mit Kontakten verknüpft</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Automatische Schlüsselaktualisierung</string>
<string name="label_experimental_settings_desc_title">Warnung</string>
<string name="label_experimental_settings_desc_summary">Diese Funktionen sind noch nicht final oder das Ergebnis von Benutzererfahrungs-/Sicherheitsuntersuchungen. Verlasse dich daher nicht auf deren Sicherheit und melde uns bitte keine auftretenden Probleme!</string>
<string name="label_experimental_settings_keybase_title">Keybase.io-Nachweise</string>
<string name="label_experimental_settings_keybase_summary">Keybase.io für Schlüsselnachweise kontaktieren und diese jedesmal zeigen, wenn ein Schlüssel angezeigt wird</string>
<string name="label_experimental_settings_theme_summary">(Die Symbole und viele Bildschirme sind noch nicht an das dunkle Design angepasst)</string>
<string name="label_settings_analytics_title">Erlaube anonyme Benutzungsstatistiken</string>
<string name="label_settings_analytics_summary">Wenn aktiviert, sendet anonyme Benutzungsstatistiken, um dabei zu helfen, die App zu verbessern</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Tor aktivieren</string>
<string name="pref_proxy_tor_summary">Orbot muss installiert sein</string>
@ -780,8 +777,6 @@
<string name="my_keys">Meine Schlüssel</string>
<string name="nav_backup">Backup/Wiederherstellung</string>
<string name="nav_transfer">Sichere WiFi Übertragung</string>
<string name="nav_shop">Shop</string>
<string name="nav_autocrypt">Autocrypt mit Thunderbird probieren</string>
<!--hints-->
<string name="encrypt_content_edit_text_hint">Text eingeben</string>
<!--certs-->
@ -1807,12 +1802,6 @@ Ein sicherer Schlüssel sollte eine Stärke von 2048 Bits besitzen.</string>
<string name="keylist_item_key_id">Key ID: %s</string>
<string name="keylist_header_anonymous">Anonym</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">Um die Erfahrung aller Benutzer zu verbessen, soll OpenKeychain anonyme Benutzungsstatistiken sammeln dürfen?\n\nUm mehr zu erfahren, schau in unsere<a href="https://openkeychain.org/help/privacy-policy">Privatsphärenrichtlinien</a>.</string>
<string name="button_analytics_yes">Ja, ich möchte helfen!</string>
<string name="button_analytics_no">Nein, danke</string>
<string name="snack_analytics_accept">Vielen Dank für die Hilfe! Du kannst deine Entscheidung in den Einstellungen ändern.</string>
<string name="snack_analytics_reject">Das ist in Ordnung, wir werden nicht erneut fragen. Du kannst deine Entscheidung in den Einstellungen ändern.</string>
<string name="snackbutton_analytics_settings">Einstellungen</string>
<string name="subkey_action_create">Teilschlüssel wird erstellt werden</string>
<string name="subkey_action_revoke">Teilschlüssel wird widerrufen werden</string>
<string name="subkey_action_strip">Teilschlüssel wird entfernt werden</string>
@ -1820,6 +1809,4 @@ Ein sicherer Schlüssel sollte eine Stärke von 2048 Bits besitzen.</string>
<string name="subkey_action_expiry_date">Ablauf wird geändert zu \"%s\"</string>
<string name="share_key_clipboard">Schlüssel per Zwischenablage teilen</string>
<string name="share_key">Schlüssel teilen</string>
<string name="shop_loading_label">Shop wird geladen...</string>
<string name="shop_title">COTECH Sicherheitsschlüssel</string>
</resources>

View file

@ -299,6 +299,4 @@
<string name="snack_keylist_clipboard_action">Δείτε τα αποτελέσματα</string>
<string name="keylist_header_anonymous">Ανώνυμος</string>
<string name="keylist_header_special">#</string>
<string name="button_analytics_no">Όχι, ευχαριστώ</string>
<string name="snackbutton_analytics_settings">Ρυθμίσεις</string>
</resources>

View file

@ -176,5 +176,4 @@
<string name="key_gen_back">Reen</string>
<string name="key_gen_finish">Fini</string>
<string name="snack_keylist_clipboard_action">Rigardi</string>
<string name="snackbutton_analytics_settings">Agordoj</string>
</resources>

View file

@ -210,8 +210,7 @@
<string name="label_sync_settings_contacts_summary_on">Vincular claves a contactos basándose en nombres y direcciones de correo electrónico. Esto ocurre sin conexión completamente en tu dispositivo.</string>
<string name="label_sync_settings_contacts_summary_off">Nuevas claves no serán vinculadas a contactos</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Actualizaciones automáticas de claves</string>
<string name="label_experimental_settings_desc_title">Advertencia</string>
<string name="label_experimental_settings_desc_title">Advertencia</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Activar Tor</string>
<string name="pref_proxy_tor_summary">Requiere Orbot instalado</string>
@ -434,5 +433,4 @@
<string name="select_identity_cancel">Desactivar</string>
<string name="key_gen_back">Atrás</string>
<string name="snack_keylist_clipboard_action">Ve</string>
<string name="snackbutton_analytics_settings">Ajustes</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Vincula claves con contactos basándose en nombres y direcciones de correo electrónico. Esto sucede con su dispositivo completamente desconectado.</string>
<string name="label_sync_settings_contacts_summary_off">Las claves nuevas no se vincularán a contactos</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Actualizaciones automáticas de claves</string>
<string name="label_experimental_settings_desc_title">Advertencia</string>
<string name="label_experimental_settings_desc_title">Advertencia</string>
<string name="label_experimental_settings_desc_summary">Estas características todavía no están finalizadas o no se han obtenido los resultados de experiencia del usuario y la investigación de seguridad. Por tanto, no confíe en su seguridad y, por favor, ¡no informe de los problemas que encuentre!</string>
<string name="label_experimental_settings_keybase_title">Comprobantes de Keybase.io</string>
<string name="label_experimental_settings_keybase_summary">Contacta con keybase.io para obtener comprobantes de clave y muestrelos cada vez que se muestra una clave</string>
<string name="label_experimental_settings_theme_summary">(Los iconos y algunas pantallas todavía no están ajustadas de acuerdo con el tema decorativo oscuro)</string>
<string name="label_settings_analytics_title">Permitir estadísticas de uso anónimas</string>
<string name="label_settings_analytics_summary">Si está habilitado, envía estadísticas de uso anónimas para ayudar a mejorar la app</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Habilitar Tor</string>
<string name="pref_proxy_tor_summary">Requiere que Orbot esté instalado</string>
@ -1804,12 +1801,6 @@
<string name="keylist_item_key_id">Identificación de la clave: %s</string>
<string name="keylist_header_anonymous">Anónimo</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">Para mejorar la experiencia de todos los usuarios, ¿puede OpenKeychain recopilar estadísticas de uso anónimas? \n\n Para saber más, mira nuestra <a href="https://openkeychain.org/help/privacy-policy">Política de Privacidad</a>.</string>
<string name="button_analytics_yes">Si, quiero ayudar!</string>
<string name="button_analytics_no">No, gracias!</string>
<string name="snack_analytics_accept">Gracias por ayudar! Puede cambiar esta preferencia en la configuración.</string>
<string name="snack_analytics_reject">Está bien, no volveremos a preguntar. Puedes cambiar de opinión en la configuración.</string>
<string name="snackbutton_analytics_settings">Configuración</string>
<string name="subkey_action_create">La subclave será creada</string>
<string name="subkey_action_revoke">La subclave será revocada</string>
<string name="subkey_action_strip">La subclave será removida</string>

View file

@ -206,13 +206,11 @@
<string name="label_sync_settings_contacts_summary_on">Lotu giltzak harremanekin izen eta post@ helbideetan ohinarrituz. Hau erabat lineaz-kanpo gertatzen da zure gailuan.</string>
<string name="label_sync_settings_contacts_summary_off">Giltza berriak ez dira harremanekin lotuko</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Berezgaitasunez eguneratu giltzak</string>
<string name="label_experimental_settings_desc_title">Kontuz</string>
<string name="label_experimental_settings_desc_title">Kontuz</string>
<string name="label_experimental_settings_desc_summary">Ezaugarri hauek ez dute amaitu edo erabiltzaile jarduera/segurtasun emaitzak erdietsita. Honela, ez dira beren segurtasunean ohinarritzen eta mesedez ez jakinarazi aurkitzen dituzun arazoak!</string>
<string name="label_experimental_settings_keybase_summary">Jarri harremanetan keybase.io giltzak probatzeko eta erakutsi hauek giltza bat erakusten den bakoitzean</string>
<string name="label_experimental_settings_theme_summary">(Ikurrak eta ikusleiho asko oraindik ez daude azalgai ilunarekin zehaztuta)</string>
<string name="label_settings_analytics_title">Ahalbidetu izengabeko estatistika erabilpena</string>
<!--Proxy Preferences-->
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Gaitu Tor</string>
<string name="pref_proxy_tor_summary">Orbot ezarrita egotea behar du</string>
<string name="pref_proxy_normal_title">Gaitu beste proxy bat</string>
@ -1552,9 +1550,6 @@
<string name="keylist_item_key_id">Giltza ID-a: %s</string>
<string name="keylist_header_anonymous">Izengabe</string>
<string name="keylist_header_special">#</string>
<string name="button_analytics_yes">Bai, laguntzea nahi dut!</string>
<string name="button_analytics_no">Ez, mila esker</string>
<string name="snackbutton_analytics_settings">Ezarpenak</string>
<string name="subkey_action_create">Azpigiltza sortuko da</string>
<string name="subkey_action_revoke">Azpigiltza ukatuko da</string>
<string name="subkey_action_expiry_never">Epemuga inoiz ez-ra aldatuko da</string>

View file

@ -430,6 +430,4 @@
<string name="snack_keylist_clipboard_action">مشاهده</string>
<string name="notify_title_keysync">درحال آپدیت‌کردن کلیدها...</string>
<string name="keylist_header_anonymous">ناشناس</string>
<string name="button_analytics_no">نه، ممنون</string>
<string name="snackbutton_analytics_settings">تنظیمات</string>
</resources>

View file

@ -381,5 +381,4 @@
<string name="snack_keylist_clipboard_action">Näkymä</string>
<string name="notify_title_keysync">Päivitetään avaimia...</string>
<string name="keylist_header_anonymous">Anonyymi</string>
<string name="snackbutton_analytics_settings">Asetukset</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Relier les clés aux contacts daprès les noms et les adresses courriel. Cela se passe entièrement hors ligne sur votre appareil.</string>
<string name="label_sync_settings_contacts_summary_off">Les nouvelles clés ne seront pas reliées aux contacts</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Mises à jour automatiques des clés</string>
<string name="label_experimental_settings_desc_title">Avertissement</string>
<string name="label_experimental_settings_desc_summary">Ces fonctions ne sont pas encore terminées et nont pas fait lobjet de recherche sur leur convivialité ni leur sécurité. Par conséquent, ne vous fiez pas à leur sécurité et veuillez ne pas signaler les problèmes que vous rencontrez.</string>
<string name="label_experimental_settings_keybase_title">Preuves keybase.io</string>
<string name="label_experimental_settings_keybase_summary">Contacter keybase.io pour obtenir des preuves de clé et les afficher chaque fois quune clé est affichée</string>
<string name="label_experimental_settings_theme_summary">(Les icônes et de nombreux écrans ne sont pas encore adaptés au thème sombre)</string>
<string name="label_settings_analytics_title">Autoriser les statistiques anonymes dutilisation</string>
<string name="label_settings_analytics_summary">Si cette option est activée, des statistiques anonymes d\'utilisation sont envoyées pour aider à améliorer lappli</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Activer Tor</string>
<string name="pref_proxy_tor_summary">Orbot doit être installé</string>
@ -780,8 +777,6 @@
<string name="my_keys">Mes clés</string>
<string name="nav_backup">Sauvegarder/Restaurer</string>
<string name="nav_transfer">Transfert Wi-Fi sécurisé</string>
<string name="nav_shop">Boutique</string>
<string name="nav_autocrypt">Essayer Autocrypt avec Thunderbird</string>
<!--hints-->
<string name="encrypt_content_edit_text_hint">Saisir le texte</string>
<!--certs-->
@ -1806,12 +1801,6 @@
<string name="keylist_item_key_id">ID de clé : %s</string>
<string name="keylist_header_anonymous">Anonyme</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">Pour améliorer lexpérience de tous les utilisateurs, OpenKeychain peut-elle recueillir des statistiques anonymes dutilisation?\n\nPour en apprendre davantage, consultez notre <a href="https://openkeychain.org/help/privacy-policy">politique de confidentialité</a>.</string>
<string name="button_analytics_yes">Oui, je souhaite aider</string>
<string name="button_analytics_no">Non, merci</string>
<string name="snack_analytics_accept">Nous vous remercions de votre aide. Vous pouvez changer cette préférence dans vos paramètres.</string>
<string name="snack_analytics_reject">Très bien, nous ne vous le demanderons plus. Vous pouvez changer cette préférence dans les paramètres.</string>
<string name="snackbutton_analytics_settings">Paramètres</string>
<string name="subkey_action_create">Une sous-clé sera créée</string>
<string name="subkey_action_revoke">La sous-clé sera révoquée</string>
<string name="subkey_action_strip">La sous-clé sera dépouillée</string>
@ -1819,6 +1808,4 @@
<string name="subkey_action_expiry_date">La date dexpiration sera changé à %s</string>
<string name="share_key_clipboard">Partager la clé avec le presse-papiers</string>
<string name="share_key">Partager la clé</string>
<string name="shop_loading_label">Chargement de la boutique…</string>
<string name="shop_title">Clés de sécurité COTECH</string>
</resources>

View file

@ -211,14 +211,11 @@
<string name="label_sync_settings_contacts_summary_on">Ligar chaves a contactos baseándose en nomes e enderezos de correo electrónico. Isto acontece sen precisar conexión a rede no seu dispositivo.</string>
<string name="label_sync_settings_contacts_summary_off">As novas chaves non se ligarán aos contactos</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Actualización automática de chaves</string>
<string name="label_experimental_settings_desc_title">Aviso</string>
<string name="label_experimental_settings_desc_title">Aviso</string>
<string name="label_experimental_settings_desc_summary">Estas características aínda non están rematadas ou son resultado da investigación da experiencia de usuaria e/ou seguridade. Xa que logo, non confíe na súa seguridade e por favor non informe sobre os fallos que poida atopar!</string>
<string name="label_experimental_settings_keybase_title">probas de Keybase.io</string>
<string name="label_experimental_settings_keybase_summary">Pedirlle probas de chave a keybase.io e expoñelas cada vez que se mostra a chave</string>
<string name="label_experimental_settings_theme_summary">(As iconas e moitas pantallas non están aínda ben axustadas ao decorado oscuro)</string>
<string name="label_settings_analytics_title">Permitir estátisticas anónimas sobre o uso</string>
<string name="label_settings_analytics_summary">Se está habilitado, envía estatísticas anónimas sobre o uso para axudar a mellorar a app.</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Habilitar Tor</string>
<string name="pref_proxy_tor_summary">Precisa instalar Orbot</string>
@ -1487,5 +1484,4 @@
<string name="keylist_item_key_id">ID da Chave: %s</string>
<string name="keylist_header_anonymous">Anónimo</string>
<string name="keylist_header_special">#</string>
<string name="snackbutton_analytics_settings">Axustes</string>
</resources>

View file

@ -295,5 +295,4 @@
<string name="token_unlock_ok">લોક ખોલો</string>
<string name="select_identity_cancel">નિષ્ક્રિય કરો</string>
<string name="key_gen_back">પહેલાનું</string>
<string name="snackbutton_analytics_settings">સેટિંગ્સ</string>
</resources>

View file

@ -214,6 +214,4 @@
<string name="key_gen_back">पिछला</string>
<string name="key_gen_finish">समाप्त</string>
<string name="snack_keylist_clipboard_action">राय</string>
<string name="button_analytics_no">जी नहीं, धन्यवाद</string>
<string name="snackbutton_analytics_settings">सेटिंग्स</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Kulcsok összekapcsolása partnerekkel a nevük és az e-mail címük alapján. Ez teljesen mértékben hálózati kapcsolat nélkül történik az ön eszközén.</string>
<string name="label_sync_settings_contacts_summary_off">Az új kulcsok nem lesznek összekapcsolva partnerekkel</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Automatikus kulcsfrissítések</string>
<string name="label_experimental_settings_desc_title">Figyelmeztetés</string>
<string name="label_experimental_settings_desc_summary">Ezek a funkciók még nincsenek befejezve, vagy felhasználói élmény és biztonsági kutatást eredményei. Emiatt ne bízzon a biztonságukban, és ne jelentse a felmerülő hibákat!</string>
<string name="label_experimental_settings_keybase_title">Keybase.io igazolások</string>
<string name="label_experimental_settings_keybase_summary">Kapcsolatfelvétel a keybase.io-val a kulcsigazolásokért, és megtekintés minden alkalommal, amikor egy kulcs megjelenik</string>
<string name="label_experimental_settings_theme_summary">(Az ikonok és számos képernyő még nem lett hozzáigazítva a sötét témához)</string>
<string name="label_settings_analytics_title">Névtelen használati statisztikák engedélyezése</string>
<string name="label_settings_analytics_summary">Ha engedélyezve van, akkor névtelen használati statisztikákat küld, hogy segítsen az alkalmazás továbbfejlesztésében </string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Tor engedélyezése</string>
<string name="pref_proxy_tor_summary">Az Orbot telepítése szükséges</string>
@ -743,15 +740,10 @@
<string name="keylist_item_key_id">Kulcsazonosító: %s</string>
<string name="keylist_header_anonymous">Anoním / Névtelen</string>
<string name="keylist_header_special">#</string>
<string name="button_analytics_yes">Igen, szeretnék segíteni!</string>
<string name="button_analytics_no">Köszönöm, nem</string>
<string name="snackbutton_analytics_settings">Beállítások</string>
<string name="subkey_action_create">Az alkulcs létrehozásra kerül</string>
<string name="subkey_action_revoke">Az alkulcs visszavonásra kerül</string>
<string name="subkey_action_expiry_never">A lejárat sohára változik</string>
<string name="subkey_action_expiry_date">A lejárat %s értékre változik</string>
<string name="share_key_clipboard">Kulcs megosztása vágólappal</string>
<string name="share_key">Kulcs megosztása</string>
<string name="shop_loading_label">Shop betöltése...</string>
<string name="shop_title">COTECH biztonsági kulcsok</string>
</resources>

View file

@ -249,5 +249,4 @@
<string name="snack_keylist_clipboard_action">Lihat</string>
<string name="keylist_header_anonymous">Anonim</string>
<string name="keylist_header_special"># </string>
<string name="snackbutton_analytics_settings">Pengaturan</string>
</resources>

View file

@ -194,8 +194,7 @@
<string name="label_sync_settings_contacts_summary_on">Collega le chiavi ai contatti in base ai nomi e indirizzi email. Ciò avviene completamente offline sul tuo dispositivo.</string>
<string name="label_sync_settings_contacts_summary_off">Le chiavi nuove non verranno collegate ai contatti</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Aggiornamenti automatici delle chiavi</string>
<string name="label_experimental_settings_desc_title">Attenzione</string>
<string name="label_experimental_settings_desc_title">Attenzione</string>
<string name="label_experimental_settings_desc_summary">Queste funzioni non sono ancora finite o testate da utenti/ricerche di sicurezza. Perciò non contare sulla loro sicurezza e per favore non segnalare i problemi che riscontri!</string>
<string name="label_experimental_settings_keybase_summary">Contatta keybase.io per avere prove e mostrale ogni volta che visualizzo una chiave</string>
<string name="label_experimental_settings_theme_summary">(Le icone e molte schermate non sono ancora modificate correttamente per il tema scuro)</string>
@ -1177,6 +1176,4 @@
<string name="keylist_item_key_id">ID chiave: %s</string>
<string name="keylist_header_anonymous">Anonimo</string>
<string name="keylist_header_special">#</string>
<string name="button_analytics_no">No, grazie</string>
<string name="snackbutton_analytics_settings">Impostazioni</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">オフランで完結して、名前とメールアドレスに基づいて、鍵を連絡先にリンク</string>
<string name="label_sync_settings_contacts_summary_off">新しい鍵は連絡先と関連付けしない</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">鍵の自動アップデート</string>
<string name="label_experimental_settings_desc_title">注意</string>
<string name="label_experimental_settings_desc_summary">これらの機能はまだ完成していないか、ユーザーエクスペリエンス/セキュリティ研究の結果ではありません。そのため、このセキュリティに依存したり、遭遇した問題を報告しないでください!</string>
<string name="label_experimental_settings_keybase_title">Keybase.io 検証</string>
<string name="label_experimental_settings_keybase_summary">鍵の検証のためkeybase.ioに連絡し、鍵が表示されるたびに表示します</string>
<string name="label_experimental_settings_theme_summary">(アイコンおよび多くの画面は、まだダークテーマに応じて調整されていません)</string>
<string name="label_settings_analytics_title">匿名の使用統計データの送信を許可する</string>
<string name="label_settings_analytics_summary">有効にすると、アプリの改善に役立つ匿名の使用統計データが送信されます</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Torを有効</string>
<string name="pref_proxy_tor_summary">Orbotのインストールが要求されます</string>
@ -766,8 +763,6 @@
<string name="my_keys">自分の鍵</string>
<string name="nav_backup">バックアップ/リストア</string>
<string name="nav_transfer">安全な Wifi 転送</string>
<string name="nav_shop">ショップ</string>
<string name="nav_autocrypt">Thunderbird と Autocrypt をお試しください</string>
<!--hints-->
<string name="encrypt_content_edit_text_hint">テキストを入力</string>
<!--certs-->
@ -1776,12 +1771,6 @@
<string name="keylist_item_key_id">鍵 ID: %s</string>
<string name="keylist_header_anonymous">匿名</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">すべてのユーザーのエクスペリエンスを改善するために、OpenKeychainの匿名の使用統計データを送信していただけますか\n\n詳細については、<a href="https://openkeychain.org/help/privacy-policy">プライバシーポリシー</a>をご覧ください。</string>
<string name="button_analytics_yes">はい、匿名の使用統計データを送信して応援します!</string>
<string name="button_analytics_no">いいえ、使用統計データを送信しません。</string>
<string name="snack_analytics_accept">応援をありがとうございます! この設定は「設定」で変更できます。</string>
<string name="snack_analytics_reject">大丈夫です、今後お尋ねしないようにします。「設定」でこの設定を変更できます。</string>
<string name="snackbutton_analytics_settings">設定</string>
<string name="subkey_action_create">サブキーが作成されます</string>
<string name="subkey_action_revoke">サブキーは取り消されます</string>
<string name="subkey_action_strip">サブキーは削除されます</string>
@ -1789,6 +1778,4 @@
<string name="subkey_action_expiry_date">有効期限は%sに変更されます</string>
<string name="share_key_clipboard">クリップボードでキーを共有する</string>
<string name="share_key">鍵の共有</string>
<string name="shop_loading_label">ショップを読み込んでいます…</string>
<string name="shop_title">COTECH セキュリティ鍵</string>
</resources>

View file

@ -202,13 +202,11 @@
<string name="label_sync_settings_contacts_summary_on">이름과 이메일 주소에 기초해 키를 연락처에 연결합니다. 이 작업은 완전히 오프라인에서만 수행됩니다.</string>
<string name="label_sync_settings_contacts_summary_off">새 키를 앞으로 연락처에 연결하지 않음</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">자동 키 갱신</string>
<string name="label_experimental_settings_desc_title">경고</string>
<string name="label_experimental_settings_desc_title">경고</string>
<string name="label_experimental_settings_desc_summary">이 기능들은 아직 완성되지 않았거나 유저 경험/보안 연구의 결과가 아닙니다. 그러니 이 기능의 보안성에 의존하거나 겪는 문제를 보고하지 말아주세요!</string>
<string name="label_experimental_settings_keybase_summary">keybase.io에 연결해서 키가 표시될 때 마다 키 증명을 표시</string>
<string name="label_experimental_settings_theme_summary">(아이콘이나 많은 화면이 검은 테마에 알맞게 아직 조정되지 않았습니다)</string>
<string name="label_settings_analytics_title">익명 사용 통계 허가</string>
<!--Proxy Preferences-->
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Tor 활성화</string>
<string name="pref_proxy_tor_summary">Orbot이 설치되어 있어야 합니다.</string>
<string name="pref_proxy_normal_title">다른 프록시 활성화</string>
@ -896,10 +894,4 @@
<string name="snack_keylist_clipboard_action">보기</string>
<string name="notify_title_keysync">키를 업데이트 중...</string>
<string name="keylist_header_anonymous">익명</string>
<string name="dialog_analytics_consent">모든 사용자의 경험을 개선하기 위해서 OpenKeychain가 익명 사용 데이터를 수집하는걸 허가 하시겠습니까?\n\n더 많은 정보는 저희의 <a href="https://openkeychain.org/help/privacy-policy">개인정보 보호정책</a>을 참고하십시오.</string>
<string name="button_analytics_yes">예, 돕겠습니다!</string>
<string name="button_analytics_no">아니요</string>
<string name="snack_analytics_accept">도와주셔서 감사합니다! 설정에서 언제든지 변경하실 수 있습니다.</string>
<string name="snack_analytics_reject">괜찮습니다. 이제 표시하지 않습니다. 마음이 바뀌신다면 설정에서 변경하실 수 있습니다.</string>
<string name="snackbutton_analytics_settings">설정</string>
</resources>

View file

@ -164,5 +164,4 @@
<string name="select_identity_cancel">പ്രവർത്തനരഹിതമാക്കു </string>
<string name="key_gen_back">പിന്നോട്ട്</string>
<string name="snack_keylist_clipboard_action">കാണുക</string>
<string name="snackbutton_analytics_settings">സെറ്റിംഗ്സ് </string>
</resources>

View file

@ -202,8 +202,7 @@
<string name="label_sync_settings_contacts_summary_on">Lenk nøkler til kontakter basert på navn og e-postadresser. Dette skjer helt frakoblet på din enhet.</string>
<string name="label_sync_settings_contacts_summary_off">Nye nøkler vil ikke lenkes til kontakter</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Automatiske nøkkeloppdateringer</string>
<string name="label_experimental_settings_desc_title">Advarsel</string>
<string name="label_experimental_settings_desc_title">Advarsel</string>
<string name="label_experimental_settings_desc_summary">Disse funksjonene er ikke ferdige enda, eller resultat av brukererfaring/sikkerhetsforskning. Som sådan, bør du ikke stole på at de er sikre, og ikke rapportere feil du kommer over!</string>
<string name="label_experimental_settings_keybase_title">Keybase.io-bevis</string>
<string name="label_experimental_settings_keybase_summary">Kontakt keybase.io for nøkkelbevis og vis dem hver gang en nøkkel vises</string>
@ -869,6 +868,4 @@
<string name="notify_title_keysync">oppdaterer nøkler…</string>
<string name="keylist_item_key_id">Nøkkel-ID: %s</string>
<string name="keylist_header_anonymous">Anonym</string>
<string name="button_analytics_no">Nei takk</string>
<string name="snackbutton_analytics_settings">Innstillinger</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Koppel sleutels aan contacten gebaseerd op namen en e-mailadressen. Dit gebeurt volledig offline op je apparaat.</string>
<string name="label_sync_settings_contacts_summary_off">Nieuwe sleutels zullen niet worden gekoppeld aan contacten</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Automatische sleutelupdates</string>
<string name="label_experimental_settings_desc_title">Waarschuwing</string>
<string name="label_experimental_settings_desc_title">Waarschuwing</string>
<string name="label_experimental_settings_desc_summary">Deze functies zijn nog niet afgewerkt of zijn resultaten van gebruikerservaringen/beveiligingsonderzoek. Vertrouw daarom niet op hun beveiliging en rapporteer geen problemen die je ervaart!</string>
<string name="label_experimental_settings_keybase_title">Keybase.io-bewijzen</string>
<string name="label_experimental_settings_keybase_summary">Maak contact met keybase.io voor bevestigingen van sleutels en toon deze telkens wanneer een sleutel wordt weergegeven</string>
<string name="label_experimental_settings_theme_summary">(De pictogrammen en veel schermen zijn nog niet geoptimaliseerd voor het donkere thema)</string>
<string name="label_settings_analytics_title">Anonieme gebruikersstatistieken toestaan</string>
<string name="label_settings_analytics_summary">Indien ingeschakeld worden er anonieme gebruikersstatistieken verzonden om de app te verbeteren</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Tor inschakelen</string>
<string name="pref_proxy_tor_summary">Vereist dat Orbot geïnstalleerd is</string>
@ -1804,12 +1801,6 @@
<string name="keylist_item_key_id">Sleutel-ID: %s</string>
<string name="keylist_header_anonymous">Anoniem</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">Mag OpenKeychain gebruikersstatistieken verzamelen om de ervaring voor alle gebruikers te verbeteren?\n\nVoor meer informatie, zie ons <a href="https://openkeychain.org/help/privacy-policy">privacybeleid</a>.</string>
<string name="button_analytics_yes">Ja, ik wil helpen!</string>
<string name="button_analytics_no">Nee, bedankt</string>
<string name="snack_analytics_accept">Bedankt voor de hulp! Je kan deze voorkeur veranderen in de instellingen.</string>
<string name="snack_analytics_reject">Geen probleem, we zullen het niet opnieuw vragen. Je kan van gedachten veranderen in de instellingen.</string>
<string name="snackbutton_analytics_settings">Instellingen</string>
<string name="subkey_action_create">Subsleutel zal worden aangemaakt</string>
<string name="subkey_action_revoke">Subsleutel zal worden ingetrokken</string>
<string name="subkey_action_strip">Subsleutel zal worden gestript</string>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Koppel sleutels aan contacten gebaseerd op namen en e-mailadressen. Dit gebeurt volledig offline op je apparaat.</string>
<string name="label_sync_settings_contacts_summary_off">Nieuwe sleutels zullen niet worden gekoppeld aan contacten</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Automatische sleutelupdates</string>
<string name="label_experimental_settings_desc_title">Waarschuwing</string>
<string name="label_experimental_settings_desc_summary">Deze functies zijn nog niet afgewerkt of zijn resultaten van gebruikerservaringen/beveiligingsonderzoek. Vertrouw daarom niet op hun beveiliging en rapporteer geen problemen die je ervaart!</string>
<string name="label_experimental_settings_keybase_title">Keybase.io-bewijzen</string>
<string name="label_experimental_settings_keybase_summary">Maak contact met keybase.io voor bevestigingen van sleutels en weergeef dit elke keer als een sleutel wordt weer gegeven</string>
<string name="label_experimental_settings_theme_summary">(De pictogrammen en veel schermen zijn nog niet geoptimaliseerd voor het donkere thema)</string>
<string name="label_settings_analytics_title">Sta anonieme gebruikersstatistieken toe</string>
<string name="label_settings_analytics_summary">Indien ingeschakeld worden er anonieme gebruikersstatistieken verzonden om de app te verbeteren</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Tor inschakelen</string>
<string name="pref_proxy_tor_summary">Vereist dat Orbot geïnstalleerd is</string>
@ -780,8 +777,6 @@
<string name="my_keys">Mijn sleutels</string>
<string name="nav_backup">Back-up/herstellen</string>
<string name="nav_transfer">Beveiligde wifi-overdracht</string>
<string name="nav_shop">Winkel</string>
<string name="nav_autocrypt">Probeer Autocrypt met Thunderbird</string>
<!--hints-->
<string name="encrypt_content_edit_text_hint">Voer tekst in</string>
<!--certs-->
@ -1806,12 +1801,6 @@
<string name="keylist_item_key_id">Sleutel-ID: %s</string>
<string name="keylist_header_anonymous">Anoniem</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">Mag OpenKeychain gebruikersstatistieken verzamelen om de ervaring voor alle gebruikers te verbeteren?\n\nVoor meer informatie, zie ons <a href="https://openkeychain.org/help/privacy-policy">privacybeleid</a>.</string>
<string name="button_analytics_yes">Ja, ik wil meehelpen!</string>
<string name="button_analytics_no">Nee, bedankt</string>
<string name="snack_analytics_accept">Bedankt voor de hulp! Je kan deze voorkeur veranderen in de instellingen.</string>
<string name="snack_analytics_reject">Geen probleem, we zullen het niet opnieuw vragen. Je kan van gedachten veranderen in de instellingen.</string>
<string name="snackbutton_analytics_settings">Instellingen</string>
<string name="subkey_action_create">Subsleutel zal worden aangemaakt</string>
<string name="subkey_action_revoke">Subsleutel zal worden ingetrokken</string>
<string name="subkey_action_strip">Subsleutel zal worden gestript</string>
@ -1819,6 +1808,4 @@
<string name="subkey_action_expiry_date">Verloopdatum zal wijzigen naar %s</string>
<string name="share_key_clipboard">Sleutel delen via klembord</string>
<string name="share_key">Sleutel delen</string>
<string name="shop_loading_label">Winkel aan het laden…</string>
<string name="shop_title">COTECH-beveiligingssleutels</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Połącz klucze z kontaktami używając nazw i adresów e-mail. Ta operacja jest wykonywana offline wyłączenie na tym urządzeniu.</string>
<string name="label_sync_settings_contacts_summary_off">Nowe klucze nie będą połączone z kontaktami</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Automatyczne aktualizacje kluczy</string>
<string name="label_experimental_settings_desc_title">Ostrzeżenie</string>
<string name="label_experimental_settings_desc_title">Ostrzeżenie</string>
<string name="label_experimental_settings_desc_summary">Te funkcje nie są jeszcze skończone lub są wynikiem badań bezpieczeństwa / wrażeń użytkownika. Tak więc nie polegaj na ich bezpieczeństwie i nie zgłaszaj problemów z nimi związanych!</string>
<string name="label_experimental_settings_keybase_title">Dowody keybase.io</string>
<string name="label_experimental_settings_keybase_summary">Sprawdź keybase.io dla dowodów kluczy i pokaż je za każdym razem kiedy klucz jest wyświetlany</string>
<string name="label_experimental_settings_theme_summary">(Ikony i ekrany nie są jeszcze dostosowane do ciemnego motywu)</string>
<string name="label_settings_analytics_title">Zezwalaj na anonimowe statystyki użycia</string>
<string name="label_settings_analytics_summary">Jeśli jest włączone wysyła anonimowe statystyki użycia aby pomóc w ulepszaniu aplikacji</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Włącz Tora</string>
<string name="pref_proxy_tor_summary">Wymaga Orbota</string>
@ -1872,12 +1869,6 @@
<string name="keylist_item_key_id">ID Klucza: %s</string>
<string name="keylist_header_anonymous">Anonimowy</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">Czy aby polepszyć doświadczenia użytkowników, OpenKeychain może zbierać anonimowe dane użytkowania?\n\nAby dowiedzieć się więcej zobacz naszą <a href="https://openkeychain.org/help/privacy-policy">Politykę Prywatności</a>.</string>
<string name="button_analytics_yes">Tak, chcę pomóc!</string>
<string name="button_analytics_no">Nie, dzięki</string>
<string name="snack_analytics_accept">Dziękujemy za pomoc! Możesz zmienić to ustawienie w opcjach.</string>
<string name="snack_analytics_reject">Dobrze, nie zapytamy więcej. Możesz zmienić to ustawienie w opcjach.</string>
<string name="snackbutton_analytics_settings">Ustawienia</string>
<string name="subkey_action_create">Podklucz zostanie utworzony</string>
<string name="subkey_action_revoke">Podklucz zostanie unieważniony</string>
<string name="subkey_action_strip">Podklucz zostanie wyczyszony</string>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Vincula chaves a contatos com base em nomes e endereços de email. Isto acontece completamente off-line em seu dispositivo.</string>
<string name="label_sync_settings_contacts_summary_off">Novas chaves não serão vinculadas aos contatos</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Atualização automática de chaves</string>
<string name="label_experimental_settings_desc_title">Aviso</string>
<string name="label_experimental_settings_desc_title">Aviso</string>
<string name="label_experimental_settings_desc_summary">Esses recursos ainda não estão finalizados ou resultam de pesquisas de experiência/segurança do usuário. Dessa forma, não confie em sua segurança e, por favor, não relate problemas que encontre!</string>
<string name="label_experimental_settings_keybase_title">Provas Keybase.io</string>
<string name="label_experimental_settings_keybase_summary">Contate o keybase.io para as provas de chaves e mostre-as toda vez que uma chave for exibida</string>
<string name="label_experimental_settings_theme_summary">(Os ícones e algumas telas ainda não foram adaptados para o tema escuro)</string>
<string name="label_settings_analytics_title">Permitir estatísticas anônimas de uso</string>
<string name="label_settings_analytics_summary">Se habilitado, envia estatísticas anônimas de uso para ajudar a melhorar a aplicação</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Ativar Tor</string>
<string name="pref_proxy_tor_summary">Requer que o Orbot esteja instalado</string>
@ -1804,12 +1801,6 @@
<string name="keylist_item_key_id">ID da Chave: %s</string>
<string name="keylist_header_anonymous">Anônimo</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">Para melhorar a experiência para todos os usuários, o OpenKeychain pode coletar estatísticas anônimas de uso?\n\nPara saber mais, consulte nossa <a href="https://openkeychain.org/help/privacy-policy">Política de Privacidade</a>.</string>
<string name="button_analytics_yes">Sim, eu quero ajudar!</string>
<string name="button_analytics_no">Não, obrigado</string>
<string name="snack_analytics_accept">Obrigado por nos ajudar! Você pode alterar esta preferência nas configurações.</string>
<string name="snack_analytics_reject">Sem problema, não iremos pedir de novo. Você pode mudar de ideia nas configurações.</string>
<string name="snackbutton_analytics_settings">Configurações</string>
<string name="subkey_action_create">A subchave será criada</string>
<string name="subkey_action_revoke">A subchave será revogada</string>
<string name="subkey_action_strip">A subchave será extirpada</string>

View file

@ -417,5 +417,4 @@
<string name="snack_keylist_clipboard_action">Vizualizare</string>
<string name="keylist_header_anonymous">Anonim</string>
<string name="keylist_header_special">#</string>
<string name="snackbutton_analytics_settings">Setări</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Связывать ключи с контактами основываясь на именах и адресах электронной почты. Это происходит полностью в автономном режиме на вашем устройстве.</string>
<string name="label_sync_settings_contacts_summary_off">Новые ключи не будут связаны с контактами</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Автообновление ключей</string>
<string name="label_experimental_settings_desc_title">Предупреждение</string>
<string name="label_experimental_settings_desc_summary">Эти новые возможности ещё не закончены и/или пока только изучаются. Проще говоря, не стоит полагаться на их безопасность. Пожалуйста, не сообщайте о связанных с ними проблемах!</string>
<string name="label_experimental_settings_keybase_title">Подтверждение Keybase.io </string>
<string name="label_experimental_settings_keybase_summary">Опрашивать keybase.io для подтверждения ключей и показывать это каждый раз при отображении ключей</string>
<string name="label_experimental_settings_theme_summary">(Значки и многие экраны ещё не скорректированы для тёмной темы)</string>
<string name="label_settings_analytics_title">Отправлять анонимную статистику</string>
<string name="label_settings_analytics_summary">Если данная функция включена, приложение будет отправлять анонимную статистику для помощи разработчикам</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Использовать Tor</string>
<string name="pref_proxy_tor_summary">Требуется установка Orbot</string>
@ -817,8 +814,6 @@
<string name="my_keys">Мои ключи</string>
<string name="nav_backup">Резервирование/Восстановление</string>
<string name="nav_transfer">Безопасная передача по Wi-Fi</string>
<string name="nav_shop">Магазин</string>
<string name="nav_autocrypt">Попробуйте Autocrypt с Thunderbird</string>
<!--hints-->
<string name="encrypt_content_edit_text_hint">Напишите текст</string>
<!--certs-->
@ -1875,12 +1870,6 @@
<string name="keylist_item_key_id">ID ключа: %s</string>
<string name="keylist_header_anonymous">Аноним</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">Может ли OpenKeychain собирать анонимную статистику для улучшения работы приложения?\n\nЧтобы узнать больше, изучите нашу <a href="https://openkeychain.org/help/privacy-policy">Политику конфиденциальности</a>.</string>
<string name="button_analytics_yes">Да, я хочу помочь!</string>
<string name="button_analytics_no">Нет, спасибо</string>
<string name="snack_analytics_accept">Спасибо за помощь! Вы можете изменить данную функцию в настройках.</string>
<string name="snack_analytics_reject">Всё нормально, больше мы не будем спрашивать. Вы можете изменить своё мнение с помощью настроек.</string>
<string name="snackbutton_analytics_settings">Настройки</string>
<string name="subkey_action_create">Доп. ключ будет создан</string>
<string name="subkey_action_revoke">Доп. ключ будет отозван</string>
<string name="subkey_action_strip">Доп. ключ будет отделён</string>
@ -1888,6 +1877,4 @@
<string name="subkey_action_expiry_date">Срок годности будет изменён на %s</string>
<string name="share_key_clipboard">Отправить ключ в буфер обмена</string>
<string name="share_key">Отправить ключ</string>
<string name="shop_loading_label">Загрузка магазина...</string>
<string name="shop_title">Ключи безопасности COTECH</string>
</resources>

View file

@ -831,6 +831,5 @@
<string name="key_gen_finish">Končaj</string>
<string name="notify_channel_passcache">Hranjenje gesla v spominu</string>
<string name="notify_title_keysync">Posodabljam ključe...</string>
<string name="snackbutton_analytics_settings">Nastavitve</string>
<string name="share_key">Deli ključ</string>
<string name="share_key">Deli ključ</string>
</resources>

View file

@ -188,8 +188,7 @@
<string name="label_sync_settings_contacts_summary_on">Повезивање кључева са контактима на основу имена и е-адреса. Одвија се у потпуности ван везе на вашем уређају.</string>
<string name="label_sync_settings_contacts_summary_off">Нови кључеви неће бити повезани са контактима</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Аутоматско ажурирање кључева</string>
<string name="label_experimental_settings_desc_title">Упозорење</string>
<string name="label_experimental_settings_desc_title">Упозорење</string>
<string name="label_experimental_settings_desc_summary">Ове функције још нису завршене или су резултат истраживања корисничког искуства и безбедности. Стога се не ослањајте на њихову безбедност и молимо вас да не пријављујете грешке на које наиђете!</string>
<string name="label_experimental_settings_keybase_summary">Тражење доказа кључева на keybase.io и њихов приказ приликом приказивања кључа</string>
<string name="label_experimental_settings_theme_summary">(Иконе и многи екрани још нису прилагођени за тамну тему)</string>
@ -1449,5 +1448,4 @@
<string name="notify_channel_passcache">Кеширај лозинку</string>
<string name="notify_title_keysync">Ажурирам кључеве…</string>
<string name="keylist_item_key_id">ИД кључа: %s</string>
<string name="snackbutton_analytics_settings">Podešavanja</string>
</resources>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">Länka nycklar till kontakter baserade på namn och e-postadresser. Detta sker helt offline på enheten.</string>
<string name="label_sync_settings_contacts_summary_off">Nya nycklar kommer inte att kopplas till kontakter</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Automatiska nyckeluppdateringar</string>
<string name="label_experimental_settings_desc_title">Varning</string>
<string name="label_experimental_settings_desc_summary">Dessa funktioner är ännu inte färdiga eller resultat av användarupplevelse/säkerhetsforskning. Lita därför inte på deras säkerhet och var snäll och rapportera inte problem du stöter på!</string>
<string name="label_experimental_settings_keybase_title">Keybase.io proofs</string>
<string name="label_experimental_settings_keybase_summary">Kontakta keybase.io för nyckelbevis och visa dem varje gång en nyckel visas</string>
<string name="label_experimental_settings_theme_summary">(Ikonerna och många skärmar är ännu inte justerade för mörkt tema)</string>
<string name="label_settings_analytics_title">Tillåt anonym användarstatistik</string>
<string name="label_settings_analytics_summary">Om den är aktiverad skickar du anonym användarstatistik för att förbättra appen</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Aktivera Tor</string>
<string name="pref_proxy_tor_summary">Kräver att Orbot är installerad</string>
@ -776,8 +773,6 @@
<string name="my_keys">Mina nycklar</string>
<string name="nav_backup">Säkerhetskopiera/återställa</string>
<string name="nav_transfer">Säker Wi-Fi-överföring</string>
<string name="nav_shop">Butik</string>
<string name="nav_autocrypt">Prova Autocrypt med Thunderbird</string>
<!--hints-->
<string name="encrypt_content_edit_text_hint">Skriv text</string>
<!--certs-->
@ -1670,12 +1665,6 @@
<string name="keylist_item_key_id">Nyckel-ID: %s</string>
<string name="keylist_header_anonymous">Anonym</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">För att förbättra upplevelsen för alla användare, kan OpenKeychain samla anonym användningsstatistik?\n\nFör mer information, se vår <a href="https://openkeychain.org/help/privacy-policy">integritetspolicy</a>.</string>
<string name="button_analytics_yes">Ja, jag vill hjälpa till!</string>
<string name="button_analytics_no">Nej tack</string>
<string name="snack_analytics_accept">Tack för hjälpen! Du kan ändra denna inställning i inställningarna.</string>
<string name="snack_analytics_reject">Det är okej, vi kommer inte att fråga igen. Du kan ändra dig i inställningarna.</string>
<string name="snackbutton_analytics_settings">Inställningar</string>
<string name="subkey_action_create">Undernyckel kommer att skapas</string>
<string name="subkey_action_revoke">Undernyckel kommer att återkallas</string>
<string name="subkey_action_strip">Undernyckeln kommer att avskalas</string>

View file

@ -127,5 +127,4 @@
<string name="button_cancel">రద్దు</string>
<string name="transfer_status_connected">సంబంధిత</string>
<string name="transfer_confirm_cancel">రద్దు</string>
<string name="snackbutton_analytics_settings">సెట్టింగులు</string>
</resources>

View file

@ -212,5 +212,4 @@
<string name="key_gen_back">ย้อนกลับ</string>
<string name="key_gen_finish">เสร็จ</string>
<string name="snack_keylist_clipboard_action">ดู</string>
<string name="snackbutton_analytics_settings">การตั้งค่า</string>
</resources>

View file

@ -523,6 +523,4 @@
<string name="snack_keylist_clipboard_action">Göster</string>
<string name="notify_channel_passcache">Parola önbelleği</string>
<string name="keylist_header_anonymous">Anonim</string>
<string name="button_analytics_no">Hayır, teşekkürler</string>
<string name="snackbutton_analytics_settings">Ayarlar</string>
</resources>

View file

@ -212,15 +212,12 @@
<string name="label_sync_settings_contacts_summary_on">Прив\'язувати ключі до контактів на основі імен та адрес електронної пошти. Це відбувається повністю локально на Вашому пристрої.</string>
<string name="label_sync_settings_contacts_summary_off">Нові ключі не будуть прив\'язані до контактів</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Автоматичне оновлення ключів</string>
<string name="label_experimental_settings_desc_title">Попередження</string>
<string name="label_experimental_settings_desc_title">Попередження</string>
<string name="label_experimental_settings_desc_summary">Ці можливості ще в розробці. Будь ласка, не сподівайтесь на їх безпеку та не повідомляйте про проблеми їхнього використання!</string>
<string name="label_experimental_settings_keybase_title">Перевірки Keybase.io</string>
<string name="label_experimental_settings_keybase_summary">Контактувати з keybase.io для підтвердження ключа та показувати дійсності щоразу, коли ключ відображено</string>
<string name="label_experimental_settings_theme_summary">(Піктограми та більшість екранів не налаштовані для темної теми)</string>
<string name="label_settings_analytics_title">Дозволити анонімну статистику про використання</string>
<string name="label_settings_analytics_summary">Якщо увімкнено, надсилає анонімну статистику про використання, щоби допомогти покращити цей застосунок</string>
<!--Proxy Preferences-->
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">Увімкнути Tor</string>
<string name="pref_proxy_tor_summary">Потребує інсталяції Orbot</string>
<string name="pref_proxy_normal_title">Увімкнути інше проксі</string>
@ -1876,13 +1873,7 @@
<string name="keylist_item_key_id">Ідентифікатор ключа: %s</string>
<string name="keylist_header_anonymous">Анонімно</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">Щоб покращити досвід для всіх користувачів, чи може OpenKeychain збирати анонімну статистику про використання?\n\nЩоби дізнатися більше, див. нашу <a href="https://openkeychain.org/help/privacy-policy">Політику конфіденційності</a>.</string>
<string name="button_analytics_yes">Так, я хочу допомогти!</string>
<string name="button_analytics_no">Ні, дякую</string>
<string name="snack_analytics_accept">Дякуємо за допомогу! Ви можете змінити цей параметр у налаштуваннях.</string>
<string name="snack_analytics_reject">Усе добре, ми не будемо запитувати вас знову. Ви можете змінити свою думку в налаштуваннях.</string>
<string name="snackbutton_analytics_settings">Налаштування</string>
<string name="subkey_action_create">Підключ буде створено</string>
<string name="subkey_action_create">Підключ буде створено</string>
<string name="subkey_action_revoke">Підключ буде відкликано</string>
<string name="subkey_action_strip">Підключ буде відділено</string>
<string name="subkey_action_expiry_never">Термін дії буде змінено на ніколи</string>

View file

@ -189,8 +189,7 @@
<string name="label_sync_settings_contacts_summary_on">Liên kết khóa với danh bạ dựa trên tên và địa chỉ thư. Nó được thực hiện hoàn toàn ngoại tuyến trên thiết bị của bạn.</string>
<string name="label_sync_settings_contacts_summary_off">Các khóa mới sẽ không được liên kết với danh bạ</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">Cập nhật khóa tự động</string>
<string name="label_experimental_settings_desc_title">Cảnh báo</string>
<string name="label_experimental_settings_desc_title">Cảnh báo</string>
<string name="label_experimental_settings_desc_summary">Tính năng này hiện chưa được hoàn thành hoặc chỉ mới là kết quả nghiên cứu/thử nghiệm. Do vậy, bạn đừng tin cậy vào tính an toàn của chúng cũng như không cần thiết phải thông báo với chúng tôi!</string>
<string name="label_experimental_settings_keybase_summary">Liên hệ với keybase.io để minh chứng khóa và hiển thị chúng mỗi khi khóa được gọi</string>
<string name="label_experimental_settings_theme_summary">(Các biểu tượng và nhiều màn hình không được thêm vào giao diện dạng tối)</string>
@ -371,5 +370,4 @@
<string name="key_gen_finish">Kết thúc</string>
<string name="snack_keylist_clipboard_action">Xem</string>
<string name="keylist_header_anonymous">Ẩn danh</string>
<string name="snackbutton_analytics_settings">Thiết đặt</string>
</resources>

View file

@ -213,11 +213,9 @@
<string name="label_sync_settings_contacts_summary_on">連接金鑰至聯絡人(基於姓名和電子郵件地址)。這只在您的裝置上離線進行。</string>
<string name="label_sync_settings_contacts_summary_off">新的金鑰將不會與聯絡人連接</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">自動金鑰同步</string>
<string name="label_experimental_settings_desc_title">警告</string>
<string name="label_experimental_settings_desc_title">警告</string>
<string name="label_experimental_settings_keybase_title">Keybase.io 證明</string>
<string name="label_settings_analytics_title">允許匿名的使用資料收集</string>
<!--Proxy Preferences-->
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">啟用洋蔥(Tor)網路</string>
<string name="pref_proxy_tor_summary">必須已安裝 Orbot</string>
<string name="pref_proxy_normal_title">啟用其他代理伺服器</string>
@ -967,9 +965,6 @@
<string name="keylist_item_key_id">金鑰 ID: %s</string>
<string name="keylist_header_anonymous">匿名</string>
<string name="keylist_header_special">#</string>
<string name="button_analytics_yes">是的,我想協助!</string>
<string name="button_analytics_no">不用,謝謝</string>
<string name="snackbutton_analytics_settings">設定</string>
<string name="subkey_action_create">子金鑰將被創建</string>
<string name="subkey_action_revoke">子金鑰將被撤銷</string>
<string name="subkey_action_strip">子金鑰將被分離</string>

View file

@ -212,14 +212,11 @@
<string name="label_sync_settings_contacts_summary_on">根据名字和邮件地址将密钥关联到联系人。这是本地操作,不需要设备联网。</string>
<string name="label_sync_settings_contacts_summary_off">新密钥将不会与联系人相关联</string>
<!--label shown in Android settings under the OpenKeychain account-->
<string name="keyserver_sync_settings_title">自动更新密钥</string>
<string name="label_experimental_settings_desc_title">警告</string>
<string name="label_experimental_settings_desc_summary">这些功能目前尚未完成或者缺少用户经验性/安全性的研究结果。所以,不保证他们是安全的,请不要报告遇到的问题!</string>
<string name="label_experimental_settings_keybase_title">Keybase.io 证明</string>
<string name="label_experimental_settings_keybase_summary">每次展示密钥时自动从keybase.io获取证明并显示它们</string>
<string name="label_experimental_settings_theme_summary">(图标和某些界面还未根据深色主题进行调整)</string>
<string name="label_settings_analytics_title">允许使用匿名统计数据</string>
<string name="label_settings_analytics_summary">如果开启,发送匿名的统计数据来帮助我们改进此软件</string>
<!--Proxy Preferences-->
<string name="pref_proxy_tor_title">启用 Tor 代理</string>
<string name="pref_proxy_tor_summary">必需已安装 Orbot</string>
@ -762,8 +759,6 @@
<string name="my_keys">我的密钥</string>
<string name="nav_backup">备份/恢复</string>
<string name="nav_transfer">安全 Wifi 传输</string>
<string name="nav_shop">商店</string>
<string name="nav_autocrypt">尝试在 Thunderbird 中自动加密</string>
<!--hints-->
<string name="encrypt_content_edit_text_hint">此处输入要加密的内容</string>
<!--certs-->
@ -1772,12 +1767,6 @@
<string name="keylist_item_key_id">密钥 ID%s</string>
<string name="keylist_header_anonymous">匿名</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">为改善所有用户的使用体验允许OpenKeychain匿名搜集一些使用统计信息吗\n\n想知道更多查阅我们的<a href="https://openkeychain.org/help/privacy-policy">隐私政策</a></string>
<string name="button_analytics_yes">是的,我愿意帮忙!</string>
<string name="button_analytics_no">不用了,谢谢</string>
<string name="snack_analytics_accept">感谢帮助!你可以在设置中更改这些偏好。</string>
<string name="snack_analytics_reject">没问题,我们不会再次打扰你。如果改变了想法,你可以在设置中更改。</string>
<string name="snackbutton_analytics_settings">设置</string>
<string name="subkey_action_create">将创建子密钥</string>
<string name="subkey_action_revoke">将吊销子密钥</string>
<string name="subkey_action_strip">将剥离子密钥</string>
@ -1785,6 +1774,4 @@
<string name="subkey_action_expiry_date">过期时间将更改为%s</string>
<string name="share_key_clipboard">通过剪贴板共享</string>
<string name="share_key">共享密钥</string>
<string name="shop_loading_label">加载商店</string>
<string name="shop_title">COTECH 安全密钥</string>
</resources>

View file

@ -220,20 +220,12 @@
<string name="label_sync_settings_keyserver_summary_on">"Every three days, keys are updated from the preferred keyserver"</string>
<string name="label_sync_settings_keyserver_summary_off">"Keys are not automatically updated"</string>
<string name="label_sync_settings_wifi_title">"Sync only on Wi-Fi"</string>
<string name="label_sync_settings_contacts_title">"Link keys to contacts"</string>
<string name="label_sync_settings_contacts_summary_on">"Link keys to contacts based on names and email addresses. This happens completely offline on your device."</string>
<string name="label_sync_settings_contacts_summary_off">"New keys will not be linked to contacts"</string>
<!-- label shown in Android settings under the OpenKeychain account -->
<string name="keyserver_sync_settings_title">"Automatic key updates"</string>
<string name="label_experimental_settings_desc_title">"Warning"</string>
<string name="label_experimental_settings_desc_summary">"These features are not yet finished or results of user experience/security research. Thus, don't rely on their security and please don't report issues you encounter!"</string>
<string name="label_experimental_settings_theme_summary">"(The icons and many screens are not yet adjusted accordingly for the dark theme)"</string>
<string name="label_settings_analytics_title">Allow anonymous usage statistics</string>
<string name="label_settings_analytics_summary">If enabled, sends anonymous usage statistics to help improve the app</string>
<!-- Proxy Preferences -->
<string name="pref_proxy_tor_title">"Enable Tor"</string>
<string name="pref_proxy_tor_summary">"Requires Orbot to be installed"</string>
@ -820,8 +812,6 @@
<string name="my_keys">"My Keys"</string>
<string name="nav_backup">"Backup/Restore"</string>
<string name="nav_transfer">"Secure Wifi Transfer"</string>
<string name="nav_shop">Shop</string>
<string name="nav_autocrypt">Try Autocrypt with Thunderbird</string>
<!-- hints -->
<string name="encrypt_content_edit_text_hint">"Type text"</string>
@ -1908,13 +1898,6 @@
<string name="keylist_header_anonymous">Anonymous</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_consent">"To improve the experience for all users, may OpenKeychain collect anonymous usage statistics?\n\nTo find out more, see our <a href="https://openkeychain.org/help/privacy-policy">Privacy Policy</a>."</string>
<string name="button_analytics_yes">"Yes, I want to help!"</string>
<string name="button_analytics_no">"No, thanks"</string>
<string name="snack_analytics_accept">"Thanks for helping out! You can change this preference in the settings."</string>
<string name="snack_analytics_reject">"That's alright, we won't ask again. You can change your mind in the settings."</string>
<string name="snackbutton_analytics_settings">"Settings"</string>
<string name="subkey_action_create">Subkey will be created</string>
<string name="subkey_action_revoke">Subkey will be revoked</string>
<string name="subkey_action_strip">Subkey will be stripped</string>
@ -1923,6 +1906,4 @@
<string name="share_key_clipboard">Share key via clipboard</string>
<string name="share_key">Share key</string>
<string name="shop_loading_label">Loading shop…</string>
<string name="shop_title">COTECH Security Keys</string>
</resources>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountPreferences="@xml/account_preferences"
android:accountType="@string/account_type"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" />

View file

@ -5,13 +5,6 @@
android:summary="@string/label_experimental_settings_desc_summary"
android:title="@string/label_experimental_settings_desc_title" />
<SwitchPreference
android:defaultValue="false"
android:key="analytics_consent"
android:persistent="true"
android:summary="@string/label_settings_analytics_summary"
android:title="@string/label_settings_analytics_title" />
<ListPreference
android:defaultValue="light"
android:dialogTitle="@string/label_theme"

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.contacts"
android:accountType="@string/account_type"
android:supportsUploading="false"
android:userVisible="true"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true" />

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ContactsSource xmlns:android="http://schemas.android.com/apk/res/android">
<ContactsDataKind
android:mimeType="vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key"
android:detailColumn="data1" />
</ContactsSource>

View file

@ -3,15 +3,14 @@
android:key="syncKeyserver"
android:defaultValue="true"
android:persistent="true"
android:title="@string/label_sync_settings_keyserver_title"/>
android:title="@string/label_sync_settings_keyserver_title"
android:summaryOn="@string/label_sync_settings_keyserver_summary_on"
android:summaryOff="@string/label_sync_settings_keyserver_summary_off"
/>
<SwitchPreference
android:key="enableWifiSyncOnly"
android:defaultValue="true"
android:persistent="true"
android:dependency="syncKeyserver"
android:title="@string/label_sync_settings_wifi_title"/>
<SwitchPreference
android:key="syncContacts"
android:persistent="false"
android:title="@string/label_sync_settings_contacts_title" />
</PreferenceScreen>