2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.launcher3.logging;
19 import android.app.PendingIntent;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.SystemClock;
24 import android.util.Log;
25 import android.view.View;
26 import android.view.ViewParent;
28 import com.android.launcher3.DropTarget;
29 import com.android.launcher3.ItemInfo;
30 import com.android.launcher3.R;
31 import com.android.launcher3.Utilities;
32 import com.android.launcher3.config.ProviderConfig;
33 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
34 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
35 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
36 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
37 import com.android.launcher3.util.ComponentKey;
38 import com.android.launcher3.util.LogConfig;
40 import java.util.List;
41 import java.util.Locale;
43 import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
44 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
45 import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
46 import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
47 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
48 import static com.android.launcher3.logging.LoggerUtils.newTarget;
49 import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
52 * Manages the creation of {@link LauncherEvent}.
53 * To debug this class, execute following command before side loading a new apk.
55 * $ adb shell setprop log.tag.UserEvent VERBOSE
57 public class UserEventDispatcher {
59 private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
61 private static final String TAG = "UserEvent";
62 private static final boolean IS_VERBOSE =
63 ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
65 public static UserEventDispatcher newInstance(Context context, boolean isInLandscapeMode,
66 boolean isInMultiWindowMode) {
67 UserEventDispatcher ued = Utilities.getOverrideObject(UserEventDispatcher.class,
68 context.getApplicationContext(), R.string.user_event_dispatcher_class);
69 ued.mIsInLandscapeMode = isInLandscapeMode;
70 ued.mIsInMultiWindowMode = isInMultiWindowMode;
75 * Implemented by containers to provide a container source for a given child.
77 public interface LogContainerProvider {
80 * Copies data from the source to the destination proto.
82 * @param v source of the data
83 * @param info source of the data
84 * @param target dest of the data
85 * @param targetParent dest of the data
87 void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent);
91 * Recursively finds the parent of the given child which implements IconLogInfoProvider
93 public static LogContainerProvider getLaunchProviderRecursive(View v) {
96 parent = v.getParent();
101 // Optimization to only check up to 5 parents.
102 int count = MAXIMUM_VIEW_HIERARCHY_LEVEL;
103 while (parent != null && count-- > 0) {
104 if (parent instanceof LogContainerProvider) {
105 return (LogContainerProvider) parent;
107 parent = parent.getParent();
113 private long mElapsedContainerMillis;
114 private long mElapsedSessionMillis;
115 private long mActionDurationMillis;
116 private boolean mIsInMultiWindowMode;
117 private boolean mIsInLandscapeMode;
119 // Used for filling in predictedRank on {@link Target}s.
120 private List<ComponentKey> mPredictedApps;
122 // APP_ICON SHORTCUT WIDGET
123 // --------------------------------------------------------------
124 // packageNameHash required optional required
125 // componentNameHash required required
126 // intentHash required
127 // --------------------------------------------------------------
129 protected LauncherEvent createLauncherEvent(View v, int intentHashCode, ComponentName cn) {
130 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
131 newItemTarget(v), newTarget(Target.Type.CONTAINER));
133 // TODO: make idx percolate up the view hierarchy if needed.
135 if (fillInLogContainerData(event, v)) {
136 ItemInfo itemInfo = (ItemInfo) v.getTag();
137 event.srcTarget[idx].intentHash = intentHashCode;
139 event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
140 event.srcTarget[idx].componentHash = cn.hashCode();
141 if (mPredictedApps != null) {
142 event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
143 new ComponentKey(cn, itemInfo.user));
150 public boolean fillInLogContainerData(LauncherEvent event, View v) {
151 // Fill in grid(x,y), pageIndex of the child and container type of the parent
152 LogContainerProvider provider = getLaunchProviderRecursive(v);
153 if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
156 ItemInfo itemInfo = (ItemInfo) v.getTag();
157 provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
161 public void logAppLaunch(View v, Intent intent) {
162 LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), intent.getComponent());
166 dispatchUserEvent(ev, intent);
169 public void logNotificationLaunch(View v, PendingIntent intent) {
170 ComponentName dummyComponent = new ComponentName(intent.getCreatorPackage(), "--dummy--");
171 LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), dummyComponent);
175 dispatchUserEvent(ev, null);
178 public void logActionCommand(int command, int containerType) {
179 logActionCommand(command, containerType, 0);
182 public void logActionCommand(int command, int containerType, int pageIndex) {
183 LauncherEvent event = newLauncherEvent(
184 newCommandAction(command), newContainerTarget(containerType));
185 event.srcTarget[0].pageIndex = pageIndex;
186 dispatchUserEvent(event, null);
190 * TODO: Make this function work when a container view is passed as the 2nd param.
192 public void logActionCommand(int command, View itemView, int containerType) {
193 LauncherEvent event = newLauncherEvent(newCommandAction(command),
194 newItemTarget(itemView), newTarget(Target.Type.CONTAINER));
196 if (fillInLogContainerData(event, itemView)) {
197 // TODO: Remove the following two lines once fillInLogContainerData can take in a
199 event.srcTarget[0].type = Target.Type.CONTAINER;
200 event.srcTarget[0].containerType = containerType;
202 dispatchUserEvent(event, null);
205 public void logActionOnControl(int action, int controlType) {
206 LauncherEvent event = newLauncherEvent(
207 newTouchAction(action), newTarget(Target.Type.CONTROL));
208 event.srcTarget[0].controlType = controlType;
209 dispatchUserEvent(event, null);
212 public void logActionTapOutside(Target target) {
213 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Type.TOUCH),
215 event.action.isOutside = true;
216 dispatchUserEvent(event, null);
219 public void logActionOnContainer(int action, int dir, int containerType) {
220 logActionOnContainer(action, dir, containerType, 0);
223 public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) {
224 LauncherEvent event = newLauncherEvent(newTouchAction(action),
225 newContainerTarget(containerType));
226 event.action.dir = dir;
227 event.srcTarget[0].pageIndex = pageIndex;
228 dispatchUserEvent(event, null);
231 public void logActionOnItem(int action, int dir, int itemType) {
232 Target itemTarget = newTarget(Target.Type.ITEM);
233 itemTarget.itemType = itemType;
234 LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
235 event.action.dir = dir;
236 dispatchUserEvent(event, null);
239 public void logDeepShortcutsOpen(View icon) {
240 LogContainerProvider provider = getLaunchProviderRecursive(icon);
241 if (icon == null || !(icon.getTag() instanceof ItemInfo)) {
244 ItemInfo info = (ItemInfo) icon.getTag();
245 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
246 newItemTarget(info), newTarget(Target.Type.CONTAINER));
247 provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
248 dispatchUserEvent(event, null);
250 resetElapsedContainerMillis();
253 public void setPredictedApps(List<ComponentKey> predictedApps) {
254 mPredictedApps = predictedApps;
257 /* Currently we are only interested in whether this event happens or not and don't
258 * care about which screen moves to where. */
259 public void logOverviewReorder() {
260 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
261 newContainerTarget(ContainerType.WORKSPACE),
262 newContainerTarget(ContainerType.OVERVIEW));
263 dispatchUserEvent(event, null);
266 public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
267 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
268 newItemTarget(dragObj.originalDragInfo), newTarget(Target.Type.CONTAINER));
269 event.destTarget = new Target[] {
270 newItemTarget(dragObj.originalDragInfo), newDropTarget(dropTargetAsView)
273 dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
274 event.srcTarget[0], event.srcTarget[1]);
276 if (dropTargetAsView instanceof LogContainerProvider) {
277 ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null,
278 dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
281 event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
282 dispatchUserEvent(event, null);
286 * Currently logs following containers: workspace, allapps, widget tray.
288 public final void resetElapsedContainerMillis() {
289 mElapsedContainerMillis = SystemClock.uptimeMillis();
292 public final void resetElapsedSessionMillis() {
293 mElapsedSessionMillis = SystemClock.uptimeMillis();
294 mElapsedContainerMillis = SystemClock.uptimeMillis();
297 public final void resetActionDurationMillis() {
298 mActionDurationMillis = SystemClock.uptimeMillis();
301 public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
302 ev.isInLandscapeMode = mIsInLandscapeMode;
303 ev.isInMultiWindowMode = mIsInMultiWindowMode;
304 ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
305 ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
310 String log = "action:" + LoggerUtils.getActionStr(ev.action);
311 if (ev.srcTarget != null && ev.srcTarget.length > 0) {
312 log += "\n Source " + getTargetsStr(ev.srcTarget);
314 if (ev.destTarget != null && ev.destTarget.length > 0) {
315 log += "\n Destination " + getTargetsStr(ev.destTarget);
317 log += String.format(Locale.US,
318 "\n Elapsed container %d ms session %d ms action %d ms",
319 ev.elapsedContainerMillis,
320 ev.elapsedSessionMillis,
321 ev.actionDurationMillis);
322 log += "\n isInLandscapeMode " + ev.isInLandscapeMode;
323 log += "\n isInMultiWindowMode " + ev.isInMultiWindowMode;
327 private static String getTargetsStr(Target[] targets) {
328 return "child:" + LoggerUtils.getTargetStr(targets[0]) +
329 (targets.length > 1 ? "\tparent:" + LoggerUtils.getTargetStr(targets[1]) : "");