diff options
author | Danesh Mondegarian <daneshm90@gmail.com> | 2012-02-03 21:02:21 -0500 |
---|---|---|
committer | Adnan Begovic <adnan@cyngn.com> | 2015-11-23 14:26:11 -0800 |
commit | 261830431922efba4d33e19013380a5ca1266835 (patch) | |
tree | 67564a410121135592ab42bf4556b241945f7aa5 /src | |
parent | c9aebf6e83e6d4d996a1a3cea899c4a28792dfb7 (diff) | |
download | packages_apps_Settings-261830431922efba4d33e19013380a5ca1266835.zip packages_apps_Settings-261830431922efba4d33e19013380a5ca1266835.tar.gz packages_apps_Settings-261830431922efba4d33e19013380a5ca1266835.tar.bz2 |
Settings: Anonymous Statistics (CMStats)
CMStats is now integrated into settings.
Change-Id: Ice1b2c0c4882620eef774aca26b91a39d27f7b47
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/settings/Settings.java | 1 | ||||
-rw-r--r-- | src/com/android/settings/cmstats/AnonymousStats.java | 126 | ||||
-rw-r--r-- | src/com/android/settings/cmstats/PreviewData.java | 49 | ||||
-rw-r--r-- | src/com/android/settings/cmstats/ReportingService.java | 149 | ||||
-rw-r--r-- | src/com/android/settings/cmstats/ReportingServiceManager.java | 102 | ||||
-rw-r--r-- | src/com/android/settings/cmstats/Utilities.java | 78 |
6 files changed, 505 insertions, 0 deletions
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 06f0149..2f9901f 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -120,4 +120,5 @@ public class Settings extends SettingsActivity { public static class AppWriteSettingsActivity extends SettingsActivity { /* empty */ } public static class LiveDisplayActivity extends SettingsActivity { /* empty */ } public static class BlacklistSettingsActivity extends SettingsActivity { /* empty */ } + public static class AnonymousStatsActivity extends Settings { /* empty */ } } diff --git a/src/com/android/settings/cmstats/AnonymousStats.java b/src/com/android/settings/cmstats/AnonymousStats.java new file mode 100644 index 0000000..4f1e10a --- /dev/null +++ b/src/com/android/settings/cmstats/AnonymousStats.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +public class AnonymousStats extends SettingsPreferenceFragment implements + DialogInterface.OnClickListener, DialogInterface.OnDismissListener, + Preference.OnPreferenceChangeListener { + private static final String VIEW_STATS = "pref_view_stats"; + + private static final String PREF_FILE_NAME = "CMStats"; + /* package */ static final String ANONYMOUS_OPT_IN = "pref_anonymous_opt_in"; + /* package */ static final String ANONYMOUS_LAST_CHECKED = "pref_anonymous_checked_in"; + + private CheckBoxPreference mEnableReporting; + private Preference mViewStats; + + private Dialog mOkDialog; + private boolean mOkClicked; + + private SharedPreferences mPrefs; + + public static SharedPreferences getPreferences(Context context) { + return context.getSharedPreferences(PREF_FILE_NAME, 0); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.anonymous_stats); + + mPrefs = getPreferences(getActivity()); + + PreferenceScreen prefSet = getPreferenceScreen(); + mEnableReporting = (CheckBoxPreference) prefSet.findPreference(ANONYMOUS_OPT_IN); + mViewStats = (Preference) prefSet.findPreference(VIEW_STATS); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference == mEnableReporting) { + if (mEnableReporting.isChecked()) { + // Display the confirmation dialog + mOkClicked = false; + if (mOkDialog != null) { + mOkDialog.dismiss(); + } + mOkDialog = new AlertDialog.Builder(getActivity()) + .setMessage(R.string.anonymous_statistics_warning) + .setTitle(R.string.anonymous_statistics_warning_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(android.R.string.yes, this) + .setNeutralButton(R.string.anonymous_learn_more, this) + .setNegativeButton(android.R.string.no, this) + .show(); + mOkDialog.setOnDismissListener(this); + } else { + // Disable reporting + mPrefs.edit().putBoolean(ANONYMOUS_OPT_IN, false).apply(); + } + } else if (preference == mViewStats) { + // Display the stats page + Uri uri = Uri.parse("http://stats.cyanogenmod.org"); + startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } else { + // If we didn't handle it, let preferences handle it. + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + return true; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + return false; + } + + @Override + public void onDismiss(DialogInterface dialog) { + if (!mOkClicked) { + mEnableReporting.setChecked(false); + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + mOkClicked = true; + mPrefs.edit().putBoolean(ANONYMOUS_OPT_IN, true).apply(); + ReportingServiceManager.launchService(getActivity()); + } else if (which == DialogInterface.BUTTON_NEGATIVE) { + mEnableReporting.setChecked(false); + } else { + Uri uri = Uri.parse("http://www.cyanogenmod.org/blog/cmstats-what-it-is-and-why-you-should-opt-in"); + startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } + } +} diff --git a/src/com/android/settings/cmstats/PreviewData.java b/src/com/android/settings/cmstats/PreviewData.java new file mode 100644 index 0000000..7a78aea --- /dev/null +++ b/src/com/android/settings/cmstats/PreviewData.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.content.Context; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +public class PreviewData extends SettingsPreferenceFragment { + private static final String UNIQUE_ID = "preview_id"; + private static final String DEVICE = "preview_device"; + private static final String VERSION = "preview_version"; + private static final String COUNTRY = "preview_country"; + private static final String CARRIER = "preview_carrier"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.preview_data); + + final PreferenceScreen prefSet = getPreferenceScreen(); + final Context context = getActivity(); + + prefSet.findPreference(UNIQUE_ID).setSummary(Utilities.getUniqueID(context)); + prefSet.findPreference(DEVICE).setSummary(Utilities.getDevice()); + prefSet.findPreference(VERSION).setSummary(Utilities.getModVersion()); + prefSet.findPreference(COUNTRY).setSummary(Utilities.getCountryCode(context)); + prefSet.findPreference(CARRIER).setSummary(Utilities.getCarrier(context)); + } +} diff --git a/src/com/android/settings/cmstats/ReportingService.java b/src/com/android/settings/cmstats/ReportingService.java new file mode 100644 index 0000000..c76dc04 --- /dev/null +++ b/src/com/android/settings/cmstats/ReportingService.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.IBinder; +import android.util.Log; + +import com.android.settings.R; +import com.android.settings.Settings; + +import com.google.analytics.tracking.android.GoogleAnalytics; +import com.google.analytics.tracking.android.Tracker; + +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ReportingService extends Service { + /* package */ static final String TAG = "CMStats"; + + private StatsUploadTask mTask; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand (Intent intent, int flags, int startId) { + Log.d(TAG, "User has opted in -- reporting."); + + if (mTask == null || mTask.getStatus() == AsyncTask.Status.FINISHED) { + mTask = new StatsUploadTask(); + mTask.execute(); + } + + return Service.START_REDELIVER_INTENT; + } + + private class StatsUploadTask extends AsyncTask<Void, Void, Boolean> { + @Override + protected Boolean doInBackground(Void... params) { + String deviceId = Utilities.getUniqueID(getApplicationContext()); + String deviceName = Utilities.getDevice(); + String deviceVersion = Utilities.getModVersion(); + String deviceCountry = Utilities.getCountryCode(getApplicationContext()); + String deviceCarrier = Utilities.getCarrier(getApplicationContext()); + String deviceCarrierId = Utilities.getCarrierId(getApplicationContext()); + + Log.d(TAG, "SERVICE: Device ID=" + deviceId); + Log.d(TAG, "SERVICE: Device Name=" + deviceName); + Log.d(TAG, "SERVICE: Device Version=" + deviceVersion); + Log.d(TAG, "SERVICE: Country=" + deviceCountry); + Log.d(TAG, "SERVICE: Carrier=" + deviceCarrier); + Log.d(TAG, "SERVICE: Carrier ID=" + deviceCarrierId); + + // report to google analytics + GoogleAnalytics ga = GoogleAnalytics.getInstance(ReportingService.this); + Tracker tracker = ga.getTracker(getString(R.string.ga_trackingId)); + tracker.sendEvent(deviceName, deviceVersion, deviceCountry, null); + + // this really should be set at build time... + // format of version should be: + // version[-date-type]-device + String[] parts = deviceVersion.split("-"); + String deviceVersionNoDevice = null; + if (parts.length == 2) { + deviceVersionNoDevice = parts[0]; + } else if (parts.length == 4) { + deviceVersionNoDevice = parts[0] + "-" + parts[2]; + } + + if (deviceVersionNoDevice != null) { + tracker.sendEvent("checkin", deviceName, deviceVersionNoDevice, null); + } + tracker.close(); + + // report to the cmstats service + HttpClient httpClient = new DefaultHttpClient(); + HttpPost httpPost = new HttpPost("http://stats.cyanogenmod.org/submit"); + boolean success = false; + + try { + List<NameValuePair> kv = new ArrayList<NameValuePair>(5); + kv.add(new BasicNameValuePair("device_hash", deviceId)); + kv.add(new BasicNameValuePair("device_name", deviceName)); + kv.add(new BasicNameValuePair("device_version", deviceVersion)); + kv.add(new BasicNameValuePair("device_country", deviceCountry)); + kv.add(new BasicNameValuePair("device_carrier", deviceCarrier)); + kv.add(new BasicNameValuePair("device_carrier_id", deviceCarrierId)); + + httpPost.setEntity(new UrlEncodedFormEntity(kv)); + httpClient.execute(httpPost); + + success = true; + } catch (IOException e) { + Log.w(TAG, "Could not upload stats checkin", e); + } + + return success; + } + + @Override + protected void onPostExecute(Boolean result) { + final Context context = ReportingService.this; + long interval; + + if (result) { + final SharedPreferences prefs = AnonymousStats.getPreferences(context); + prefs.edit().putLong(AnonymousStats.ANONYMOUS_LAST_CHECKED, + System.currentTimeMillis()).apply(); + // use set interval + interval = 0; + } else { + // error, try again in 3 hours + interval = 3L * 60L * 60L * 1000L; + } + + ReportingServiceManager.setAlarm(context, interval); + stopSelf(); + } + } +} diff --git a/src/com/android/settings/cmstats/ReportingServiceManager.java b/src/com/android/settings/cmstats/ReportingServiceManager.java new file mode 100644 index 0000000..354d0d3 --- /dev/null +++ b/src/com/android/settings/cmstats/ReportingServiceManager.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.util.Log; + +public class ReportingServiceManager extends BroadcastReceiver { + private static final long MILLIS_PER_HOUR = 60L * 60L * 1000L; + private static final long MILLIS_PER_DAY = 24L * MILLIS_PER_HOUR; + private static final long UPDATE_INTERVAL = 1L * MILLIS_PER_DAY; + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { + setAlarm(context, 0); + } else { + launchService(context); + } + } + + public static void setAlarm(Context context, long millisFromNow) { + SharedPreferences prefs = AnonymousStats.getPreferences(context); + boolean optedIn = prefs.getBoolean(AnonymousStats.ANONYMOUS_OPT_IN, true); + if (!optedIn) { + return; + } + + if (millisFromNow <= 0) { + long lastSynced = prefs.getLong(AnonymousStats.ANONYMOUS_LAST_CHECKED, 0); + if (lastSynced == 0) { + // never synced, so let's fake out that the last sync was just now. + // this will allow the user tFrame time to opt out before it will start + // sending up anonymous stats. + lastSynced = System.currentTimeMillis(); + prefs.edit().putLong(AnonymousStats.ANONYMOUS_LAST_CHECKED, lastSynced).apply(); + Log.d(ReportingService.TAG, "Set alarm for first sync."); + } + millisFromNow = (lastSynced + UPDATE_INTERVAL) - System.currentTimeMillis(); + } + + Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); + intent.setClass(context, ReportingServiceManager.class); + + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + millisFromNow, + PendingIntent.getBroadcast(context, 0, intent, 0)); + Log.d(ReportingService.TAG, "Next sync attempt in : " + millisFromNow / MILLIS_PER_HOUR + " hours"); + } + + public static void launchService(Context context) { + ConnectivityManager cm = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + if (networkInfo == null || !networkInfo.isConnected()) { + return; + } + + SharedPreferences prefs = AnonymousStats.getPreferences(context); + boolean optedIn = prefs.getBoolean(AnonymousStats.ANONYMOUS_OPT_IN, true); + if (!optedIn) { + return; + } + long lastSynced = prefs.getLong(AnonymousStats.ANONYMOUS_LAST_CHECKED, 0); + if (lastSynced == 0) { + setAlarm(context, 0); + return; + } + long timeLeft = System.currentTimeMillis() - lastSynced; + if (timeLeft < UPDATE_INTERVAL) { + Log.d(ReportingService.TAG, "Waiting for next sync : " + timeLeft / MILLIS_PER_HOUR + " hours"); + return; + } + + Intent intent = new Intent(); + intent.setClass(context, ReportingService.class); + context.startService(intent); + } +} diff --git a/src/com/android/settings/cmstats/Utilities.java b/src/com/android/settings/cmstats/Utilities.java new file mode 100644 index 0000000..496f398 --- /dev/null +++ b/src/com/android/settings/cmstats/Utilities.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.content.Context; +import android.os.SystemProperties; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +import java.math.BigInteger; +import java.net.NetworkInterface; +import java.security.MessageDigest; + +public class Utilities { + public static String getUniqueID(Context context) { + final String id = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); + return digest(context.getPackageName() + id); + } + + public static String getCarrier(Context context) { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String carrier = tm.getNetworkOperatorName(); + if (TextUtils.isEmpty(carrier)) { + carrier = "Unknown"; + } + return carrier; + } + + public static String getCarrierId(Context context) { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String carrierId = tm.getNetworkOperator(); + if (TextUtils.isEmpty(carrierId)) { + carrierId = "0"; + } + return carrierId; + } + + public static String getCountryCode(Context context) { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String countryCode = tm.getNetworkCountryIso(); + if (TextUtils.isEmpty(countryCode)) { + countryCode = "Unknown"; + } + return countryCode; + } + + public static String getDevice() { + return SystemProperties.get("ro.cm.device"); + } + + public static String getModVersion() { + return SystemProperties.get("ro.cm.version"); + } + + public static String digest(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + return new BigInteger(1, md.digest(input.getBytes())).toString(16).toUpperCase(); + } catch (Exception e) { + return null; + } + } +} |