/* * Copyright (c) 2015. 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.launcher3.stats.internal.service; import android.app.AlarmManager; import android.app.IntentService; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; import android.preference.PreferenceManager; import android.util.Log; import com.android.launcher3.LauncherAppState; import com.android.launcher3.stats.external.StatsUtil; import com.android.launcher3.stats.external.TrackingBundle; import com.android.launcher3.stats.internal.db.DatabaseHelper; import com.android.launcher3.stats.internal.model.CountAction; import com.android.launcher3.stats.internal.model.CountOriginByPackageAction; import com.android.launcher3.stats.internal.model.ITrackingAction; import com.android.launcher3.stats.internal.model.RemoteFolderAction; import com.android.launcher3.stats.internal.model.TrackingEvent; import com.android.launcher3.stats.util.Logger; import java.util.ArrayList; import java.util.List; /** *
 *     Service that starts on a timer and handles aggregating events and sending them to
 *     CyanogenStats
 * 
* * @see {@link IntentService} */ public class AggregationIntentService extends IntentService { // Constants private static final String TAG = AggregationIntentService.class.getSimpleName(); private static final String TRACKING_ID = "com.cyanogenmod.trebuchet"; public static final String ACTION_AGGREGATE_AND_TRACK = "com.cyanogenmod.trebuchet.AGGREGATE_AND_TRACK"; private static final List TRACKED_ACTIONS = new ArrayList() { { add(new CountAction()); add(new CountOriginByPackageAction()); } }; private static final int INVALID_COUNT = -1; private static final String KEY_LAST_TIME_RAN = "last_time_stats_ran"; public static final String PREF_KEY_PAGE_COUNT = "page_count"; public static final String PREF_KEY_WIDGET_COUNT = "widget_count"; // Members private DatabaseHelper mDatabaseHelper = null; private int mInstanceId = -1; private SharedPreferences mPrefs = null; /** * Creates an IntentService. Invoked by your subclass's constructor. */ public AggregationIntentService() { super(AggregationIntentService.class.getSimpleName()); } @Override protected void onHandleIntent(Intent intent) { if (!isTrebuchetDefaultLauncher()) { // Cancel repeating schedule unscheduleService(); // don't return b/c we still want to upload whatever metrics are left. } String action = intent.getAction(); if (ACTION_AGGREGATE_AND_TRACK.equals(action)) { mPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); mPrefs.edit().putLong(KEY_LAST_TIME_RAN, System.currentTimeMillis()).apply(); mInstanceId = (int) System.currentTimeMillis(); mDatabaseHelper = DatabaseHelper.createInstance(this); performAggregation(); deleteTrackingEventsForInstance(); handleNonEventMetrics(); } } private void performAggregation() { // Iterate available categories for (TrackingEvent.Category category : TrackingEvent.Category.values()) { // Fetch the events from the database based on the category List eventList = mDatabaseHelper.getTrackingEventsByCategory(mInstanceId, category); Logger.logd(TAG, "Event list size: " + eventList.size()); // Short circuit if no events for the category if (eventList.size() < 1) { continue; } // Now crunch the data into actionable events for the server. // Remote Folder data will process itself separately. if (category == TrackingEvent.Category.REMOTE_FOLDER) { performTrackingCall(new RemoteFolderAction(), category, eventList); } else { for (ITrackingAction action : TRACKED_ACTIONS) { performTrackingCall(action, category, eventList); } } } } private void deleteTrackingEventsForInstance() { mDatabaseHelper.deleteEventsByInstanceId(mInstanceId); } /** * These are metrics that are not event based and need a snapshot every INTERVAL */ private void handleNonEventMetrics() { sendPageCountStats(); sendWidgetCountStats(); } private void sendPageCountStats() { int pageCount = mPrefs.getInt(PREF_KEY_PAGE_COUNT, INVALID_COUNT); if (pageCount == INVALID_COUNT) { return; } Bundle bundle = TrackingBundle .createTrackingBundle(TRACKING_ID, TrackingEvent.Category.HOMESCREEN_PAGE.name(), "count"); bundle.putString(TrackingEvent.KEY_VALUE, String.valueOf(pageCount)); StatsUtil.sendEvent(this, bundle); } private void sendWidgetCountStats() { int widgetCount = mPrefs.getInt(PREF_KEY_WIDGET_COUNT, INVALID_COUNT); if (widgetCount == INVALID_COUNT) { return; } Bundle bundle = TrackingBundle .createTrackingBundle(TRACKING_ID, TrackingEvent.Category.WIDGET.name(), "count"); bundle.putString(TrackingEvent.KEY_VALUE, String.valueOf(widgetCount)); StatsUtil.sendEvent(this, bundle); } private void performTrackingCall(ITrackingAction action, TrackingEvent.Category category, List eventList) throws IllegalArgumentException { try { for (Bundle bundle : action.createTrackingBundles(TRACKING_ID, category, eventList)) { StatsUtil.sendEvent(this, bundle); } } catch (NullPointerException e) { Log.e(TAG, "NPE fetching bundle list!", e); } catch (IllegalArgumentException e) { Log.e(TAG, "Illegal argument!", e); } } private void unscheduleService() { Intent intent = new Intent(this, AggregationIntentService.class); intent.setAction(ACTION_AGGREGATE_AND_TRACK); PendingIntent pi = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(pi); } private boolean isTrebuchetDefaultLauncher() { final IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN); filter.addCategory(Intent.CATEGORY_HOME); List filters = new ArrayList(); filters.add(filter); final String myPackageName = getPackageName(); List activities = new ArrayList(); final PackageManager packageManager = getPackageManager(); // You can use name of your package here as third argument packageManager.getPreferredActivities(filters, activities, null); for (ComponentName activity : activities) { if (myPackageName.equals(activity.getPackageName())) { Logger.logd(TAG, "Trebuchet IS default launcher!"); return true; } } Logger.logd(TAG, "Trebuchet IS NOT default launcher!"); return false; } private static final long ALARM_INTERVAL = 86400000; // 1 day /** * Schedule an alarm service, will cancel existing * * @param context {@link Context} * @throws IllegalArgumentException {@link IllegalArgumentException} */ public static void scheduleService(Context context) throws IllegalArgumentException { if (context == null) { throw new IllegalArgumentException("'context' cannot be null!"); } SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); long lastTimeRan = prefs.getLong(KEY_LAST_TIME_RAN, 0); Intent intent = new Intent(context, AggregationIntentService.class); intent.setAction(ACTION_AGGREGATE_AND_TRACK); PendingIntent pi = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(pi); alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, lastTimeRan + ALARM_INTERVAL, ALARM_INTERVAL, pi); } }