Add opt-in setting for tracking

This commit is contained in:
Vincent Breitmoser 2018-06-14 23:18:27 +02:00
parent c0a1fc84eb
commit c5d7e482e0
7 changed files with 117 additions and 10 deletions

View file

@ -158,6 +158,9 @@ 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 class Theme {
public static final String LIGHT = "light";
public static final String DARK = "dark";

View file

@ -6,34 +6,31 @@ import android.app.Application;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
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.util.Preferences;
public class TrackingManager {
private Tracker piwikTracker;
public static TrackingManager getInstance(Context context) {
TrackerConfig trackerConfig = new TrackerConfig("https://mugenguild.com/piwik/", 1, "OpenKeychain");
Tracker tracker = Piwik.getInstance(context).newTracker(trackerConfig);
tracker.setDispatchInterval(30000);
return new TrackingManager(tracker);
return new TrackingManager(context);
}
private TrackingManager(Tracker piwikTracker) {
this.piwikTracker = piwikTracker;
private TrackingManager(Context context) {
refreshSettings(context);
}
public void initialize(Application application) {
if (piwikTracker == null) {
return;
if (piwikTracker != null) {
TrackHelper.track().download().identifier(new ApkChecksum(application)).with(piwikTracker);
}
TrackHelper.track().download().identifier(new ApkChecksum(application)).with(piwikTracker);
application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
@ -47,6 +44,9 @@ public class TrackingManager {
@Override
public void onActivityResumed(Activity activity) {
if (piwikTracker == null) {
return;
}
TrackHelper.track().screen(activity.getClass().getSimpleName()).with(piwikTracker);
}
@ -99,4 +99,20 @@ public class TrackingManager {
.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/", 1, "OpenKeychain");
piwikTracker = Piwik.getInstance(context).newTracker(trackerConfig);
piwikTracker.setDispatchInterval(60000);
piwikTracker.setOptOut(false);
} else {
piwikTracker.setOptOut(true);
piwikTracker = null;
}
}
}
}

View file

@ -20,12 +20,17 @@ package org.sufficientlysecure.keychain.ui;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.WorkerThread;
@ -51,9 +56,12 @@ import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener;
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener;
import eu.davidea.flexibleadapter.SelectableAdapter.Mode;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.TrackingManager;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.daos.KeyRepository;
@ -259,6 +267,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);
maybeAskForAnalytics();
}
@WorkerThread
@ -267,6 +277,45 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo);
}
private void maybeAskForAnalytics() {
Context context = getContext();
if (context == null) {
return;
}
Preferences preferences = Preferences.getPreferences(context);
if (!Constants.DEBUG && !preferences.isAnalyticsHasConsent() && preferences.isAnalyticsAskedPolitely()) {
return;
}
try {
long firstInstallTime = context.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;
}
TrackingManager trackingManager = ((KeychainApplication) requireActivity().getApplication()).getTrackingManager();
AlertDialog show = new Builder(context)
.setMessage(R.string.dialog_analytics_text)
.setPositiveButton(R.string.button_analytics_yes, (dialog, which) -> {
preferences.setAnalyticsAskedPolitely();
preferences.setAnalyticsGotUserConsent(true);
trackingManager.refreshSettings(context);
})
.setNegativeButton(R.string.button_analytics_no, (dialog, which) -> {
preferences.setAnalyticsAskedPolitely();
preferences.setAnalyticsGotUserConsent(false);
trackingManager.refreshSettings(context);
})
.show();
show.setCanceledOnTouchOutside(false);
}
private void onLoadKeyItems(List<FlexibleKeyItem> flexibleKeyItems) {
FlexibleAdapter<FlexibleKeyItem> adapter = getAdapter();
if (adapter == null) {

View file

@ -37,6 +37,7 @@ import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
@ -50,6 +51,7 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.Pref;
import org.sufficientlysecure.keychain.KeychainApplication;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity;
@ -584,6 +586,14 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
});
}
@Override
public void onPause() {
super.onPause();
Activity activity = getActivity();
((KeychainApplication) activity.getApplication()).getTrackingManager().refreshSettings(activity);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {

View file

@ -353,6 +353,22 @@ 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();
}
@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,7 @@
<string name="keylist_header_anonymous">Anonymous</string>
<string name="keylist_header_special">#</string>
<string name="dialog_analytics_text">Allow OpenKeychain to collect anonymous usage statistics to help improve the app?</string>
<string name="button_analytics_yes">Yes, I want to help!</string>
<string name="button_analytics_no">No, thanks</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"