Refactor UserEventLogging, Add predictedRank, replace Bundle with Proto

b/26494415
- Removed bundle object that became redundant now that we have LauncherEvent proto
- Combined Stats and UserEventLogger as they are effectively doing same thing
- Removed parent field inside Target
- added predictedRank target inside Target

b/27967359
- make com.android.launcher3.action.LAUNCH broadcast explicit
Later CL: finish packageName/intent/componentHash/predictedRank fields

Change-Id: I441fb46c834f73e58a4d2324e8da7971e8713ec8
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index a7b6429..eae02ca 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -32,59 +32,66 @@
 
   // For container type and item type
   // Used mainly for ContainerType.FOLDER, ItemType.*
-  optional Target parent = 2;
-  optional int32 page_index = 3;
-  optional int32 rank = 4;
-  optional int32 grid_x = 5;
-  optional int32 grid_y = 6;
+  optional int32 page_index = 2;
+  optional int32 rank = 3;
+  optional int32 grid_x = 4;
+  optional int32 grid_y = 5;
 
   // For container types only
-  optional ContainerType container_type = 7;
-  optional int32 cardinality = 8;
+  optional ContainerType container_type = 6;
+  optional int32 cardinality = 7;
 
   // For control types only
-  optional ControlType control_type = 9;
+  optional ControlType control_type = 8;
 
   // For item types only
-  optional ItemType item_type = 10;
-  optional int32 package_name_hash = 11;
-  optional int32 component_hash = 12;      // Used for ItemType.WIDGET
-  optional int32 intent_hash = 13;         // Used for ItemType.SHORTCUT
-  optional int32 span_x = 14 [default = 1];// Used for ItemType.WIDGET
-  optional int32 span_y = 15 [default = 1];// Used for ItemType.WIDGET
+  optional ItemType item_type = 9;
+  optional int32 package_name_hash = 10;
+  optional int32 component_hash = 11;      // Used for ItemType.WIDGET
+  optional int32 intent_hash = 12;         // Used for ItemType.SHORTCUT
+  optional int32 span_x = 13 [default = 1];// Used for ItemType.WIDGET
+  optional int32 span_y = 14 [default = 1];// Used for ItemType.WIDGET
+  optional int32 predictedRank = 15;
 }
 
+// Used to define what type of item a Target would represent.
 enum ItemType {
-  APP_ICON = 0;
-  SHORTCUT = 1;
-  WIDGET = 2;
-  FOLDER_ICON = 3;
+  DEFAULT_ITEMTYPE = 0;
+  APP_ICON = 1;
+  SHORTCUT = 2;
+  WIDGET = 3;
+  FOLDER_ICON = 4;
 }
 
+// Used to define what type of container a Target would represent.
 enum ContainerType {
-  WORKSPACE = 0;
-  HOTSEAT = 1;
-  FOLDER = 2;
-  ALLAPPS = 3;
-  WIDGETS = 4;
-  OVERVIEW = 5;
-  PREDICTION = 6;
-  SEARCHRESULT = 7;
+  DEFAULT_CONTAINERTYPE = 0;
+  WORKSPACE = 1;
+  HOTSEAT = 2;
+  FOLDER = 3;
+  ALLAPPS = 4;
+  WIDGETS = 5;
+  OVERVIEW = 6;
+  PREDICTION = 7;
+  SEARCHRESULT = 8;
 }
 
+// Used to define what type of control a Target would represent.
 enum ControlType {
-  ALL_APPS_BUTTON = 0;
-  WIDGETS_BUTTON = 1;
-  WALLPAPER_BUTTON = 2;
-  SETTINGS_BUTTON = 3;
-  REMOVE_TARGET = 4;
-  UNINSTALL_TARGET = 5;
-  APPINFO_TARGET = 6;
-  RESIZE_HANDLE = 7;
-  FAST_SCROLL_HANDLE = 8;
+  DEFAULT_CONTROLTYPE = 0;
+  ALL_APPS_BUTTON = 1;
+  WIDGETS_BUTTON = 2;
+  WALLPAPER_BUTTON = 3;
+  SETTINGS_BUTTON = 4;
+  REMOVE_TARGET = 5;
+  UNINSTALL_TARGET = 6;
+  APPINFO_TARGET = 7;
+  RESIZE_HANDLE = 8;
+  VERTICAL_SCROLL = 9;
   // HOME, BACK, GO_TO_PLAYSTORE
 }
 
