• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package com.android.launcher3.logging;
18 
19 import static com.android.launcher3.logging.LoggerUtils.newAction;
20 import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
21 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
22 import static com.android.launcher3.logging.LoggerUtils.newControlTarget;
23 import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
24 import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
25 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
26 import static com.android.launcher3.logging.LoggerUtils.newTarget;
27 import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
28 
29 import android.app.PendingIntent;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.SharedPreferences;
34 import android.os.SystemClock;
35 import android.util.Log;
36 import android.view.View;
37 
38 import androidx.annotation.Nullable;
39 
40 import com.android.launcher3.DropTarget;
41 import com.android.launcher3.ItemInfo;
42 import com.android.launcher3.R;
43 import com.android.launcher3.Utilities;
44 import com.android.launcher3.config.FeatureFlags;
45 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
46 import com.android.launcher3.userevent.nano.LauncherLogProto;
47 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
48 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
49 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
50 import com.android.launcher3.util.ComponentKey;
51 import com.android.launcher3.util.InstantAppResolver;
52 import com.android.launcher3.util.LogConfig;
53 import com.android.launcher3.util.ResourceBasedOverride;
54 
55 import java.util.Locale;
56 import java.util.UUID;
57 
58 /**
59  * Manages the creation of {@link LauncherEvent}.
60  * To debug this class, execute following command before side loading a new apk.
61  *
62  * $ adb shell setprop log.tag.UserEvent VERBOSE
63  */
64 public class UserEventDispatcher implements ResourceBasedOverride {
65 
66     private static final String TAG = "UserEvent";
67     private static final boolean IS_VERBOSE =
68             FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
69     private static final String UUID_STORAGE = "uuid";
70 
newInstance(Context context, UserEventDelegate delegate)71     public static UserEventDispatcher newInstance(Context context,
72             UserEventDelegate delegate) {
73         SharedPreferences sharedPrefs = Utilities.getDevicePrefs(context);
74         String uuidStr = sharedPrefs.getString(UUID_STORAGE, null);
75         if (uuidStr == null) {
76             uuidStr = UUID.randomUUID().toString();
77             sharedPrefs.edit().putString(UUID_STORAGE, uuidStr).apply();
78         }
79         UserEventDispatcher ued = Overrides.getObject(UserEventDispatcher.class,
80                 context.getApplicationContext(), R.string.user_event_dispatcher_class);
81         ued.mDelegate = delegate;
82         ued.mUuidStr = uuidStr;
83         ued.mInstantAppResolver = InstantAppResolver.newInstance(context);
84         return ued;
85     }
86 
newInstance(Context context)87     public static UserEventDispatcher newInstance(Context context) {
88         return newInstance(context, null);
89     }
90 
91     public interface UserEventDelegate {
modifyUserEvent(LauncherEvent event)92         void modifyUserEvent(LauncherEvent event);
93     }
94 
95     /**
96      * Fills in the container data on the given event if the given view is not null.
97      * @return whether container data was added.
98      */
fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v)99     public static boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
100         // Fill in grid(x,y), pageIndex of the child and container type of the parent
101         LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
102         if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
103             return false;
104         }
105         ItemInfo itemInfo = (ItemInfo) v.getTag();
106         provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
107         return true;
108     }
109 
110     private boolean mSessionStarted;
111     private long mElapsedContainerMillis;
112     private long mElapsedSessionMillis;
113     private long mActionDurationMillis;
114     private String mUuidStr;
115     protected InstantAppResolver mInstantAppResolver;
116     private boolean mAppOrTaskLaunch;
117     private UserEventDelegate mDelegate;
118 
119     //                      APP_ICON    SHORTCUT    WIDGET
120     // --------------------------------------------------------------
121     // packageNameHash      required    optional    required
122     // componentNameHash    required                required
123     // intentHash                       required
124     // --------------------------------------------------------------
125 
126     @Deprecated
logAppLaunch(View v, Intent intent)127     public void logAppLaunch(View v, Intent intent) {
128         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
129                 newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
130 
131         if (fillInLogContainerData(event, v)) {
132             if (mDelegate != null) {
133                 mDelegate.modifyUserEvent(event);
134             }
135             fillIntentInfo(event.srcTarget[0], intent);
136         }
137         dispatchUserEvent(event, intent);
138         mAppOrTaskLaunch = true;
139     }
140 
logActionTip(int actionType, int viewType)141     public void logActionTip(int actionType, int viewType) { }
142 
143     @Deprecated
logTaskLaunchOrDismiss(int action, int direction, int taskIndex, ComponentKey componentKey)144     public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex,
145             ComponentKey componentKey) {
146         LauncherEvent event = newLauncherEvent(newTouchAction(action), // TAP or SWIPE or FLING
147                 newTarget(Target.Type.ITEM));
148         if (action == Action.Touch.SWIPE || action == Action.Touch.FLING) {
149             // Direction DOWN means the task was launched, UP means it was dismissed.
150             event.action.dir = direction;
151         }
152         event.srcTarget[0].itemType = LauncherLogProto.ItemType.TASK;
153         event.srcTarget[0].pageIndex = taskIndex;
154         fillComponentInfo(event.srcTarget[0], componentKey.componentName);
155         dispatchUserEvent(event, null);
156         mAppOrTaskLaunch = true;
157     }
158 
fillIntentInfo(Target target, Intent intent)159     protected void fillIntentInfo(Target target, Intent intent) {
160         target.intentHash = intent.hashCode();
161         fillComponentInfo(target, intent.getComponent());
162     }
163 
fillComponentInfo(Target target, ComponentName cn)164     private void fillComponentInfo(Target target, ComponentName cn) {
165         if (cn != null) {
166             target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
167             target.componentHash = (mUuidStr + cn.flattenToString()).hashCode();
168         }
169     }
170 
logNotificationLaunch(View v, PendingIntent intent)171     public void logNotificationLaunch(View v, PendingIntent intent) {
172         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
173                 newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
174         if (fillInLogContainerData(event, v)) {
175             event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
176         }
177         dispatchUserEvent(event, null);
178     }
179 
logActionCommand(int command, Target srcTarget)180     public void logActionCommand(int command, Target srcTarget) {
181         logActionCommand(command, srcTarget, null);
182     }
183 
logActionCommand(int command, int srcContainerType, int dstContainerType)184     public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
185         logActionCommand(command, newContainerTarget(srcContainerType),
186                 dstContainerType >=0 ? newContainerTarget(dstContainerType) : null);
187     }
188 
logActionCommand(int command, Target srcTarget, Target dstTarget)189     public void logActionCommand(int command, Target srcTarget, Target dstTarget) {
190         LauncherEvent event = newLauncherEvent(newCommandAction(command), srcTarget);
191         if (command == Action.Command.STOP) {
192             if (mAppOrTaskLaunch || !mSessionStarted) {
193                 mSessionStarted = false;
194                 return;
195             }
196         }
197 
198         if (dstTarget != null) {
199             event.destTarget = new Target[1];
200             event.destTarget[0] = dstTarget;
201             event.action.isStateChange = true;
202         }
203         dispatchUserEvent(event, null);
204     }
205 
206     /**
207      * TODO: Make this function work when a container view is passed as the 2nd param.
208      */
logActionCommand(int command, View itemView, int srcContainerType)209     public void logActionCommand(int command, View itemView, int srcContainerType) {
210         LauncherEvent event = newLauncherEvent(newCommandAction(command),
211                 newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
212 
213         if (fillInLogContainerData(event, itemView)) {
214             // TODO: Remove the following two lines once fillInLogContainerData can take in a
215             // container view.
216             event.srcTarget[0].type = Target.Type.CONTAINER;
217             event.srcTarget[0].containerType = srcContainerType;
218         }
219         dispatchUserEvent(event, null);
220     }
221 
logActionOnControl(int action, int controlType)222     public void logActionOnControl(int action, int controlType) {
223         logActionOnControl(action, controlType, null, -1);
224     }
225 
logActionOnControl(int action, int controlType, int parentContainerType)226     public void logActionOnControl(int action, int controlType, int parentContainerType) {
227         logActionOnControl(action, controlType, null, parentContainerType);
228     }
229 
logActionOnControl(int action, int controlType, @Nullable View controlInContainer)230     public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer) {
231         logActionOnControl(action, controlType, controlInContainer, -1);
232     }
233 
logActionOnControl(int action, int controlType, int parentContainer, int grandParentContainer)234     public void logActionOnControl(int action, int controlType, int parentContainer,
235                                    int grandParentContainer){
236         LauncherEvent event = newLauncherEvent(newTouchAction(action),
237                 newControlTarget(controlType),
238                 newContainerTarget(parentContainer),
239                 newContainerTarget(grandParentContainer));
240         dispatchUserEvent(event, null);
241     }
242 
logActionOnControl(int action, int controlType, @Nullable View controlInContainer, int parentContainerType)243     public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer,
244                                    int parentContainerType) {
245         final LauncherEvent event = (controlInContainer == null && parentContainerType < 0)
246                 ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL))
247                 : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL),
248                         newTarget(Target.Type.CONTAINER));
249         event.srcTarget[0].controlType = controlType;
250         if (controlInContainer != null) {
251             fillInLogContainerData(event, controlInContainer);
252         }
253         if (parentContainerType >= 0) {
254             event.srcTarget[1].containerType = parentContainerType;
255         }
256         if (action == Action.Touch.DRAGDROP) {
257             event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
258         }
259         dispatchUserEvent(event, null);
260     }
261 
logActionTapOutside(Target target)262     public void logActionTapOutside(Target target) {
263         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Type.TOUCH),
264                 target);
265         event.action.isOutside = true;
266         dispatchUserEvent(event, null);
267     }
268 
logActionBounceTip(int containerType)269     public void logActionBounceTip(int containerType) {
270         LauncherEvent event = newLauncherEvent(newAction(Action.Type.TIP),
271                 newContainerTarget(containerType));
272         event.srcTarget[0].tipType = LauncherLogProto.TipType.BOUNCE;
273         dispatchUserEvent(event, null);
274     }
275 
logActionOnContainer(int action, int dir, int containerType)276     public void logActionOnContainer(int action, int dir, int containerType) {
277         logActionOnContainer(action, dir, containerType, 0);
278     }
279 
logActionOnContainer(int action, int dir, int containerType, int pageIndex)280     public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) {
281         LauncherEvent event = newLauncherEvent(newTouchAction(action),
282                 newContainerTarget(containerType));
283         event.action.dir = dir;
284         event.srcTarget[0].pageIndex = pageIndex;
285         dispatchUserEvent(event, null);
286     }
287 
288     /**
289      * Used primarily for swipe up and down when state changes when swipe up happens from the
290      * navbar bezel, the {@param srcChildContainerType} is NAVBAR and
291      * {@param srcParentContainerType} is either one of the two
292      * (1) WORKSPACE: if the launcher is the foreground activity
293      * (2) APP: if another app was the foreground activity
294      */
logStateChangeAction(int action, int dir, int downX, int downY, int srcChildTargetType, int srcParentContainerType, int dstContainerType, int pageIndex)295     public void logStateChangeAction(int action, int dir, int downX, int downY, int srcChildTargetType,
296                                      int srcParentContainerType, int dstContainerType,
297                                      int pageIndex) {
298         LauncherEvent event;
299         if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
300             event = newLauncherEvent(newTouchAction(action),
301                     newItemTarget(srcChildTargetType),
302                     newContainerTarget(srcParentContainerType));
303         } else {
304             event = newLauncherEvent(newTouchAction(action),
305                     newContainerTarget(srcChildTargetType),
306                     newContainerTarget(srcParentContainerType));
307         }
308         event.destTarget = new Target[1];
309         event.destTarget[0] = newContainerTarget(dstContainerType);
310         event.action.dir = dir;
311         event.action.isStateChange = true;
312         event.srcTarget[0].pageIndex = pageIndex;
313         event.srcTarget[0].spanX = downX;
314         event.srcTarget[0].spanY = downY;
315         dispatchUserEvent(event, null);
316         resetElapsedContainerMillis("state changed");
317     }
318 
logActionOnItem(int action, int dir, int itemType)319     public void logActionOnItem(int action, int dir, int itemType) {
320         Target itemTarget = newTarget(Target.Type.ITEM);
321         itemTarget.itemType = itemType;
322         LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
323         event.action.dir = dir;
324         dispatchUserEvent(event, null);
325     }
326 
logDeepShortcutsOpen(View icon)327     public void logDeepShortcutsOpen(View icon) {
328         LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(icon);
329         if (icon == null || !(icon.getTag() instanceof ItemInfo || provider == null)) {
330             return;
331         }
332         ItemInfo info = (ItemInfo) icon.getTag();
333         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
334                 newItemTarget(info, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
335         provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
336         dispatchUserEvent(event, null);
337 
338         resetElapsedContainerMillis("deep shortcut open");
339     }
340 
logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView)341     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
342         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
343                 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
344                 newTarget(Target.Type.CONTAINER));
345         event.destTarget = new Target[] {
346                 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
347                 newDropTarget(dropTargetAsView)
348         };
349 
350         dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
351                 event.srcTarget[0], event.srcTarget[1]);
352 
353         if (dropTargetAsView instanceof LogContainerProvider) {
354             ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null,
355                     dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
356 
357         }
358         event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
359         dispatchUserEvent(event, null);
360     }
361 
logActionBack(boolean completed, int downX, int downY, boolean isButton, boolean gestureSwipeLeft, int containerType)362     public void logActionBack(boolean completed, int downX, int downY, boolean isButton,
363             boolean gestureSwipeLeft, int containerType) {
364         int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE;
365         Action action = newCommandAction(actionTouch);
366         action.command = Action.Command.BACK;
367         action.dir = isButton
368                 ? Action.Direction.NONE
369                 : gestureSwipeLeft
370                         ? Action.Direction.LEFT
371                         : Action.Direction.RIGHT;
372         Target target = newControlTarget(isButton
373                 ? LauncherLogProto.ControlType.BACK_BUTTON
374                 : LauncherLogProto.ControlType.BACK_GESTURE);
375         target.spanX = downX;
376         target.spanY = downY;
377         target.cardinality = completed ? 1 : 0;
378         LauncherEvent event = newLauncherEvent(action, target, newContainerTarget(containerType));
379 
380         dispatchUserEvent(event, null);
381     }
382 
383     /**
384      * Currently logs following containers: workspace, allapps, widget tray.
385      * @param reason
386      */
resetElapsedContainerMillis(String reason)387     public final void resetElapsedContainerMillis(String reason) {
388         mElapsedContainerMillis = SystemClock.uptimeMillis();
389         if (!IS_VERBOSE) {
390             return;
391         }
392         Log.d(TAG, "resetElapsedContainerMillis reason=" + reason);
393 
394     }
395 
startSession()396     public final void startSession() {
397         mSessionStarted = true;
398         mElapsedSessionMillis = SystemClock.uptimeMillis();
399         mElapsedContainerMillis = SystemClock.uptimeMillis();
400     }
401 
resetActionDurationMillis()402     public final void resetActionDurationMillis() {
403         mActionDurationMillis = SystemClock.uptimeMillis();
404     }
405 
dispatchUserEvent(LauncherEvent ev, Intent intent)406     public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
407         mAppOrTaskLaunch = false;
408         ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
409         ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
410 
411         if (!IS_VERBOSE) {
412             return;
413         }
414         String log = "\n-----------------------------------------------------"
415                 + "\naction:" + LoggerUtils.getActionStr(ev.action);
416         if (ev.srcTarget != null && ev.srcTarget.length > 0) {
417             log += "\n Source " + getTargetsStr(ev.srcTarget);
418         }
419         if (ev.destTarget != null && ev.destTarget.length > 0) {
420             log += "\n Destination " + getTargetsStr(ev.destTarget);
421         }
422         log += String.format(Locale.US,
423                 "\n Elapsed container %d ms, session %d ms, action %d ms",
424                 ev.elapsedContainerMillis,
425                 ev.elapsedSessionMillis,
426                 ev.actionDurationMillis);
427         log += "\n\n";
428         Log.d(TAG, log);
429     }
430 
getTargetsStr(Target[] targets)431     private static String getTargetsStr(Target[] targets) {
432         String result = "child:" + LoggerUtils.getTargetStr(targets[0]);
433         for (int i = 1; i < targets.length; i++) {
434             result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]);
435         }
436         return result;
437     }
438 }
439