Merge pull request #2350 from open-keychain/piwik-tracking

Add opt-in tracking with Piwik
This commit is contained in:
Vincent Breitmoser
2018-07-17 16:11:07 +02:00
committed by GitHub
14 changed files with 398 additions and 25 deletions

View File

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

View File

@@ -28,6 +28,9 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import java.io.File;
import java.net.Proxy;
import java.util.Arrays;
import java.util.List;
public final class Constants {
@@ -158,6 +161,10 @@ 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";
@@ -168,6 +175,14 @@ 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, THEME,
SYNC_CONTACTS, SYNC_KEYSERVER, ENABLE_WIFI_SYNC_ONLY, EXPERIMENTAL_ENABLE_KEYBASE,
EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, EXPERIMENTAL_USB_ALLOW_UNTESTED,
PASSPHRASE_CACHE_SUBS, SEARCH_KEYSERVER, SEARCH_KEYBASE, 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,6 +30,7 @@ import android.support.annotation.Nullable;
import android.widget.Toast;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
import org.sufficientlysecure.keychain.network.TlsCertificatePinning;
import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
@@ -41,6 +42,7 @@ 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
@@ -105,6 +107,9 @@ public class KeychainApplication extends Application {
KeyserverSyncManager.updateKeyserverSyncScheduleAsync(this, Constants.DEBUG_KEYSERVER_SYNC);
TemporaryFileProvider.scheduleCleanupImmediately();
analyticsManager = AnalyticsManager.getInstance(getApplicationContext());
analyticsManager.initialize(this);
}
/**
@@ -152,4 +157,8 @@ public class KeychainApplication extends Application {
Timber.plant(new DebugTree());
}
}
public AnalyticsManager getAnalyticsManager() {
return analyticsManager;
}
}

View File

@@ -0,0 +1,97 @@
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.Constants;
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 (!Constants.DEBUG && 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 (!Constants.DEBUG && installedLessThanThreeDaysAgo) {
return;
}
} catch (NameNotFoundException e) {
return;
}
long twentyFourHoursAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
boolean askedLessThan24HoursAgo = preferences.getAnalyticsLastAsked() > twentyFourHoursAgo;
if (!Constants.DEBUG && 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());
alertDialog.setCanceledOnTouchOutside(false);
}
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

@@ -0,0 +1,158 @@
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.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) {
// 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;
}
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 analyticsHasConsent = Preferences.getPreferences(context).isAnalyticsHasConsent();
boolean analyticsEnabled = piwikTracker != null;
if (analyticsHasConsent != analyticsEnabled) {
if (analyticsHasConsent) {
TrackerConfig 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;
}
}
}
}

View File

@@ -53,6 +53,8 @@ import org.openintents.openpgp.OpenPgpSignatureResult.AutocryptPeerResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants;
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;
@@ -99,6 +101,7 @@ public class OpenPgpService extends Service {
private ApiAppDao mApiAppDao;
private OpenPgpServiceKeyIdExtractor mKeyIdExtractor;
private ApiPendingIntentFactory mApiPendingIntentFactory;
private AnalyticsManager analyticsManager;
@Override
public void onCreate() {
@@ -108,6 +111,8 @@ 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,
@@ -1025,6 +1030,8 @@ 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

@@ -21,11 +21,14 @@ package org.sufficientlysecure.keychain.service;
import java.util.concurrent.atomic.AtomicBoolean;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Parcelable;
import android.support.v4.os.CancellationSignal;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.operations.BackupOperation;
import org.sufficientlysecure.keychain.operations.BaseOperation;
@@ -52,13 +55,20 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
public class KeychainServiceTask {
public static KeychainServiceTask create(Context context) {
return new KeychainServiceTask(context.getApplicationContext());
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);
}
private KeychainServiceTask(Context context) {
private KeychainServiceTask(Context context, KeyWritableRepository keyRepository, AnalyticsManager analyticsManager) {
this.context = context;
this.keyRepository = KeyWritableRepository.create(context);
this.keyRepository = keyRepository;
this.analyticsManager = analyticsManager;
}
private final Context context;
@@ -121,6 +131,8 @@ 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

@@ -54,6 +54,7 @@ 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;
@@ -259,6 +260,8 @@ 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

@@ -23,7 +23,6 @@ import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.Toolbar;
import android.view.View;
@@ -37,7 +36,9 @@ 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;
@@ -62,6 +63,7 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
public Drawer mDrawer;
private Toolbar mToolbar;
private AnalyticsManager analyticsManager;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -72,6 +74,8 @@ 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)
@@ -200,6 +204,8 @@ 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);

View File

@@ -584,6 +584,14 @@ 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

@@ -42,11 +42,13 @@ import android.view.ViewPropertyAnimator;
import android.view.animation.OvershootInterpolator;
import com.astuetz.PagerSlidingTabStrip;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R;
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;
@@ -62,12 +64,13 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
KeyRepository keyRepository;
// view
private ViewPager mViewPager;
private PagerSlidingTabStrip mSlidingTabLayout;
private ViewPager viewPager;
private PagerSlidingTabStrip slidingTabLayout;
private ActionMode mActionMode;
private ActionMode actionMode;
private boolean hasSecret;
private boolean mActionIconShown;
private boolean actionIconShown;
private PagerTabStripAdapter tabAdapter;
enum ViewKeyAdvTab {
START(ViewKeyAdvStartFragment.class, R.string.key_view_tab_start, false),
@@ -86,6 +89,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
}
}
private AnalyticsManager analyticsManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -93,9 +98,10 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
setFullScreenDialogClose(v -> finish());
keyRepository = KeyRepository.create(this);
analyticsManager = ((KeychainApplication) getApplication()).getAnalyticsManager();
mViewPager = findViewById(R.id.pager);
mSlidingTabLayout = findViewById(R.id.sliding_tab_layout);
viewPager = findViewById(R.id.pager);
slidingTabLayout = findViewById(R.id.sliding_tab_layout);
if (!getIntent().hasExtra(EXTRA_MASTER_KEY_ID)) {
throw new IllegalArgumentException("Missing required extra master_key_id");
@@ -185,7 +191,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
}
mToolbar.setBackgroundColor(color);
mStatusBar.setBackgroundColor(ViewKeyActivity.getStatusBarBackgroundColor(color));
mSlidingTabLayout.setBackgroundColor(color);
slidingTabLayout.setBackgroundColor(color);
invalidateOptionsMenu();
}
@@ -196,21 +202,21 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
}
private void initTabs() {
PagerTabStripAdapter tabAdapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(tabAdapter);
tabAdapter = new PagerTabStripAdapter(this);
viewPager.setAdapter(tabAdapter);
for (ViewKeyAdvTab tab : ViewKeyAdvTab.values()) {
tabAdapter.addTab(tab.fragmentClass, null, getString(tab.titleRes));
}
// update layout after operations
mSlidingTabLayout.setViewPager(mViewPager);
mSlidingTabLayout.setOnPageChangeListener(this);
slidingTabLayout.setViewPager(viewPager);
slidingTabLayout.setOnPageChangeListener(this);
// switch to tab selected by extra
Intent intent = getIntent();
int switchToTab = intent.getIntExtra(EXTRA_SELECTED_TAB, 0);
mViewPager.setCurrentItem(switchToTab);
viewPager.setCurrentItem(switchToTab);
}
@Override
@@ -234,15 +240,15 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
getMenuInflater().inflate(R.menu.action_mode_edit, menu);
final MenuItem vActionModeItem = menu.findItem(R.id.menu_action_mode_edit);
boolean isCurrentActionFragment = ViewKeyAdvTab.values()[mViewPager.getCurrentItem()].hasActionMode;
boolean isCurrentActionFragment = ViewKeyAdvTab.values()[viewPager.getCurrentItem()].hasActionMode;
// if the state is as it should be, never mind
if (isCurrentActionFragment == mActionIconShown) {
if (isCurrentActionFragment == actionIconShown) {
return isCurrentActionFragment;
}
// show or hide accordingly
mActionIconShown = isCurrentActionFragment;
actionIconShown = isCurrentActionFragment;
vActionModeItem.setEnabled(isCurrentActionFragment);
animateMenuItem(vActionModeItem, isCurrentActionFragment);
@@ -273,13 +279,13 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
@Override
public void onActionModeStarted(final ActionMode mode) {
super.onActionModeStarted(mode);
mActionMode = mode;
actionMode = mode;
}
@Override
public void onActionModeFinished(ActionMode mode) {
super.onActionModeFinished(mode);
mActionMode = null;
actionMode = null;
}
@Override
@@ -289,11 +295,14 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList
@Override
public void onPageSelected(int position) {
if (mActionMode != null) {
mActionMode.finish();
mActionMode = null;
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
invalidateOptionsMenu();
String fragmentName = tabAdapter.getItem(position).getClass().getSimpleName();
analyticsManager.trackFragmentImpression(getClass().getSimpleName(), fragmentName);
}
@Override

View File

@@ -33,6 +33,7 @@ import android.support.annotation.Nullable;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.Pref;
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
import timber.log.Timber;
@@ -81,6 +82,10 @@ public class Preferences {
mSharedPreferences = context.getSharedPreferences(PREF_FILE_NAME, PREF_FILE_MODE);
}
public SharedPreferences getSharedPreferences() {
return mSharedPreferences;
}
public String getLanguage() {
return mSharedPreferences.getString(Constants.Pref.LANGUAGE, "");
}
@@ -353,6 +358,30 @@ public class Preferences {
mSharedPreferences.edit().putBoolean(Pref.SYNC_IS_SCHEDULED, isScheduled).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

@@ -236,6 +236,9 @@
<string name="label_experimental_settings_keybase_summary">"Contact keybase.io for key proofs and show them every time a key is displayed"</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>
@@ -2045,4 +2048,11 @@
<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>
</resources>

View File

@@ -5,6 +5,13 @@
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" />
<SwitchPreference
android:defaultValue="false"
android:key="experimentalEnableLinkedIdentities"