+// Used to define the action component of the LauncherEvent.
 message Action {
   enum Type {
     TOUCH = 0;
@@ -113,10 +120,10 @@
   required Action action = 1;
 
   // List of targets that touch actions can be operated on.
-  optional Target src_target = 2;
-  optional Target dest_target = 3;
+  repeated Target src_target = 2;
+  repeated Target dest_target = 3;
 
   optional int64 action_duration_millis = 4;
   optional int64 elapsed_container_millis = 5;
   optional int64 elapsed_session_millis = 6;
-}
+}
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 8d69f9a..d689f1b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -68,6 +68,10 @@
          filter the activities shown in the launcher. Can be empty. -->
     <string name="app_filter_class" translatable="false"></string>
 
+    <!-- List of package names that com.android.launcher3.action.LAUNCH
+     should be targeting. Can be empty. -->
+    <array name="launch_broadcast_targets" translatable="false"></array>
+
     <!-- Name of an icon provider class. -->
     <string name="icon_provider_class" translatable="false"></string>
 
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index e691b48..1af7668 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -28,8 +27,12 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.logging.UserEventLogger;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+
 public class Hotseat extends FrameLayout
-        implements Stats.LaunchSourceProvider{
+        implements UserEventLogger.LaunchSourceProvider{
 
     private CellLayout mContent;
 
@@ -157,7 +160,10 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, Bundle sourceData) {
-        sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_HOTSEAT);
+    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+        target.itemType = LauncherLogProto.APP_ICON;
+        target.gridX = info.cellX;
+        target.gridY = info.cellY;
+        targetParent.containerType = LauncherLogProto.HOTSEAT;
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d2d07cc..5873368 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -148,6 +148,7 @@
     static final boolean DEBUG_WIDGETS = false;
     static final boolean DEBUG_STRICT_MODE = false;
     static final boolean DEBUG_RESUME_TIME = false;
+    static final boolean DEBUG_LOGGING = false;
 
     static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
 
@@ -174,7 +175,7 @@
     protected static final int REQUEST_LAST = 100;
 
     // To turn on these properties, type
-    // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
+    // adb shell setprop logTap.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
     static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
 
     // The Intent extra that defines whether to ignore the launch animation
@@ -365,7 +366,6 @@
         int appWidgetId;
     }
 
-    private Stats mStats;
     private UserEventLogger mUserEventLogger;
 
     public FocusIndicatorView mFocusHandler;
@@ -425,7 +425,6 @@
         mDragController = new DragController(this);
         mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
 
-        mStats = new Stats(this);
         initLogger();
 
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
@@ -638,10 +637,6 @@
         }
     }
 
