Merge "Fixing regression in initializing install time." into jb-ub-gel-agar
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b9f75cf..f885a6c 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/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index 92f126c..fa713a4 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -27,6 +27,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Looper;
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -167,8 +168,6 @@
         public float mOuterRingSize;
         public float mInnerRingSize;
         public FolderIcon mFolderIcon = null;
-        public Drawable mOuterRingDrawable = null;
-        public Drawable mInnerRingDrawable = null;
         public static Drawable sSharedOuterRingDrawable = null;
         public static Drawable sSharedInnerRingDrawable = null;
         public static int sPreviewSize = -1;
@@ -180,12 +179,14 @@
         public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) {
             mFolderIcon = folderIcon;
             Resources res = launcher.getResources();
-            mOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
-            mInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo);
 
             // We need to reload the static values when configuration changes in case they are
             // different in another configuration
             if (sStaticValuesDirty) {
+                if (Looper.myLooper() != Looper.getMainLooper()) {
+                    throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread "
+                            + Thread.currentThread());
+                }
                 sPreviewSize = res.getDimensionPixelSize(R.dimen.folder_preview_size);
                 sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
                 sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index f990d25..bd343ea 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -343,6 +343,8 @@
         int cellY;
     }
 
+    private Stats mStats;
+
     private static boolean isPropertyEnabled(String propertyName) {
         return Log.isLoggable(propertyName, Log.VERBOSE);
     }
@@ -376,6 +378,8 @@
         mDragController = new DragController(this);
         mInflater = getLayoutInflater();
 
+        mStats = new Stats(this);
+
         mAppWidgetManager = AppWidgetManager.getInstance(this);
         mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
         mAppWidgetHost.startListening();
@@ -2093,7 +2097,8 @@
         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) {
@@ -2119,6 +2124,8 @@
 
             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 0000000..ca088f7
--- /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) { }
+            }
+        }
+    }
+}