summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVineet Patil <vineetpatil27.08@gmail.com>2016-09-29 17:11:23 -0700
committerVineet Patil <vineetpatil27.08@gmail.com>2016-09-30 16:33:57 -0700
commit7619c3c57f0e9cd41a26aa8170ecae05591ac466 (patch)
tree1d3cb28d43548370e41c8952684f70536ec9f7f5
parent57cadbe3de1b0e8faf1e8196b7d1df361f3960ca (diff)
downloadpackages_apps_Messaging-7619c3c57f0e9cd41a26aa8170ecae05591ac466.tar.gz
packages_apps_Messaging-7619c3c57f0e9cd41a26aa8170ecae05591ac466.tar.bz2
packages_apps_Messaging-7619c3c57f0e9cd41a26aa8170ecae05591ac466.zip
Adding metrics related to Ridesharing
Change-Id: I07720930146f3c6219ad77da222a5cf0218b20d8 Issue-Id: RIDE-344
-rw-r--r--AndroidManifest.xml9
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationActivity.java43
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageView.java8
-rw-r--r--src/com/cyanogenmod/messaging/util/MetricsHelper.java223
-rw-r--r--src/com/cyanogenmod/messaging/util/MetricsJob.java139
5 files changed, 422 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 09a3a8d..e7731cb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -55,6 +55,8 @@
<uses-permission android:name="com.cyanogen.ambient.permission.BIND_RIDEINPROGRESS_SERVICE" />
<uses-permission android:name="com.cyanogen.ambient.permission.BIND_RIDESHARING_SERVICE" />
+ <uses-permission android:name="android.permission.BIND_JOB_SERVICE" />
+
<!-- Optional features -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
@@ -552,6 +554,13 @@
android:launchMode="singleTop"
android:configChanges="orientation|screenSize|keyboardHidden"
android:label="@string/sim_manage_messages_title" />
+
+ <meta-data android:name="com.cyanogen.ambient.analytics.key"
+ android:value="6l2dXt9DFioFa1Mfb4eZsM9l87Rl9hp1UL75tO9w"/>
+
+ <service android:name="com.cyanogenmod.messaging.util.MetricsJob"
+ android:permission="android.permission.BIND_JOB_SERVICE" />
+
</application>
</manifest>
diff --git a/src/com/android/messaging/ui/conversation/ConversationActivity.java b/src/com/android/messaging/ui/conversation/ConversationActivity.java
index 66310ea..20f857d 100644
--- a/src/com/android/messaging/ui/conversation/ConversationActivity.java
+++ b/src/com/android/messaging/ui/conversation/ConversationActivity.java
@@ -16,14 +16,20 @@
package com.android.messaging.ui.conversation;
+import android.app.AlarmManager;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.text.TextUtils;
+import android.util.Log;
import android.view.MenuItem;
import com.android.messaging.R;
@@ -42,9 +48,13 @@ import com.android.messaging.util.LogUtil;
import com.android.messaging.util.OsUtil;
import com.android.messaging.util.UiUtils;
+import com.cyanogenmod.messaging.util.MetricsJob;
+
public class ConversationActivity extends BugleActionBarActivity
implements ContactPickerFragmentHost, ConversationFragmentHost,
ConversationActivityUiStateHost {
+ public static final String TAG = "ConversationActivity";
+
public static final int FINISH_RESULT_CODE = 1;
private static final String SAVED_INSTANCE_STATE_UI_STATE_KEY = "uistate";
@@ -117,6 +127,39 @@ public class ConversationActivity extends BugleActionBarActivity
UIIntents.get().launchFullScreenVideoViewer(this, Uri.parse(extraToDisplay));
}
}
+
+ // Schedule a Job for Metrics Service
+ JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+ if (jobScheduler != null) {
+ boolean jobExists = false;
+ for (JobInfo ji : jobScheduler.getAllPendingJobs()) {
+ if (ji.getId() != MetricsJob.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.
+ // As long as this service has been used, we know we'll need this data.
+ ComponentName jobComponent = new ComponentName(this, MetricsJob.class);
+
+ JobInfo job = new JobInfo.Builder(MetricsJob.METRICS_JOB_ID, jobComponent)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setPersisted(true)
+ .setPeriodic(AlarmManager.INTERVAL_DAY)
+ .setBackoffCriteria(AlarmManager.INTERVAL_FIFTEEN_MINUTES,
+ JobInfo.BACKOFF_POLICY_EXPONENTIAL)
+ .build();
+ jobScheduler.schedule(job);
+ } else {
+ Log.v(TAG, "Messaging Metrics job " + MetricsJob.METRICS_JOB_ID + " already exists");
+ }
+ } else {
+ Log.e(TAG, "Running on a device without JobScheduler." +
+ " Messaging Metrics will fail to collect.");
+ }
}
@Override
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageView.java b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
index 94a8fe0..bf82983 100644
--- a/src/com/android/messaging/ui/conversation/ConversationMessageView.java
+++ b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
@@ -17,6 +17,7 @@
package com.android.messaging.ui.conversation;
import android.content.Context;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
@@ -83,6 +84,7 @@ import com.cyanogen.lookup.phonenumber.response.LookupResponse;
import com.cyanogenmod.messaging.lookup.LookupProviderManager.LookupProviderListener;
import com.cyanogenmod.messaging.ui.AttributionContactIconView;
import com.cyanogenmod.messaging.util.GoogleStaticMapsUtil;
+import com.cyanogenmod.messaging.util.MetricsHelper;
import com.cyanogenmod.messaging.util.RidesharingUtil;
import com.cyanogenmod.messaging.util.RoundedCornerTransformation;
import com.google.common.base.Predicate;
@@ -752,6 +754,9 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
builder.addDropoffLocation(decodedAddress);
Intent intent = builder.build();
mContext.startActivity(intent);
+
+ MetricsHelper.increaseCountOfEventMetricAfterValidate(mContext.getApplicationContext(), new ComponentName(mContext.getApplicationContext(), ConversationActivity.class),
+ MetricsHelper.MetricEvent.UBER_RIDE_REQUESTED);
}
}
});
@@ -765,6 +770,9 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
mButtonDivider.measure(dividerWidthMeasureSpec, unspecifiedMeasureSpec);
mMessageMapsView.setVisibility(View.VISIBLE);
+
+ MetricsHelper.increaseCountOfEventMetricAfterValidate(mContext.getApplicationContext(), new ComponentName(mContext.getApplicationContext(), ConversationActivity.class),
+ MetricsHelper.MetricEvent.RIDESHARING_MAP_SHOWN);
}
private class ImageLoadedCallback implements com.squareup.picasso.Callback {
diff --git a/src/com/cyanogenmod/messaging/util/MetricsHelper.java b/src/com/cyanogenmod/messaging/util/MetricsHelper.java
new file mode 100644
index 0000000..aff2e32
--- /dev/null
+++ b/src/com/cyanogenmod/messaging/util/MetricsHelper.java
@@ -0,0 +1,223 @@
+/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*
+ * Copyright (c) 2016 Cyanogen Inc.
+ * All Rights Reserved.
+ * Cyanogen Confidential and Proprietary.
+ * =========================================================================*/
+
+package com.cyanogenmod.messaging.util;
+
+import android.content.SharedPreferences;
+import android.util.Log;
+
+//import com.android.internal.annotations.VisibleForTesting;
+import com.cyanogen.ambient.analytics.Event;
+import android.content.ComponentName;
+import android.content.Context;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public final class MetricsHelper {
+
+ private static final String TAG = "MetricsHelper";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final String METRICS_SHARED_PREFERENCES = "messaging_metrics";
+ public static final String METRICS_VALIDATION = "messaging_metrics_validation";
+ public static final String DELIMIT = ":";
+ private static final String CATEGORY_BASE = "messaging.metrics.";
+
+ // 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;
+
+ public static final String CATEGORY_MESSAGING_RIDESHARING_INTEGRATION = "category_messaging_ridesharing_integration";
+ public static final String EVENT_RIDESHARING_MAP_SHOWN = "event_ridesharing_map_shown";
+ public static final String EVENT_UBER_RIDE_REQUESTED = "event_uber_ride_requested";
+ public static final String PARAM_COUNT = "param_count";
+ public static final String PARAM_PROVIDER = "provider";
+
+ public static enum MetricEvent {
+ RIDESHARING_MAP_SHOWN,
+ UBER_RIDE_REQUESTED;
+ }
+
+ //@VisibleForTesting
+ /* package */ static void storeEvent(Context context, ComponentName cn,
+ HashMap<String, String> data, String category, String event) {
+
+ SharedPreferences sp = context.getSharedPreferences(
+ METRICS_SHARED_PREFERENCES, Context.MODE_PRIVATE);
+
+ SharedPreferences.Editor editor = sp.edit();
+
+ for (String param : data.keySet()) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(cn.flattenToShortString()); // Add ComponentName String
+ sb.append(DELIMIT);
+ sb.append(category); // Add our category value
+ sb.append(DELIMIT);
+ sb.append(event); // Add our event value
+ sb.append(DELIMIT);
+ sb.append(param); // add our param value
+ editor.putString(sb.toString(), data.get(param));
+ }
+ editor.apply();
+ }
+
+ /**
+ * Get the sharedpreferences events and output a hashmap for the event's values.
+ *
+ * @param context the current context
+ * @param componentName ComponentName who created the event
+ *
+ * @return HashMap of our params and their values.
+ */
+ /* package*/ static HashMap<String, String> getStoredEventParams(Context context,
+ ComponentName componentName, String category, String event) {
+
+ SharedPreferences sp = context.getSharedPreferences(
+ METRICS_SHARED_PREFERENCES, Context.MODE_PRIVATE);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(componentName.flattenToShortString()); // Add ComponentName String
+ sb.append(DELIMIT);
+ sb.append(category); // Add our category value
+ sb.append(DELIMIT);
+ sb.append(event); // Add our event value
+ sb.append(DELIMIT);
+
+ HashMap<String, String> eventMap = new HashMap<>();
+ Map<String, ?> map = sp.getAll();
+
+ for(Map.Entry<String,?> entry : map.entrySet()) {
+ if (entry.getKey().startsWith(sb.toString())) {
+ String[] keyParts = entry.getKey().split(DELIMIT);
+ String key = keyParts[POS_PARAM_VALUE];
+ eventMap.put(key, String.valueOf(entry.getValue()));
+ }
+ }
+ return eventMap;
+ }
+
+ /**
+ * Helper method to increase the count of event metric if the last action was not
+ * the same as the current action.
+ *
+ * @param context
+ */
+ public static void increaseCountOfEventMetricAfterValidate(Context context, ComponentName componentName,
+ MetricEvent metricEvent) {
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(componentName.flattenToShortString()); // Add ComponentName String
+ sb.append(DELIMIT);
+ sb.append(CATEGORY_MESSAGING_RIDESHARING_INTEGRATION); // Add our category value
+
+ String validationKey = sb.toString();
+ String event;
+ switch (metricEvent) {
+ case UBER_RIDE_REQUESTED:
+ event = EVENT_UBER_RIDE_REQUESTED;
+ break;
+ case RIDESHARING_MAP_SHOWN:
+ default:
+ event = EVENT_RIDESHARING_MAP_SHOWN;
+ break;
+ }
+
+ if (checkLastEvent(context, validationKey, event)) {
+ HashMap<String, String> metricsData
+ = getStoredEventParams(context, componentName, CATEGORY_MESSAGING_RIDESHARING_INTEGRATION,
+ event);
+
+ int count = 1;
+ if (metricsData.containsKey(PARAM_COUNT)) {
+ count += Integer.valueOf(metricsData.get(PARAM_COUNT));
+ }
+
+ metricsData.put(PARAM_COUNT, String.valueOf(count));
+ storeEvent(context, componentName, metricsData, CATEGORY_MESSAGING_RIDESHARING_INTEGRATION, event);
+ }
+ }
+
+ /* package */ static boolean checkLastEvent(Context context, String validationKey, String event) {
+ SharedPreferences preferences = context.getSharedPreferences(METRICS_VALIDATION,
+ Context.MODE_PRIVATE);
+
+ SharedPreferences.Editor editor = preferences.edit();
+ String lastEvent = preferences.getString(validationKey, null);
+
+ if (lastEvent != null && lastEvent.equals(event)) {
+ return false;
+ } else {
+ editor.putString(validationKey, event);
+ }
+
+ editor.apply();
+ return true;
+ }
+
+
+ /**
+ * 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);
+
+ Map<String, ?> map = sp.getAll();
+
+ HashMap<String, Event.Builder> unBuiltEvents = new HashMap<>();
+
+ for(Map.Entry<String,?> entry : map.entrySet()){
+ String[] keyParts = entry.getKey().split(DELIMIT);
+
+ 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);
+ }
+
+ eventBuilder.addField(parameter, String.valueOf(entry.getValue()));
+ unBuiltEvents.put(eventKey, eventBuilder);
+ }
+ }
+ return unBuiltEvents;
+ }
+
+ 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);
+ }
+ }
+ editor.apply();
+ }
+
+}
diff --git a/src/com/cyanogenmod/messaging/util/MetricsJob.java b/src/com/cyanogenmod/messaging/util/MetricsJob.java
new file mode 100644
index 0000000..8b623a7
--- /dev/null
+++ b/src/com/cyanogenmod/messaging/util/MetricsJob.java
@@ -0,0 +1,139 @@
+/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*
+ * Copyright (c) 2016 Cyanogen Inc.
+ * All Rights Reserved.
+ * Cyanogen Confidential and Proprietary.
+ * =========================================================================*/
+
+package com.cyanogenmod.messaging.util;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import com.cyanogen.ambient.common.ConnectionResult;
+import com.cyanogen.ambient.common.ConnectionResult;
+import com.cyanogen.ambient.common.api.AmbientApiClient;
+import com.cyanogen.ambient.analytics.AnalyticsServices;
+import com.cyanogen.ambient.analytics.Event;
+import com.cyanogen.ambient.common.api.Result;
+
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * MetricsJob is an aggregation and shipping service that is fired
+ * once every 24 hours to pass Metrics to ModCore's analytics service.
+ */
+public final class MetricsJob extends JobService {
+
+ private static final String TAG = "MetricsJob";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final long TIMEOUT_MILLIS = 1000;
+
+ public static final int METRICS_JOB_ID = 1441;
+
+ private MetricsTask mUploadTask;
+
+ private AmbientApiClient ambientApiClient;
+
+ public MetricsJob() {
+ super();
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (DEBUG) Log.v(TAG, "sending events");
+
+ // AmbientClient
+ ambientApiClient = new AmbientApiClient.Builder(this)
+ .addApi(AnalyticsServices.API)
+ .addOnConnectionFailedListener(new AmbientApiClient.OnConnectionFailedListener() {
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ Log.e(TAG, "Failed to connect to Ambient. reason: " + result.getErrorCode());
+ }
+ }).build();
+
+ // Send stored Specific events
+ mUploadTask = new MetricsTask(params);
+ mUploadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void) null);
+
+ // Running on another thread, return true.
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+
+ if (ambientApiClient != null && (ambientApiClient.isConnected() || ambientApiClient.isConnecting())) {
+ ambientApiClient.disconnect();
+ }
+
+ // Cancel our async task
+ mUploadTask.cancel(true);
+
+ // report that we should try again soon.
+ return true;
+ }
+
+
+ class MetricsTask extends AsyncTask<Void, Void, Boolean> {
+
+ JobParameters mMetricsJobParams;
+
+ public MetricsTask(JobParameters params) {
+ this.mMetricsJobParams = params;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+
+ HashMap<String, Event.Builder> eventsToSend
+ = MetricsHelper.getEventsToSend(MetricsJob.this);
+
+ for (String key : eventsToSend.keySet()) {
+
+ Event.Builder eventBuilder = eventsToSend.get(key);
+
+ if (DEBUG) Log.v(TAG, "sending:" + eventBuilder.toString());
+
+ if (isCancelled()) {
+ return false;
+ }
+
+ Result r = AnalyticsServices.AnalyticsApi.sendEvent(
+ ambientApiClient,
+ eventBuilder.build())
+ .await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+
+ // 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.
+ MetricsHelper.clearEventData(MetricsJob.this, key);
+ }
+ return true;
+ }
+
+ @Override
+ protected void onCancelled() {
+ if (DEBUG) Log.w(TAG, "Messaging 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 */);
+ }
+ }
+
+}