-    public Stats getStats() {
-        return mStats;
-    }
-
     /**
      * Logger object is a singleton and does not have to be coupled with the foreground activity.
      * Since most user event logging is done on the UI, the object is retrieved from the
@@ -652,16 +647,19 @@
             mUserEventLogger = mLauncherCallbacks.getLogger();
         }
         if (mUserEventLogger == null) {
-            mUserEventLogger = new UserEventLogger() {
+            mUserEventLogger = new UserEventLogger(this) {
                 @Override
                 public void processEvent(LauncherLogProto.LauncherEvent ev) {
-                    if (ev.action.touch == LauncherLogProto.Action.TAP && ev.srcTarget.itemType == LauncherLogProto.APP_ICON) {
-                        Log.d(TAG, String.format(Locale.US, "action:%s target:%s\n\telapsed container %d ms session %d ms",
-                                LoggerUtils.getActionStr(ev.action),
-                                LoggerUtils.getTargetStr(ev.srcTarget),
-                                ev.elapsedContainerMillis,
-                                ev.elapsedSessionMillis));
+                    if (!DEBUG_LOGGING) {
+                        return;
                     }
+                    Log.d("UserEvent", String.format(Locale.US,
+                            "action:%s\nchild:%s\nparent:%s\nelapsed container %d ms session %d ms",
+                            LoggerUtils.getActionStr(ev.action),
+                            LoggerUtils.getTargetStr(ev.srcTarget[0]),
+                            LoggerUtils.getTargetStr(ev.srcTarget[1]),
+                            ev.elapsedContainerMillis,
+                            ev.elapsedSessionMillis));
                 }
             };
         }
@@ -2705,7 +2703,7 @@
         }
 
         boolean success = startActivitySafely(v, intent, tag);
-        mStats.recordLaunch(v, intent, shortcut);
+        mUserEventLogger.logLaunch(v, intent);
 
         if (success && v instanceof BubbleTextView) {
             mWaitingForResume = (BubbleTextView) v;
@@ -3488,6 +3486,7 @@
             List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps();
             if (apps != null) {
                 mAppsView.setPredictedApps(apps);
+                mUserEventLogger.setPredictedApps(apps);
             }
         }
     }
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 635d413..f33cf83 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -1,12 +1,11 @@
 package com.android.launcher3;
 
-import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.View;
-import android.view.ViewGroup;
+
 import com.android.launcher3.allapps.AllAppsSearchBarController;
 import com.android.launcher3.logging.UserEventLogger;
 import com.android.launcher3.util.ComponentKey;
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index 6ce2293..adb5031 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -39,8 +39,8 @@
 
     // TODO: Delete these files on upgrade
     public static final List<String> OBSOLETE_FILES = Collections.unmodifiableList(Arrays.asList(
-            "launches.log",
-            "stats.log",
+            "launches.logTap",
+            "stats.logTap",
             "launcher.preferences",
             "com.android.launcher3.compat.PackageInstallerCompatV16.queue"));
 }
diff --git a/src/com/android/launcher3/Stats.java b/src/com/android/launcher3/Stats.java
deleted file mode 100644
index 10a26ad..0000000
--- a/src/com/android/launcher3/Stats.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewParent;
-
-import com.android.launcher3.config.ProviderConfig;
-
-public class Stats {
-
-    /**
-     * Implemented by containers to provide a launch source for a given child.
-     */
-    public interface LaunchSourceProvider {
-        void fillInLaunchSourceData(View v, Bundle sourceData);
-    }
-
-    /**
-     * Helpers to add the source to a launch intent.
-     */
-    public static class LaunchSourceUtils {
-        /**
-         * Create a default bundle for LaunchSourceProviders to fill in their data.
-         */
-        public static Bundle createSourceData() {
-            Bundle sourceData = new Bundle();
-            sourceData.putString(SOURCE_EXTRA_CONTAINER, CONTAINER_HOMESCREEN);
-            // Have default container/sub container pages
-            sourceData.putInt(SOURCE_EXTRA_CONTAINER_PAGE, 0);
-            sourceData.putInt(SOURCE_EXTRA_SUB_CONTAINER_PAGE, 0);
-            return sourceData;
-        }
-
-        /**
-         * Finds the next launch source provider in the parents of the view hierarchy and populates
-         * the source data from that provider.
-         */
-        public static void populateSourceDataFromAncestorProvider(View v, Bundle sourceData) {
-            if (v == null) {
-                return;
-            }
-
-            Stats.LaunchSourceProvider provider = null;
-            ViewParent parent = v.getParent();
-            while (parent != null && parent instanceof View) {
-                if (parent instanceof Stats.LaunchSourceProvider) {
-                    provider = (Stats.LaunchSourceProvider) parent;
-                    break;
-                }
-                parent = parent.getParent();
-            }
-
-            if (provider != null) {
-                provider.fillInLaunchSourceData(v, sourceData);
-            } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
-                throw new RuntimeException("Expected LaunchSourceProvider");
-            }
-        }
-    }
-
-    private static final boolean DEBUG_BROADCASTS = false;
-
-    public static final String ACTION_LAUNCH = "com.android.launcher3.action.LAUNCH";
-    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";
-    public static final String EXTRA_SOURCE = "source";
-
-    public static final String SOURCE_EXTRA_CONTAINER = "container";
-    public static final String SOURCE_EXTRA_CONTAINER_PAGE = "container_page";
-    public static final String SOURCE_EXTRA_SUB_CONTAINER = "sub_container";
-    public static final String SOURCE_EXTRA_SUB_CONTAINER_PAGE = "sub_container_page";
-
-    public static final String CONTAINER_SEARCH_BOX = "search_box";
-    public static final String CONTAINER_ALL_APPS = "all_apps";
-    public static final String CONTAINER_HOMESCREEN = "homescreen"; // aka. Workspace
-    public static final String CONTAINER_HOTSEAT = "hotseat";
-
-    public static final String SUB_CONTAINER_FOLDER = "folder";
-    public static final String SUB_CONTAINER_ALL_APPS_A_Z = "a-z";
-    public static final String SUB_CONTAINER_ALL_APPS_PREDICTION = "prediction";
-    public static final String SUB_CONTAINER_ALL_APPS_SEARCH = "search";
-
-    private final Launcher mLauncher;
-    private final String mLaunchBroadcastPermission;
-
-    public Stats(Launcher launcher) {
-        mLauncher = launcher;
-        mLaunchBroadcastPermission =
-                launcher.getResources().getString(R.string.receive_launch_broadcasts_permission);
-
-        if (DEBUG_BROADCASTS) {
-            launcher.registerReceiver(
-                    new BroadcastReceiver() {
-                        @Override
-                        public void onReceive(Context context, Intent intent) {
-                            Log.v("Stats", "got broadcast: " + intent + " for launched intent: "
-                                    + intent.getStringExtra(EXTRA_INTENT));
-                        }
-                    },
-                    new IntentFilter(ACTION_LAUNCH),
-                    mLaunchBroadcastPermission,
-                    null
-            );
-        }
-    }
-
-    public void recordLaunch(View v, Intent intent, ShortcutInfo shortcut) {
-        intent = new Intent(intent);
-        intent.setSourceBounds(null);
-
-        final String flat = intent.toUri(0);
-        Intent broadcastIntent = new Intent(ACTION_LAUNCH).putExtra(EXTRA_INTENT, flat);
-        if (shortcut != null) {
-            broadcastIntent.putExtra(EXTRA_CONTAINER, shortcut.container)
-                    .putExtra(EXTRA_SCREEN, shortcut.screenId)
-                    .putExtra(EXTRA_CELLX, shortcut.cellX)
-                    .putExtra(EXTRA_CELLY, shortcut.cellY);
-        }
-
-        Bundle sourceExtras = LaunchSourceUtils.createSourceData();
-        LaunchSourceUtils.populateSourceDataFromAncestorProvider(v, sourceExtras);
-        broadcastIntent.putExtra(EXTRA_SOURCE, sourceExtras);
-        mLauncher.sendBroadcast(broadcastIntent, mLaunchBroadcastPermission);
-        if (intent.getComponent() != null) {
-            mLauncher.getLogger().logAppLaunch(intent.getComponent().getPackageName(), shortcut, sourceExtras);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 4aea85e..6b38f64 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -39,7 +39,6 @@
 import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcelable;
@@ -73,6 +72,9 @@
 import com.android.launcher3.dragndrop.SpringLoadedDragController;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.logging.UserEventLogger;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
@@ -92,7 +94,7 @@
 public class Workspace extends PagedView
         implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
         DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
-        Insettable, DropTargetSource, AccessibilityDragSource, Stats.LaunchSourceProvider {
+        Insettable, DropTargetSource, AccessibilityDragSource, UserEventLogger.LaunchSourceProvider {
     private static final String TAG = "Launcher.Workspace";
 
     private static boolean ENFORCE_DRAG_EVENT_ORDER = false;
@@ -4264,9 +4266,12 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, Bundle sourceData) {
-        sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_HOMESCREEN);
-        sourceData.putInt(Stats.SOURCE_EXTRA_CONTAINER_PAGE, getCurrentPage());
+    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+        target.itemType = LauncherLogProto.APP_ICON;
+        target.gridX = info.cellX;
+        target.gridY = info.cellY;
+        target.pageIndex = getCurrentPage();
+        targetParent.containerType = LauncherLogProto.WORKSPACE;
     }
 
     /**
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 2b3d061..32d444d 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -19,7 +19,6 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.View;
@@ -27,17 +26,19 @@
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Stats;
 import com.android.launcher3.Utilities;
-
+import com.android.launcher3.logging.UserEventLogger.LaunchSourceProvider;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import java.util.List;
 
 /**
  * A RecyclerView with custom fast scroll support for the all apps view.
  */
 public class AllAppsRecyclerView extends BaseRecyclerView
-        implements Stats.LaunchSourceProvider {
+        implements LaunchSourceProvider {
 
     private AlphabeticalAppsList mApps;
     private AllAppsFastScrollHelper mFastScrollHelper;
@@ -165,11 +166,9 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, Bundle sourceData) {
-        sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS);
+    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
         if (mApps.hasFilter()) {
-            sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
-                    Stats.SUB_CONTAINER_ALL_APPS_SEARCH);
+            targetParent.containerType = LauncherLogProto.SEARCHRESULT;
         } else {
             if (v instanceof BubbleTextView) {
                 BubbleTextView icon = (BubbleTextView) v;
@@ -178,14 +177,13 @@
                     List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
                     AlphabeticalAppsList.AdapterItem item = items.get(position);
                     if (item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
-                        sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
-                                Stats.SUB_CONTAINER_ALL_APPS_PREDICTION);
+                        targetParent.containerType = LauncherLogProto.PREDICTION;
                         return;
                     }
                 }
             }
-            sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
-                    Stats.SUB_CONTAINER_ALL_APPS_A_Z);
+
+            targetParent.containerType = LauncherLogProto.ALLAPPS;
         }
     }
 
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2b34a79..b6be8e0 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -29,7 +29,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
-import android.os.Bundle;
 import android.text.InputType;
 import android.text.Selection;
 import android.text.Spannable;
