summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Sandler <dsandler@android.com>2013-08-05 02:12:05 -0400
committerDaniel Sandler <dsandler@android.com>2013-08-06 00:18:38 -0400
commitff02d49e464c2fe92ba625a3046f31aa042e5d32 (patch)
tree8b766db2f5690e011fa0c2c2b3c235c593c9fdc4
parent482a5b6ed389ef943990277e461444626c34ebf2 (diff)
downloadandroid_packages_apps_Trebuchet-ff02d49e464c2fe92ba625a3046f31aa042e5d32.tar.gz
android_packages_apps_Trebuchet-ff02d49e464c2fe92ba625a3046f31aa042e5d32.tar.bz2
android_packages_apps_Trebuchet-ff02d49e464c2fe92ba625a3046f31aa042e5d32.zip
Initial implementation: Broadcasts on app launch.
Look for com.android.launcher3.action.LAUNCH to be sent when an icon is clicked in Launcher. (Restricted to com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS which is a signature permission right now. This is specifically tracking apps launched via shortcut icon; any other method of launching apps (notifications, recents, internal navigation, etc.) is outside of Launcher's purview and hence not broadcast. The broadcast currently includes, in the "intent" extra, the Uri flattening of the specific shortcut clicked. The file /data/data/<pkg>/files/launches.log contains a binary log of all such launches, including additional info like screen# that should probably be in the broadcast too. This info is summarized in .../stats.log, which encodes a simple histogram of app launches since basically forever. This should probably be done over a sliding window, which will require more processing on startup. Bug: 10031590 Change-Id: Ifc5921d5dc20701c67678cbfdc89b03cacd62028
-rw-r--r--AndroidManifest.xml6
-rw-r--r--src/com/android/launcher3/Launcher.java9
-rw-r--r--src/com/android/launcher3/Stats.java202
3 files changed, 216 insertions, 1 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b9f75cf5d..f885a6c63 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -50,6 +50,11 @@
android:label="@string/permlab_write_settings"
android:description="@string/permdesc_write_settings"/>
+ <permission
+ android:name="com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS"
+ android:protectionLevel="signature"
+ />
+
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
@@ -60,6 +65,7 @@
<uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
<uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" />
<uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS" />
<application
android:name="com.android.launcher3.LauncherApplication"
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index fa9627973..c3ea1ef8a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -345,6 +345,8 @@ public class Launcher extends Activity
int cellY;
}
+ private Stats mStats;
+
private static boolean isPropertyEnabled(String propertyName) {
return Log.isLoggable(propertyName, Log.VERBOSE);
}
@@ -378,6 +380,8 @@ public class Launcher extends Activity
mDragController = new DragController(this);
mInflater = getLayoutInflater();
+ mStats = new Stats(this);
+
mAppWidgetManager = AppWidgetManager.getInstance(this);
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();
@@ -2086,7 +2090,8 @@ public class Launcher extends Activity
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
// Open shortcut
- final Intent intent = ((ShortcutInfo) tag).intent;
+ final ShortcutInfo shortcut = (ShortcutInfo) tag;
+ final Intent intent = shortcut.intent;
// Check for special shortcuts
if (intent.getComponent() != null) {
@@ -2112,6 +2117,8 @@ public class Launcher extends Activity
boolean success = startActivitySafely(v, intent, tag);
+ mStats.recordLaunch(intent, shortcut);
+
if (success && v instanceof BubbleTextView) {
mWaitingForResume = (BubbleTextView) v;
mWaitingForResume.setStayPressed(true);
diff --git a/src/com/android/launcher3/Stats.java b/src/com/android/launcher3/Stats.java
new file mode 100644
index 000000000..ca088f71a
--- /dev/null
+++ b/src/com/android/launcher3/Stats.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2012 The Android Open Source 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import java.io.*;
+import java.util.ArrayList;
+
+public class Stats {
+ private static final boolean DEBUG_BROADCASTS = false;
+ private static final String TAG = "Launcher3/Stats";
+
+ private static final boolean LOCAL_LAUNCH_LOG = true;
+
+ public static final String ACTION_LAUNCH = "com.android.launcher3.action.LAUNCH";
+ public static final String PERM_LAUNCH = "com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS";
+ public static final String EXTRA_INTENT = "intent";
+ public static final String EXTRA_CONTAINER = "container";
+ public static final String EXTRA_SCREEN = "screen";
+ public static final String EXTRA_CELLX = "cellX";
+ public static final String EXTRA_CELLY = "cellY";
+
+ private static final String LOG_FILE_NAME = "launches.log";
+ private static final int LOG_VERSION = 1;
+ private static final int LOG_TAG_VERSION = 0x1;
+ private static final int LOG_TAG_LAUNCH = 0x1000;
+
+ private static final String STATS_FILE_NAME = "stats.log";
+ private static final int STATS_VERSION = 1;
+ private static final int INITIAL_STATS_SIZE = 100;
+
+ // TODO: delayed/batched writes
+ private static final boolean FLUSH_IMMEDIATELY = true;
+
+ private final Launcher mLauncher;
+
+ DataOutputStream mLog;
+
+ ArrayList<String> mIntents;
+ ArrayList<Integer> mHistogram;
+
+ public Stats(Launcher launcher) {
+ mLauncher = launcher;
+
+ loadStats();
+
+ if (LOCAL_LAUNCH_LOG) {
+ try {
+ mLog = new DataOutputStream(mLauncher.openFileOutput(LOG_FILE_NAME, Context.MODE_APPEND));
+ mLog.writeInt(LOG_TAG_VERSION);
+ mLog.writeInt(LOG_VERSION);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "unable to create stats log: " + e);
+ mLog = null;
+ } catch (IOException e) {
+ Log.e(TAG, "unable to write to stats log: " + e);
+ mLog = null;
+ }
+ }
+
+ if (DEBUG_BROADCASTS) {
+ launcher.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ android.util.Log.v("Stats", "got broadcast: " + intent + " for launched intent: "
+ + intent.getStringExtra(EXTRA_INTENT));
+ }
+ },
+ new IntentFilter(ACTION_LAUNCH),
+ PERM_LAUNCH,
+ null
+ );
+ }
+ }
+
+ public void incrementLaunch(String intentStr) {
+ int pos = mIntents.indexOf(intentStr);
+ if (pos < 0) {
+ mIntents.add(intentStr);
+ mHistogram.add(1);
+ } else {
+ mHistogram.set(pos, mHistogram.get(pos) + 1);
+ }
+ }
+
+ public void recordLaunch(Intent intent, ShortcutInfo shortcut) {
+ intent = new Intent(intent);
+ intent.setSourceBounds(null);
+
+ final String flat = intent.toUri(0);
+
+ mLauncher.sendBroadcast(
+ new Intent(ACTION_LAUNCH)
+ .putExtra(EXTRA_INTENT, flat)
+ .putExtra(EXTRA_CONTAINER, shortcut.container)
+ .putExtra(EXTRA_SCREEN, shortcut.screenId)
+ .putExtra(EXTRA_CELLX, shortcut.cellX)
+ .putExtra(EXTRA_CELLY, shortcut.cellY),
+ PERM_LAUNCH);
+
+ incrementLaunch(flat);
+
+ if (FLUSH_IMMEDIATELY) {
+ saveStats();
+ }
+
+ if (LOCAL_LAUNCH_LOG && mLog != null) {
+ try {
+ mLog.writeInt(LOG_TAG_LAUNCH);
+ mLog.writeLong(System.currentTimeMillis());
+ mLog.writeShort((short) shortcut.container);
+ mLog.writeShort((short) shortcut.screenId);
+ mLog.writeShort((short) shortcut.cellX);
+ mLog.writeShort((short) shortcut.cellY);
+ mLog.writeUTF(flat);
+ if (FLUSH_IMMEDIATELY) {
+ mLog.flush(); // TODO: delayed writes
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void saveStats() {
+ DataOutputStream stats = null;
+ try {
+ stats = new DataOutputStream(mLauncher.openFileOutput(STATS_FILE_NAME + ".tmp", Context.MODE_PRIVATE));
+ stats.writeInt(STATS_VERSION);
+ final int N = mHistogram.size();
+ stats.writeInt(N);
+ for (int i=0; i<N; i++) {
+ stats.writeUTF(mIntents.get(i));
+ stats.writeInt(mHistogram.get(i));
+ }
+ stats.close();
+ stats = null;
+ mLauncher.getFileStreamPath(STATS_FILE_NAME + ".tmp")
+ .renameTo(mLauncher.getFileStreamPath(STATS_FILE_NAME));
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "unable to create stats data: " + e);
+ } catch (IOException e) {
+ Log.e(TAG, "unable to write to stats data: " + e);
+ } finally {
+ if (stats != null) {
+ try {
+ stats.close();
+ } catch (IOException e) { }
+ }
+ }
+ }
+
+ private void loadStats() {
+ mIntents = new ArrayList<String>(INITIAL_STATS_SIZE);
+ mHistogram = new ArrayList<Integer>(INITIAL_STATS_SIZE);
+ DataInputStream stats = null;
+ try {
+ stats = new DataInputStream(mLauncher.openFileInput(STATS_FILE_NAME));
+ final int version = stats.readInt();
+ if (version == STATS_VERSION) {
+ final int N = stats.readInt();
+ for (int i=0; i<N; i++) {
+ final String pkg = stats.readUTF();
+ final int count = stats.readInt();
+ mIntents.add(pkg);
+ mHistogram.add(count);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // not a problem
+ } catch (IOException e) {
+ // more of a problem
+
+ } finally {
+ if (stats != null) {
+ try {
+ stats.close();
+ } catch (IOException e) { }
+ }
+ }
+ }
+}