diff options
author | Stephen Bird <sbird@cyngn.com> | 2016-05-11 13:58:10 -0700 |
---|---|---|
committer | Stephen Bird <sbird@cyngn.com> | 2016-05-13 12:40:16 -0700 |
commit | 79a94cb29863922e6c1dcf319c2ab6b28459ccef (patch) | |
tree | 5f4bbed14d9dbf2bb1abfa330217b85c0cc28b40 /src | |
parent | c64a3f4ed76dbb69418a090551e1322bd74864c4 (diff) | |
download | android_packages_apps_Dialer-79a94cb29863922e6c1dcf319c2ab6b28459ccef.tar.gz android_packages_apps_Dialer-79a94cb29863922e6c1dcf319c2ab6b28459ccef.tar.bz2 android_packages_apps_Dialer-79a94cb29863922e6c1dcf319c2ab6b28459ccef.zip |
Metrics fixes and switching to job sched
Some metrics were not being properly aggregated and sent.
Change-Id: I7b1973c130b57a9c23420383fc144ded4c074ee4
Ticket: CD-629
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/dialer/incall/InCallMetricsHelper.java | 291 | ||||
-rw-r--r-- | src/com/android/dialer/incall/InCallMetricsJob.java (renamed from src/com/android/dialer/incall/InCallMetricsReceiver.java) | 142 |
2 files changed, 246 insertions, 187 deletions
diff --git a/src/com/android/dialer/incall/InCallMetricsHelper.java b/src/com/android/dialer/incall/InCallMetricsHelper.java index 638abd854..c39a36fb6 100644 --- a/src/com/android/dialer/incall/InCallMetricsHelper.java +++ b/src/com/android/dialer/incall/InCallMetricsHelper.java @@ -1,13 +1,14 @@ package com.android.dialer.incall; import android.app.AlarmManager; -import android.app.PendingIntent; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; +import android.text.TextUtils; import android.util.Log; -import com.android.dialer.DialtactsActivity; + import com.android.internal.annotations.VisibleForTesting; import com.android.phone.common.ambient.AmbientConnection; import com.android.phone.common.incall.DialerDataSubscription; @@ -23,16 +24,19 @@ public class InCallMetricsHelper { private static final String TAG = InCallMetricsHelper.class.getSimpleName(); private static final boolean DEBUG = false; - public static final String CATEGORY_BASE = "dialernext.incall."; + public static final String CATEGORY_BASE = "dialer.incall."; public static final String METRICS_SHARED_PREFERENCES = "metrics_shared_preferences"; public static final String DELIMIT = ":"; - static final int COMPONENT_NAME = 0; - static final int CATEGORY = 1; - static final int EVENT = 2; - static final int PARAMS = 3; - private static final int REGULAR_KEY = 4; - private static final int SHORT_KEY = 4; + // Positions in our shared preference keys + private static final int POS_COMPONENT_NAME = 0; + private static final int POS_CATEGORY_VALUE = 1; + private static final int POS_EVENT_VALUE = 2; + private static final int POS_PARAM_VALUE = 3; + + private static final int METRICS_JOB_ID = 1; + + public static final String PARAM_PROVIDER = "provider"; // The TODO/Done flags denote which is convered by testing. These will be removed in the future. public enum Categories { @@ -132,23 +136,41 @@ public class InCallMetricsHelper { InCallMetricsHelper helper = getInstance(); helper.mContext = context; - AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - Intent i = new Intent(context, InCallMetricsReceiver.class); - - PendingIntent pi = PendingIntent.getService(context, 0, i, 0); - am.setInexactRepeating(AlarmManager.RTC_WAKEUP, 1000L, // every 24 hours - AlarmManager.INTERVAL_DAY, pi); - } + JobScheduler jobScheduler + = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - /** - * Sends all events - * @param event - */ - private void sendAmbientEvent(Event event) { - if (DEBUG) { - Log.d(TAG, "Event: " + event.toString()); + if (jobScheduler != null) { + boolean jobExists = false; + for (JobInfo ji : jobScheduler.getAllPendingJobs()) { + if (ji.getId() != METRICS_JOB_ID) { + // Job exists + jobExists = true; + break; + } + } + if (!jobExists) { + // We need a job to send our aggregated events to our metrics service every 24 + // hours. + ComponentName jobComponent = new ComponentName(context, + InCallMetricsJob.class); + + JobInfo job = new JobInfo.Builder(METRICS_JOB_ID, jobComponent) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE) + .setPersisted(true) + .setPeriodic(AlarmManager.INTERVAL_DAY) + .setBackoffCriteria(AlarmManager.INTERVAL_FIFTEEN_MINUTES, + JobInfo.BACKOFF_POLICY_EXPONENTIAL) + .build(); + jobScheduler.schedule(job); + } else { + if (DEBUG) Log.d(TAG, "InCall job " + 1 + " already exists"); + } + } else { + if (DEBUG) { + Log.e(TAG, "Running on a device without JobScheduler." + + " InCall Metrics will fail to collect."); + } } - AnalyticsServices.AnalyticsApi.sendEvent(AmbientConnection.CLIENT.get(mContext), event); } /** @@ -158,7 +180,7 @@ public class InCallMetricsHelper { * @param params fields that are part of the event * @param cn associated with the plugin */ - public static Event sendEvent(Context c, Categories category, Events action, + public static void sendEvent(Context c, Categories category, Events action, HashMap<Parameters, Object> params, ComponentName cn) { Event.Builder event = new Event.Builder(CATEGORY_BASE + category.value(), action.value()); if (params != null && params.size() > 0) { @@ -167,9 +189,11 @@ public class InCallMetricsHelper { } } Event e = event.build(); + if (DEBUG) { + Log.d(TAG, "Event: " + event.toString()); + } InCallQueries.shipAnalyticsToPlugin(DialerDataSubscription.get(c).mClient, cn, e); - getInstance().sendAmbientEvent(e); - return e; + AnalyticsServices.AnalyticsApi.sendEvent(AmbientConnection.CLIENT.get(c), e); } /** @@ -180,21 +204,13 @@ public class InCallMetricsHelper { * @param data */ public static void storeEvent(ComponentName cn, Categories category, Events event, - HashMap<Parameters, Object> data) { + HashMap<Parameters, String> data) { storeEvent(cn, category, event, data, null); } @VisibleForTesting - public static String buildKey(ComponentName componentName, Categories category, - Events action, Parameters parameter) { - - return componentName.flattenToShortString() + DELIMIT + category.value() + DELIMIT + - action.value() + DELIMIT + parameter.value(); - - } - - private static void storeEvent(ComponentName cn, Categories category, Events event, - HashMap<Parameters, Object> data, String location) { + /* package */ static void storeEvent(ComponentName cn, Categories category, Events event, + HashMap<Parameters, String> data, String location) { SharedPreferences sp = getInstance().mContext.getSharedPreferences( METRICS_SHARED_PREFERENCES, Context.MODE_PRIVATE); @@ -202,12 +218,19 @@ public class InCallMetricsHelper { SharedPreferences.Editor editor = sp.edit(); for (Parameters param : data.keySet()) { - Object o = data.get(param); - String eventKey = buildKey(cn, category, event, param); - if (location != null) { - eventKey += DELIMIT + location; + StringBuilder sb = new StringBuilder(); + sb.append(cn.flattenToShortString()); // Add ComponentName String + sb.append(DELIMIT); + sb.append(category.value()); // Add our category value + sb.append(DELIMIT); + sb.append(event.value()); // Add our event value + sb.append(DELIMIT); + sb.append(param.value()); // add our param value + if (!TextUtils.isEmpty(location)) { + sb.append(DELIMIT); + sb.append(location); // add our location value } - putEditor(editor, eventKey, o); + editor.putString(sb.toString(), data.get(param)); } editor.apply(); } @@ -218,98 +241,49 @@ public class InCallMetricsHelper { * @param p parameter * @return new count of the item. */ - private static int increaseCount(HashMap<Parameters, Object> hashmap, Parameters p) { + private static String increaseCount(HashMap<Parameters, String> hashmap, Parameters p) { if (hashmap.containsKey(p)) { - return (int)hashmap.get(p) + 1; + return String.valueOf(Integer.valueOf(hashmap.get(p)) + 1); } else { - return 1; + return String.valueOf(1); } } /** - * Helper method for putting objects into a shared preference - * @param e - * @param key - * @param value + * Get the SharedPreferences events and output a hashmap for the event's values. + * + * @param componentName ComponentName who created the event + * + * @return HashMap of our params and their values. */ - private static void putEditor(SharedPreferences.Editor e, String key, Object value) { - if (value instanceof Integer) { - e.putInt(key, (int)value); - } else if (value instanceof String) { - e.putString(key, String.valueOf(value)); - } else if (value instanceof Boolean) { - e.putBoolean(key, (boolean)value); - } else if (value instanceof Double) { - e.putLong(key, ((Double) value).longValue()); - } else if (value instanceof Long) { - e.putLong(key, (long)value); - } - } - - /** - * Get the sharedpreferences events and output a hashmap for the event's values. - * @param cn - * @param category - * @param event - * @return - */ - public static HashMap<Parameters, Object> getStoredEventParams(ComponentName cn, - Categories category, - Events event) { + /* package*/ static HashMap<Parameters, String> getStoredEventParams( + ComponentName componentName, Categories category, Events event) { SharedPreferences sp = getInstance().mContext.getSharedPreferences( METRICS_SHARED_PREFERENCES, Context.MODE_PRIVATE); - String eventKey = cn.flattenToShortString() + DELIMIT + category.value() + DELIMIT + event.value(); + StringBuilder sb = new StringBuilder(); + sb.append(componentName.flattenToShortString()); // Add ComponentName String + sb.append(DELIMIT); + sb.append(category.value()); // Add our category value + sb.append(DELIMIT); + sb.append(event.value()); // Add our event value + sb.append(DELIMIT); - HashMap<Parameters, Object> eventMap = new HashMap<>(); + HashMap<Parameters, String> eventMap = new HashMap<>(); Map<String, ?> map = sp.getAll(); for(Map.Entry<String,?> entry : map.entrySet()) { - if (entry.getKey().startsWith(eventKey)) { + if (entry.getKey().startsWith(sb.toString())) { String[] keyParts = entry.getKey().split(DELIMIT); - String key; - if (keyParts.length > 4) { - key = keyParts[keyParts.length - 2]; - } else { - key = keyParts[keyParts.length - 1]; - } - eventMap.put(Parameters.valueOf(key), entry.getValue()); + Parameters key = Parameters.valueOf(keyParts[POS_PARAM_VALUE]); + eventMap.put(key, String.valueOf(entry.getValue())); } } return eventMap; } /** - * Helper method to increase the count of a specific parameter if the last action was not the - * same as the current action. - */ - public static void increaseCountOfMetricAfterValidate(ComponentName cn, Events event, - Categories cat, Parameters param) { - - String validationKey = cn.flattenToShortString() + DELIMIT + cat.value(); - - SharedPreferences preferences = getInstance().mContext - .getSharedPreferences(DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); - - SharedPreferences.Editor editor = preferences.edit(); - String lastEvent = preferences.getString(validationKey, null); - - if (lastEvent != null && lastEvent.equals(event.value())) { - return; - } else { - editor.putString(validationKey, event.value()); - } - - editor.apply(); - - - HashMap<Parameters, Object> metricsData = getStoredEventParams(cn, cat, event); - metricsData.put(param, increaseCount(metricsData,param)); - storeEvent(cn, cat, event, metricsData); - } - - /** * Helper method to increase the count of a specific parameter * @param cn * @param event @@ -322,60 +296,67 @@ public class InCallMetricsHelper { // this is only null if we do not have a sim card. return; } - HashMap<Parameters, Object> metricsData = getStoredEventParams(cn, cat, event); + HashMap<Parameters, String> metricsData = getStoredEventParams(cn, cat, event); metricsData.put(param, increaseCount(metricsData,param)); storeEvent(cn, cat, event, metricsData); } - @VisibleForTesting - /* package */ static HashMap<String, HashMap<Parameters, Object>> getHashOfHashOfItems( - Map<String, ?> map) { - HashMap<String, HashMap<Parameters, Object>> items = new HashMap<>(); - for(Map.Entry<String,?> entry : map.entrySet()){ + /** + * Prepares all our metrics for sending. + */ + public static HashMap<String, Event.Builder> getEventsToSend(Context c) { + SharedPreferences sp = c.getSharedPreferences(METRICS_SHARED_PREFERENCES, + Context.MODE_PRIVATE); - String[] keyParts = entry.getKey().split(DELIMIT); - if (keyParts.length < REGULAR_KEY) { - continue; - } + Map<String, ?> map = sp.getAll(); - ComponentName component = ComponentName.unflattenFromString(keyParts[COMPONENT_NAME]); - Categories category = Categories.valueOf(keyParts[CATEGORY]); - Events event = Events.valueOf(keyParts[EVENT]); - Parameters parm = Parameters.valueOf(keyParts[PARAMS]); + HashMap<String, Event.Builder> unBuiltEvents = new HashMap<>(); - String eventKey = component.flattenToShortString() + DELIMIT + category.value() + DELIMIT - + event.value(); + for(Map.Entry<String,?> entry : map.entrySet()){ + String[] keyParts = entry.getKey().split(DELIMIT); - if (!items.containsKey(eventKey)) { - items.put(eventKey, new HashMap<Parameters, Object>()); - } + if (keyParts.length == POS_PARAM_VALUE + 1) { + String componentString = keyParts[POS_COMPONENT_NAME]; + String eventCategory = keyParts[POS_CATEGORY_VALUE]; + String parameter = keyParts[POS_PARAM_VALUE]; + String eventAction = keyParts[POS_EVENT_VALUE]; + + StringBuilder sb = new StringBuilder(); + sb.append(componentString); // Add ComponentName String + sb.append(DELIMIT); + sb.append(eventCategory); // Add our category value + sb.append(DELIMIT); + sb.append(eventAction); // Add our event value + String eventKey = sb.toString(); + + Event.Builder eventBuilder; + if (unBuiltEvents.containsKey(eventKey)) { + eventBuilder = unBuiltEvents.get(eventKey); + } else { + eventBuilder = new Event.Builder(CATEGORY_BASE + eventCategory, eventAction); + eventBuilder.addField(PARAM_PROVIDER, componentString); + } - HashMap<Parameters, Object> params = items.get(eventKey); - params.put(parm, entry.getValue()); - items.put(eventKey, params); + eventBuilder.addField(parameter, String.valueOf(entry.getValue())); + unBuiltEvents.put(eventKey, eventBuilder); + } } - return items; + return unBuiltEvents; } - /** - * Prepares all our metrics for sending. - */ - static void prepareToSend(Context context) { - SharedPreferences sp = getInstance().mContext.getSharedPreferences( - METRICS_SHARED_PREFERENCES, Context.MODE_PRIVATE); - HashMap<String, HashMap<Parameters, Object>> items = getHashOfHashOfItems(sp.getAll()); - for (String key : items.keySet()) { - String[] keyParts = key.split(DELIMIT); - if (keyParts.length < SHORT_KEY) { - continue; + public static void clearEventData(Context c, String key) { + SharedPreferences sp = c.getSharedPreferences(METRICS_SHARED_PREFERENCES, + Context.MODE_PRIVATE); + + Map<String, ?> map = sp.getAll(); + SharedPreferences.Editor editor = sp.edit(); + + for(Map.Entry<String,?> entry : map.entrySet()){ + String storedKey = entry.getKey(); + if (storedKey.startsWith(key)) { + editor.remove(storedKey); } - ComponentName component = ComponentName.unflattenFromString(keyParts[COMPONENT_NAME]); - Categories category = Categories.valueOf(keyParts[CATEGORY]); - Events event = Events.valueOf(keyParts[EVENT]); - HashMap<Parameters, Object> params = items.get(key); - sendEvent(context, category, event, params, component); } + editor.apply(); } - - } diff --git a/src/com/android/dialer/incall/InCallMetricsReceiver.java b/src/com/android/dialer/incall/InCallMetricsJob.java index 196202f6d..f406b8318 100644 --- a/src/com/android/dialer/incall/InCallMetricsReceiver.java +++ b/src/com/android/dialer/incall/InCallMetricsJob.java @@ -1,33 +1,33 @@ package com.android.dialer.incall; -import android.app.IntentService; +import android.app.job.JobParameters; +import android.app.job.JobService; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.database.Cursor; import android.net.Uri; +import android.os.AsyncTask; import android.provider.CallLog; import android.telephony.PhoneNumberUtils; import android.util.Log; import com.android.dialer.util.TelecomUtil; +import com.android.phone.common.incall.DialerDataSubscription; +import com.android.phone.common.incall.api.InCallQueries; +import com.cyanogen.ambient.analytics.AnalyticsServices; +import com.cyanogen.ambient.common.api.Result; import com.cyanogen.ambient.incall.CallLogConstants; -import com.android.dialer.incall.InCallMetricsHelper; -import cyanogenmod.providers.CMSettings; import com.android.internal.annotations.VisibleForTesting; -import com.cyanogen.ambient.incall.CallLogConstants; import com.cyanogen.ambient.analytics.Event; -import com.android.dialer.incall.InCallMetricsHelper; -import cyanogenmod.providers.CMSettings; -import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; +import java.util.concurrent.TimeUnit; /** - * InCallMetricsReceiver is an aggregation and shipping service that is fired + * InCallMetricsJob is an aggregation and shipping service that is fired * once every 24 hours to pass Metrics to ModCore's analytics service. * * InCallMetrics is responsible for retrieving call data from the call log as well @@ -36,13 +36,9 @@ import java.util.HashMap; * InCall Metrics is used to send other data besides just InCallPlugin Metrics, * as we currently are aggregating sim calls for better UI/UX data; */ -public class InCallMetricsReceiver extends IntentService { - - public InCallMetricsReceiver() { - super(InCallMetricsReceiver.class.getSimpleName()); - } +public class InCallMetricsJob extends JobService { - private static final String TAG = InCallMetricsHelper.class.getSimpleName(); + private static final String TAG = InCallMetricsJob.class.getSimpleName(); private static final boolean DEBUG = false; private static final String CALL_COUNT_SUCCESS = "call_count_success"; @@ -50,28 +46,110 @@ public class InCallMetricsReceiver extends IntentService { private static final String CALL_IS_PSTN = "call_is_pstn"; private static final String ORIGIN = "origin"; + private InCallMetricsTask mUploadTask; + @Override - protected void onHandleIntent(Intent intent) { - // Get current time a day ago for call aggregation. - Date d = new Date(System.currentTimeMillis()); - Calendar calendar = Calendar.getInstance(); - calendar.setTime(d); - calendar.add(Calendar.DATE, -1); - d.setTime(calendar.getTime().getTime()); - - if (TelecomUtil.hasReadPhoneStatus(getApplicationContext())) { - lookupCallsSince(d.getTime(), getContentResolver(), getApplicationContext()); + public boolean onStartJob(JobParameters params) { + if (DEBUG) Log.v(TAG, "sending events"); + // Send stored Incall Specific events + + mUploadTask = new InCallMetricsTask(params); + mUploadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void) null); + + // Running on another thread, return true. + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + // Cancel our async task + mUploadTask.cancel(true); + + // report that we should try again soon. + return true; + } + + class InCallMetricsTask extends AsyncTask<Void, Void, Boolean> { + + JobParameters mMetricsJobParams; + + public InCallMetricsTask(JobParameters params) { + this.mMetricsJobParams = params; } - // Send stored Incall Specific events - if (CMSettings.Secure.getInt(getApplicationContext().getContentResolver(), - CMSettings.Secure.STATS_COLLECTION, 1) != 0) { - InCallMetricsHelper.prepareToSend(getApplicationContext()); + private static final long TIMEOUT_MILLIS = 1000; + + @Override + protected Boolean doInBackground(Void... params) { + + if (TelecomUtil.hasReadPhoneStatus(getApplicationContext())) { + // Get current time a day ago for call aggregation. + Date d = new Date(System.currentTimeMillis()); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(d); + calendar.add(Calendar.DATE, -1); + d.setTime(calendar.getTime().getTime()); + + lookupCallsSince(d.getTime(), getContentResolver(), getApplicationContext()); + } + + HashMap<String, Event.Builder> eventsToSend + = InCallMetricsHelper.getEventsToSend(InCallMetricsJob.this); + + for (String key : eventsToSend.keySet()) { + + Event.Builder eventBuilder = eventsToSend.get(key); + + if (DEBUG) Log.v(TAG, "sending:" + eventBuilder.toString()); + + if (isCancelled()) { + return false; + } + + Event builtEvent = eventBuilder.build(); + + Result r = AnalyticsServices.AnalyticsApi.sendEvent( + DialerDataSubscription.get(InCallMetricsJob.this).mClient, + builtEvent) + .await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + + if (builtEvent.getCustomFields().containsKey(InCallMetricsHelper.PARAM_PROVIDER)) { + + ComponentName componentName = ComponentName.unflattenFromString( + (String)builtEvent.getCustomFields().get( + InCallMetricsHelper.PARAM_PROVIDER)); + + InCallQueries.shipAnalyticsToPlugin(DialerDataSubscription.get( + InCallMetricsJob.this).mClient, componentName, builtEvent); + } + + + // if any of our results were not successful, something is wrong. + // Stop this job for now. + if (!r.getStatus().isSuccess()) { + return false; + } + + // We sent all the data we had for this event to the database. So clear it from our + // SharedPreferences. + InCallMetricsHelper.clearEventData(InCallMetricsJob.this, key); + } + return true; + } + + @Override + protected void onCancelled() { + if (DEBUG) Log.w(TAG, "InCall Metrics Job Cancelled"); + // do nothing } - } - public InCallMetricsReceiver(String name) { - super(name); + @Override + protected void onPostExecute(Boolean success) { + if (DEBUG) Log.v(TAG, "was success: " + success); + + // attempt to reschedule if analytics service is unavailable for our events + jobFinished(mMetricsJobParams, !success /* reschedule */); + } } @VisibleForTesting |