@@ -70,7 +69,6 @@
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Stats;
 import com.android.launcher3.UninstallDropTarget.DropTargetSource;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
@@ -79,6 +77,9 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.logging.UserEventLogger.LaunchSourceProvider;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.UiThreadCircularReveal;
 
@@ -92,7 +93,7 @@
 public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
         View.OnFocusChangeListener, DragListener, DropTargetSource, AccessibilityDragSource,
-        Stats.LaunchSourceProvider {
+        LaunchSourceProvider {
     private static final String TAG = "Launcher.Folder";
 
     /**
@@ -1416,11 +1417,12 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, Bundle sourceData) {
-        // Fill in from the folder icon's launch source provider first
-        Stats.LaunchSourceUtils.populateSourceDataFromAncestorProvider(mFolderIcon, sourceData);
-        sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER, Stats.SUB_CONTAINER_FOLDER);
-        sourceData.putInt(Stats.SOURCE_EXTRA_SUB_CONTAINER_PAGE, mContent.getCurrentPage());
+    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+        target.itemType = LauncherLogProto.APP_ICON;
+        target.gridX = info.cellX;
+        target.gridY = info.cellY;
+        target.pageIndex = mContent.getCurrentPage();
+        targetParent.containerType = LauncherLogProto.FOLDER;
     }
 
     private class OnScrollHintListener implements OnAlarmListener {
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 4b30384..584e38e 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -5,7 +5,6 @@
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Stats;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -18,16 +17,6 @@
  */
 public class LoggerUtils {
     private static final String TAG = "LoggerUtils";
-    private static final boolean DEBUG = false;
-
-    static int getContainerType(ShortcutInfo shortcut) {
-        switch ((int) shortcut.container) {
-            case LauncherSettings.Favorites.CONTAINER_DESKTOP: return LauncherLogProto.WORKSPACE;
-            case LauncherSettings.Favorites.CONTAINER_HOTSEAT: return LauncherLogProto.HOTSEAT;
-            default:
-                return (int) shortcut.container;
-        }
-    }
 
     public static String getActionStr(LauncherLogProto.Action action) {
         switch(action.touch) {
@@ -62,8 +51,10 @@
             default: typeStr = "UNKNOWN";
         }
 
-        return typeStr + " " + t.packageNameHash + " grid=(" + t.gridX + "," + t.gridY + ") "
-                + getContainerStr(t.parent);
+        return typeStr + ", packageHash=" + t.packageNameHash
+                + ", componentHash=" + t.componentHash
+                + ", intentHash=" + t.intentHash
+                + ", grid=(" + t.gridX + "," + t.gridY + "), id=" + t.pageIndex;
     }
 
     private static String getControlStr(Target t) {
@@ -76,7 +67,6 @@
             case LauncherLogProto.UNINSTALL_TARGET: return "UNINSTALL_TARGET";
             case LauncherLogProto.APPINFO_TARGET: return "APPINFO_TARGET";
             case LauncherLogProto.RESIZE_HANDLE: return "RESIZE_HANDLE";
-            case LauncherLogProto.FAST_SCROLL_HANDLE: return "FAST_SCROLL_HANDLE";
             default: return "UNKNOWN";
         }
     }
@@ -114,4 +104,22 @@
         }
         return str + " id=" + t.pageIndex;
     }
