From 79a94cb29863922e6c1dcf319c2ab6b28459ccef Mon Sep 17 00:00:00 2001 From: Stephen Bird Date: Wed, 11 May 2016 13:58:10 -0700 Subject: Metrics fixes and switching to job sched Some metrics were not being properly aggregated and sent. Change-Id: I7b1973c130b57a9c23420383fc144ded4c074ee4 Ticket: CD-629 --- AndroidManifest_cm.xml | 7 +- .../android/dialer/incall/InCallMetricsHelper.java | 291 +++++++++----------- .../android/dialer/incall/InCallMetricsJob.java | 306 +++++++++++++++++++++ .../dialer/incall/InCallMetricsReceiver.java | 228 --------------- 4 files changed, 444 insertions(+), 388 deletions(-) create mode 100644 src/com/android/dialer/incall/InCallMetricsJob.java delete mode 100644 src/com/android/dialer/incall/InCallMetricsReceiver.java diff --git a/AndroidManifest_cm.xml b/AndroidManifest_cm.xml index 3cebac637..af6a83188 100644 --- a/AndroidManifest_cm.xml +++ b/AndroidManifest_cm.xml @@ -68,11 +68,8 @@ android:noHistory="true" android:theme="@style/Theme.Material.Light.Dialog.NoTitle" /> - - - - - + 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 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 data) { + HashMap 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 data, String location) { + /* package */ static void storeEvent(ComponentName cn, Categories category, Events event, + HashMap 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,97 +241,48 @@ public class InCallMetricsHelper { * @param p parameter * @return new count of the item. */ - private static int increaseCount(HashMap hashmap, Parameters p) { + private static String increaseCount(HashMap 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 getStoredEventParams(ComponentName cn, - Categories category, - Events event) { + /* package*/ static HashMap 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 eventMap = new HashMap<>(); + HashMap eventMap = new HashMap<>(); Map map = sp.getAll(); for(Map.Entry 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 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 @@ -322,60 +296,67 @@ public class InCallMetricsHelper { // this is only null if we do not have a sim card. return; } - HashMap metricsData = getStoredEventParams(cn, cat, event); + HashMap metricsData = getStoredEventParams(cn, cat, event); metricsData.put(param, increaseCount(metricsData,param)); storeEvent(cn, cat, event, metricsData); } - @VisibleForTesting - /* package */ static HashMap> getHashOfHashOfItems( - Map map) { - HashMap> items = new HashMap<>(); - for(Map.Entry entry : map.entrySet()){ + /** + * Prepares all our metrics for sending. + */ + public static HashMap 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 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 unBuiltEvents = new HashMap<>(); - String eventKey = component.flattenToShortString() + DELIMIT + category.value() + DELIMIT - + event.value(); + for(Map.Entry entry : map.entrySet()){ + String[] keyParts = entry.getKey().split(DELIMIT); - if (!items.containsKey(eventKey)) { - items.put(eventKey, new HashMap()); - } + 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 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> 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 map = sp.getAll(); + SharedPreferences.Editor editor = sp.edit(); + + for(Map.Entry 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 params = items.get(key); - sendEvent(context, category, event, params, component); } + editor.apply(); } - - } diff --git a/src/com/android/dialer/incall/InCallMetricsJob.java b/src/com/android/dialer/incall/InCallMetricsJob.java new file mode 100644 index 000000000..f406b8318 --- /dev/null +++ b/src/com/android/dialer/incall/InCallMetricsJob.java @@ -0,0 +1,306 @@ +package com.android.dialer.incall; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +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.internal.annotations.VisibleForTesting; +import com.cyanogen.ambient.analytics.Event; + +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +/** + * 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 + * as events logged to shared preferences. + * + * 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 InCallMetricsJob extends JobService { + + private static final String TAG = InCallMetricsJob.class.getSimpleName(); + private static final boolean DEBUG = false; + + private static final String CALL_COUNT_SUCCESS = "call_count_success"; + private static final String CALL_COUNT_FAILURE = "call_count_failure"; + private static final String CALL_IS_PSTN = "call_is_pstn"; + private static final String ORIGIN = "origin"; + + private InCallMetricsTask mUploadTask; + + @Override + 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 { + + JobParameters mMetricsJobParams; + + public InCallMetricsTask(JobParameters params) { + this.mMetricsJobParams = params; + } + + 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 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 + } + + @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 + /* package */ static void lookupCallsSince(long time, + ContentResolver contentResolver, Context context) { + + Uri uri = CallLogConstants.CONTENT_ALL_URI.buildUpon().build(); + + String[] projection = new String[] { + CallLog.Calls.DURATION, + CallLog.Calls.DATE, + CallLogConstants.PLUGIN_PACKAGE_NAME, + CallLog.Calls.NUMBER, + ORIGIN + }; + + + String where = CallLog.Calls.DATE + " >= ?"; + + String[] args = new String[] { + String.valueOf(time) + }; + + Cursor c = contentResolver.query(uri, projection, where, args, null); + + // Ensure that our ContentProvider was available to return a cursor + if (c == null) { + return; + } + + HashMap> keys = new HashMap<>(); + + while (c.moveToNext()) { + String pluginComponent = + c.getString(c.getColumnIndex(CallLogConstants.PLUGIN_PACKAGE_NAME)); + + String callOrigin = + c.getString(c.getColumnIndex(ORIGIN)); + + long callDuration = + c.getLong(c.getColumnIndex(CallLog.Calls.DURATION)); + + boolean isPSTN = + PhoneNumberUtils.isGlobalPhoneNumber( + c.getString( + c.getColumnIndex(CallLog.Calls.NUMBER))); + + if (callOrigin == null) { + // mark this as an unknown location call; + callOrigin = "unknown"; + } + + if (pluginComponent == null) { + // TODO: figure out which sim the call was placed through + pluginComponent = "sim"; + } + pluginComponent += InCallMetricsHelper.DELIMIT + callOrigin; + HashMap data; + long call = 1; + long calls_success = 0; + long calls_failure = 0; + if (keys.containsKey(pluginComponent)) { + data = keys.get(pluginComponent); + if (data.containsKey(CallLog.Calls.DURATION)) { + // Determine if the call was successfully connected to another device + // for a period of time. (includes: people, voicemails, machines) + // TODO: figure out how to fix false positive for calls that immediately end + // once they are started + if (callDuration > 0) { + // Call succeeded, increase by one + calls_success += call; + if (data.containsKey(CALL_COUNT_SUCCESS)) { + // add previous success to our current count; + calls_success += (long) data.get(CALL_COUNT_SUCCESS); + } + if (data.containsKey(CALL_COUNT_FAILURE)) { + // call success, keep old failure count w/o modification + calls_failure = (long) data.get(CALL_COUNT_FAILURE); + } + } else { + // Call failed to connect, increase by one + calls_failure += call; + if (data.containsKey(CALL_COUNT_SUCCESS)) { + // Not a successful call, keep old count + calls_success = (long) data.get(CALL_COUNT_SUCCESS); + } + if (data.containsKey(CALL_COUNT_FAILURE)) { + // add previous failures to our current count of one + calls_failure += (long) data.get(CALL_COUNT_FAILURE); + } + } + callDuration += (long) data.get(CallLog.Calls.DURATION); + } + } else { + data = new HashMap<>(); + if (callDuration > 0) { + calls_success = call; + } else { + calls_failure = call; + } + } + data.put(CallLog.Calls.DURATION, callDuration); + data.put(CALL_COUNT_SUCCESS, calls_success); + data.put(CALL_COUNT_FAILURE, calls_failure); + data.put(CALL_IS_PSTN, isPSTN); + keys.put(pluginComponent, data); + } + + c.close(); + + for (String key : keys.keySet()) { + // Shippit + HashMap value = keys.get(key); + String[] keySplit = key.split(InCallMetricsHelper.DELIMIT); + String pluginComponent = keySplit[0]; + String callOrigin = keySplit[1]; + long callDuration = (Long)value.get(CallLog.Calls.DURATION); + long callCountSuccess = (Long)value.get(CALL_COUNT_SUCCESS); + long callCountFailure = (Long)value.get(CALL_COUNT_FAILURE); + boolean isPSTN = (Boolean) value.get(CALL_IS_PSTN); + + if (DEBUG) { + Log.v(TAG, "Method:" + pluginComponent + + " Origin:" + callOrigin + + " CountSuccess:" + callCountSuccess + + " CountFailure:" + callCountFailure + + " Duration:" + callDuration); + } + + HashMap params = new HashMap<>(); + params.put(InCallMetricsHelper.Parameters.OUTGOING_SUCCESS, callCountSuccess); + params.put(InCallMetricsHelper.Parameters.OUTGOING_FAIL, callCountFailure); + params.put(InCallMetricsHelper.Parameters.OUTGOING_TOTAL_DURATION, callDuration); + params.put(InCallMetricsHelper.Parameters.PROVIDER_NAME, pluginComponent); + params.put(InCallMetricsHelper.Parameters.ACTION_LOCATION, callOrigin); + + InCallMetricsHelper.Events event; + if (pluginComponent.startsWith("sim")) { + event = InCallMetricsHelper.Events.CALL_SIM_PSTN; + } else { + if (isPSTN) { + event = InCallMetricsHelper.Events.CALL_PROVIDER_VOICE; + } else { + event = InCallMetricsHelper.Events.CALL_PROVIDER_PSTN; + } + } + + InCallMetricsHelper.sendEvent(context, InCallMetricsHelper.Categories.CALLS, + event, params, ComponentName.unflattenFromString(pluginComponent)); + + } + } +} + diff --git a/src/com/android/dialer/incall/InCallMetricsReceiver.java b/src/com/android/dialer/incall/InCallMetricsReceiver.java deleted file mode 100644 index 196202f6d..000000000 --- a/src/com/android/dialer/incall/InCallMetricsReceiver.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.android.dialer.incall; - -import android.app.IntentService; -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.provider.CallLog; -import android.telephony.PhoneNumberUtils; -import android.util.Log; -import com.android.dialer.util.TelecomUtil; -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; - -/** - * InCallMetricsReceiver 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 - * as events logged to shared preferences. - * - * 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()); - } - - private static final String TAG = InCallMetricsHelper.class.getSimpleName(); - private static final boolean DEBUG = false; - - private static final String CALL_COUNT_SUCCESS = "call_count_success"; - private static final String CALL_COUNT_FAILURE = "call_count_failure"; - private static final String CALL_IS_PSTN = "call_is_pstn"; - private static final String ORIGIN = "origin"; - - @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()); - } - - // Send stored Incall Specific events - if (CMSettings.Secure.getInt(getApplicationContext().getContentResolver(), - CMSettings.Secure.STATS_COLLECTION, 1) != 0) { - InCallMetricsHelper.prepareToSend(getApplicationContext()); - } - } - - public InCallMetricsReceiver(String name) { - super(name); - } - - @VisibleForTesting - /* package */ static void lookupCallsSince(long time, - ContentResolver contentResolver, Context context) { - - Uri uri = CallLogConstants.CONTENT_ALL_URI.buildUpon().build(); - - String[] projection = new String[] { - CallLog.Calls.DURATION, - CallLog.Calls.DATE, - CallLogConstants.PLUGIN_PACKAGE_NAME, - CallLog.Calls.NUMBER, - ORIGIN - }; - - - String where = CallLog.Calls.DATE + " >= ?"; - - String[] args = new String[] { - String.valueOf(time) - }; - - Cursor c = contentResolver.query(uri, projection, where, args, null); - - // Ensure that our ContentProvider was available to return a cursor - if (c == null) { - return; - } - - HashMap> keys = new HashMap<>(); - - while (c.moveToNext()) { - String pluginComponent = - c.getString(c.getColumnIndex(CallLogConstants.PLUGIN_PACKAGE_NAME)); - - String callOrigin = - c.getString(c.getColumnIndex(ORIGIN)); - - long callDuration = - c.getLong(c.getColumnIndex(CallLog.Calls.DURATION)); - - boolean isPSTN = - PhoneNumberUtils.isGlobalPhoneNumber( - c.getString( - c.getColumnIndex(CallLog.Calls.NUMBER))); - - if (callOrigin == null) { - // mark this as an unknown location call; - callOrigin = "unknown"; - } - - if (pluginComponent == null) { - // TODO: figure out which sim the call was placed through - pluginComponent = "sim"; - } - pluginComponent += InCallMetricsHelper.DELIMIT + callOrigin; - HashMap data; - long call = 1; - long calls_success = 0; - long calls_failure = 0; - if (keys.containsKey(pluginComponent)) { - data = keys.get(pluginComponent); - if (data.containsKey(CallLog.Calls.DURATION)) { - // Determine if the call was successfully connected to another device - // for a period of time. (includes: people, voicemails, machines) - // TODO: figure out how to fix false positive for calls that immediately end - // once they are started - if (callDuration > 0) { - // Call succeeded, increase by one - calls_success += call; - if (data.containsKey(CALL_COUNT_SUCCESS)) { - // add previous success to our current count; - calls_success += (long) data.get(CALL_COUNT_SUCCESS); - } - if (data.containsKey(CALL_COUNT_FAILURE)) { - // call success, keep old failure count w/o modification - calls_failure = (long) data.get(CALL_COUNT_FAILURE); - } - } else { - // Call failed to connect, increase by one - calls_failure += call; - if (data.containsKey(CALL_COUNT_SUCCESS)) { - // Not a successful call, keep old count - calls_success = (long) data.get(CALL_COUNT_SUCCESS); - } - if (data.containsKey(CALL_COUNT_FAILURE)) { - // add previous failures to our current count of one - calls_failure += (long) data.get(CALL_COUNT_FAILURE); - } - } - callDuration += (long) data.get(CallLog.Calls.DURATION); - } - } else { - data = new HashMap<>(); - if (callDuration > 0) { - calls_success = call; - } else { - calls_failure = call; - } - } - data.put(CallLog.Calls.DURATION, callDuration); - data.put(CALL_COUNT_SUCCESS, calls_success); - data.put(CALL_COUNT_FAILURE, calls_failure); - data.put(CALL_IS_PSTN, isPSTN); - keys.put(pluginComponent, data); - } - - c.close(); - - for (String key : keys.keySet()) { - // Shippit - HashMap value = keys.get(key); - String[] keySplit = key.split(InCallMetricsHelper.DELIMIT); - String pluginComponent = keySplit[0]; - String callOrigin = keySplit[1]; - long callDuration = (Long)value.get(CallLog.Calls.DURATION); - long callCountSuccess = (Long)value.get(CALL_COUNT_SUCCESS); - long callCountFailure = (Long)value.get(CALL_COUNT_FAILURE); - boolean isPSTN = (Boolean) value.get(CALL_IS_PSTN); - - if (DEBUG) { - Log.v(TAG, "Method:" + pluginComponent - + " Origin:" + callOrigin - + " CountSuccess:" + callCountSuccess - + " CountFailure:" + callCountFailure - + " Duration:" + callDuration); - } - - HashMap params = new HashMap<>(); - params.put(InCallMetricsHelper.Parameters.OUTGOING_SUCCESS, callCountSuccess); - params.put(InCallMetricsHelper.Parameters.OUTGOING_FAIL, callCountFailure); - params.put(InCallMetricsHelper.Parameters.OUTGOING_TOTAL_DURATION, callDuration); - params.put(InCallMetricsHelper.Parameters.PROVIDER_NAME, pluginComponent); - params.put(InCallMetricsHelper.Parameters.ACTION_LOCATION, callOrigin); - - InCallMetricsHelper.Events event; - if (pluginComponent.startsWith("sim")) { - event = InCallMetricsHelper.Events.CALL_SIM_PSTN; - } else { - if (isPSTN) { - event = InCallMetricsHelper.Events.CALL_PROVIDER_VOICE; - } else { - event = InCallMetricsHelper.Events.CALL_PROVIDER_PSTN; - } - } - - InCallMetricsHelper.sendEvent(context, InCallMetricsHelper.Categories.CALLS, - event, params, ComponentName.unflattenFromString(pluginComponent)); - - } - } -} - -- cgit v1.2.3