+
+
+    public static LauncherLogProto.LauncherEvent initLauncherEvent(
+            int actionType,
+            int childTargetType,
+            int parentTargetType){
+        LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
+
+        event.srcTarget = new LauncherLogProto.Target[2];
+        event.srcTarget[0] = new LauncherLogProto.Target();
+        event.srcTarget[0].type = childTargetType;
+        event.srcTarget[1] = new LauncherLogProto.Target();
+        event.srcTarget[1].type = parentTargetType;
+
+        event.action = new LauncherLogProto.Action();
+        event.action.type = actionType;
+        return event;
+    }
 }
diff --git a/src/com/android/launcher3/logging/UserEventLogger.java b/src/com/android/launcher3/logging/UserEventLogger.java
index 4e5b2c1..bc3afeb 100644
--- a/src/com/android/launcher3/logging/UserEventLogger.java
+++ b/src/com/android/launcher3/logging/UserEventLogger.java
@@ -1,71 +1,182 @@
+/*
+ * 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.logging;
 
-import android.os.Bundle;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewParent;
 
-import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Stats;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.util.ComponentKey;
 
-import java.util.Locale;
+import com.google.protobuf.nano.MessageNano;
+
+import java.util.List;
 
 public abstract class UserEventLogger {
 
+    private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
+    /**
+     * Implemented by containers to provide a launch source for a given child.
+     */
+    public interface LaunchSourceProvider {
+
+        /**
+         * Copies data from the source to the destination proto.
+         * @param v                 source of the data
+         * @param info          source of the data
+         * @param target            dest of the data
+         * @param targetParent      dest of the data
+         */
+        void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent);
+    }
+
+    /**
+     * Recursively finds the parent of the given child which implements IconLogInfoProvider
+     */
+    public static LaunchSourceProvider getLaunchProviderRecursive(View v) {
+        ViewParent parent = null;
+        if (v != null) {
+            parent = v.getParent();
+        } else {
+            return null;
+        }
+
+        // Optimization to only check up to 5 parents.
+        int count = MAXIMUM_VIEW_HIERARCHY_LEVEL;
+        while (parent != null && count-- > 0) {
+            if (parent instanceof LaunchSourceProvider) {
+                return (LaunchSourceProvider) parent;
+            } else {
+                parent = parent.getParent();
+            }
+        }
+        return null;
+    }
+
     private String TAG = "UserEventLogger";
-    private boolean DEBUG = false;
+    private static final boolean DEBUG_BROADCASTS = true;
+
+    public static final String ACTION_LAUNCH = "com.android.launcher3.action.LAUNCH";
+    public static final String EXTRA_INTENT = "intent";;
+    public static final String EXTRA_SOURCE = "source";
+
+    private final Launcher mLauncher;
+    private final String mLaunchBroadcastPermission;
 
     private long mElapsedContainerMillis;
     private long mElapsedSessionMillis;
     private long mActionDurationMillis;
 
+    // Used for filling in predictedRank on {@link Target}s.
+    private List<ComponentKey> mPredictedApps;
 
-    public final void logAppLaunch(String provider, ShortcutInfo shortcut, Bundle bundle) {
-        if (FeatureFlags.LAUNCHER3_LEGACY_LOGGING) return;
+    public UserEventLogger(Launcher launcher) {
+        mLauncher = launcher;
+        mLaunchBroadcastPermission =
+                launcher.getResources().getString(R.string.receive_launch_broadcasts_permission);
 
-        LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
-        event.action = new LauncherLogProto.Action();
-        event.action.type = LauncherLogProto.Action.TOUCH;
-        event.action.touch = LauncherLogProto.Action.TAP;
-
-        event.srcTarget = new LauncherLogProto.Target();
-        event.srcTarget.type = LauncherLogProto.Target.ITEM;
-        event.srcTarget.itemType = LauncherLogProto.APP_ICON;
-        // TODO: package hash name should be different per device.
-        event.srcTarget.packageNameHash = provider.hashCode();
-
-        event.srcTarget.parent = new LauncherLogProto.Target();
-        String subContainer = bundle.getString(Stats.SOURCE_EXTRA_SUB_CONTAINER);
-
-        if (shortcut != null) {
-            event.srcTarget.parent.containerType = LoggerUtils.getContainerType(shortcut);
-            event.srcTarget.pageIndex = (int) shortcut.screenId;
-            event.srcTarget.gridX = shortcut.cellX;
-            event.srcTarget.gridX = shortcut.cellY;
+        if (DEBUG_BROADCASTS) {
+            launcher.registerReceiver(
+                    new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent intent) {
+                            Log.v(TAG, "got broadcast: " + intent + " for launched intent: "
+                                    + intent.getStringExtra(EXTRA_INTENT));
+                        }
+                    },
+                    new IntentFilter(ACTION_LAUNCH),
+                    mLaunchBroadcastPermission,
+                    null
+            );
         }
-        if (subContainer != null) {
-            event.srcTarget.parent.type = LauncherLogProto.Target.CONTAINER;
-            if (subContainer.equals(Stats.SUB_CONTAINER_FOLDER)) {
-                event.srcTarget.parent.containerType = LauncherLogProto.FOLDER;
-            } else if (subContainer.equals(Stats.SUB_CONTAINER_ALL_APPS_A_Z)) {
-                event.srcTarget.parent.containerType = LauncherLogProto.ALLAPPS;
-            } else if (subContainer.equals(Stats.CONTAINER_HOTSEAT)) {
-                event.srcTarget.parent.containerType = LauncherLogProto.HOTSEAT;
-            } else if (subContainer.equals(Stats.SUB_CONTAINER_ALL_APPS_PREDICTION)) {
-                event.srcTarget.parent.containerType = LauncherLogProto.PREDICTION;
-            }
+    }
 
-            if (DEBUG) {
-                Log.d(TAG, String.format("parent bundle: %s %s %s %s",
-                        bundle.getString(Stats.SOURCE_EXTRA_CONTAINER),
-                        bundle.getString(Stats.SOURCE_EXTRA_CONTAINER_PAGE),
-                        bundle.getString(Stats.SOURCE_EXTRA_SUB_CONTAINER),
-                        bundle.getString(Stats.SOURCE_EXTRA_SUB_CONTAINER_PAGE)));
-            }
+    //                      APP_ICON    SHORTCUT    WIDGET
+    // --------------------------------------------------------------
+    // packageNameHash      required    optional    required
+    // componentNameHash    required                required
+    // intentHash                       required
+    // --------------------------------------------------------------
+
+    /**
+     * Prepare {@link LauncherEvent} and {@link Intent} and then attach the event
+     * to the intent and then broadcast.
+     */
+    public final void broadcastEvent(LauncherEvent ev, Intent intent) {
+        intent = new Intent(intent);
+        intent.setSourceBounds(null);
+
+        final String flat = intent.toUri(0);
+        Intent broadcastIntent = new Intent(ACTION_LAUNCH).putExtra(EXTRA_INTENT, flat);
+
+        broadcastIntent.putExtra(EXTRA_SOURCE, MessageNano.toByteArray(ev));
+        String[] packages = ((Context)mLauncher).getResources().getStringArray(R.array.launch_broadcast_targets);
+        for(String p: packages) {
+            broadcastIntent.setPackage(p);
+            mLauncher.sendBroadcast(broadcastIntent, mLaunchBroadcastPermission);
         }
+    }
+
+    public final void logLaunch(View v, Intent intent) {
+        LauncherEvent event = LoggerUtils.initLauncherEvent(
+                Action.TOUCH, Target.ITEM, Target.CONTAINER);
+        event.action.touch = Action.TAP;
+
+        // Fill in grid(x,y), pageIndex of the child and container type of the parent
+        // TODO: make this percolate up the view hierarchy if needed.
+        int idx = 0;
+        LaunchSourceProvider provider = getLaunchProviderRecursive(v);
+        provider.fillInLaunchSourceData(v, (ItemInfo) v.getTag(), event.srcTarget[idx], event.srcTarget[idx + 1]);
+
+        // TODO: Fill in all the hashes and the predictedRank
+
+        // Fill in the duration of time spent navigating in Launcher and the container.
         event.elapsedContainerMillis = System.currentTimeMillis() - mElapsedContainerMillis;
         event.elapsedSessionMillis = System.currentTimeMillis() - mElapsedSessionMillis;
         processEvent(event);
+
+        broadcastEvent(event, intent);
+    }
+
+    public void logTap(View v) {
+        // TODO
+    }
+
+    public void logLongPress() {
+        // TODO
+    }
+
+    public void logDragNDrop() {
+        // TODO
+    }
+
+    public void setPredictedApps(List<ComponentKey> predictedApps) {
+        mPredictedApps = predictedApps;
     }
 
     /**
@@ -73,25 +184,22 @@
      */
     public final void resetElapsedContainerMillis() {
         mElapsedContainerMillis = System.currentTimeMillis();
-        if(DEBUG) {
-            Log.d(TAG, "resetElapsedContainerMillis " + mElapsedContainerMillis);
-        }
     }
 
     public final void resetElapsedSessionMillis() {
         mElapsedSessionMillis = System.currentTimeMillis();
         mElapsedContainerMillis = System.currentTimeMillis();
-        if(DEBUG) {
-            Log.d(TAG, "resetElapsedSessionMillis " + mElapsedSessionMillis);
-        }
+
     }
 
     public final void resetActionDurationMillis() {
         mActionDurationMillis = System.currentTimeMillis();
-        if(DEBUG) {
-            Log.d(TAG, "resetElapsedContainerMillis " + mElapsedContainerMillis);
-        }
     }
 
     public abstract void processEvent(LauncherLogProto.LauncherEvent ev);
-}
\ No newline at end of file
+
+    public int getPredictedRank(ComponentKey key) {
+        if (mPredictedApps == null) return -1;
+        return mPredictedApps.indexOf(key);
+    }
+}
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index de8da36..2fde8f3 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -1,6 +1,5 @@
 package com.android.launcher3.testing;
 
-import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -12,9 +11,8 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherCallbacks;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.logging.UserEventLogger;
 import com.android.launcher3.allapps.AllAppsSearchBarController;
+import com.android.launcher3.logging.UserEventLogger;
 import com.android.launcher3.util.ComponentKey;
 
 import java.io.FileDescriptor;