• 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.support.annotation.Nullable;
36 import android.util.Log;
37 import android.view.View;
38 import android.view.ViewParent;
39 
40 import com.android.launcher3.DeviceProfile;
41 import com.android.launcher3.DropTarget;
42 import com.android.launcher3.ItemInfo;
43 import com.android.launcher3.R;
44 import com.android.launcher3.Utilities;
45 import com.android.launcher3.config.FeatureFlags;
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.ContainerType;
49 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
50 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
51 import com.android.launcher3.util.ComponentKey;
52 import com.android.launcher3.util.InstantAppResolver;
53 import com.android.launcher3.util.LogConfig;
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 {
65 
66     private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
67 
68     private static final String TAG = "UserEvent";
69     private static final boolean IS_VERBOSE =
70             FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
71     private static final String UUID_STORAGE = "uuid";
72 
newInstance(Context context, DeviceProfile dp, UserEventDelegate delegate)73     public static UserEventDispatcher newInstance(Context context, DeviceProfile dp,
74             UserEventDelegate delegate) {
75         SharedPreferences sharedPrefs = Utilities.getDevicePrefs(context);
76         String uuidStr = sharedPrefs.getString(UUID_STORAGE, null);
77         if (uuidStr == null) {
78             uuidStr = UUID.randomUUID().toString();
79             sharedPrefs.edit().putString(UUID_STORAGE, uuidStr).apply();
80         }
81         UserEventDispatcher ued = Utilities.getOverrideObject(UserEventDispatcher.class,
82                 context.getApplicationContext(), R.string.user_event_dispatcher_class);
83         ued.mDelegate = delegate;
84         ued.mIsInLandscapeMode = dp.isVerticalBarLayout();
85         ued.mIsInMultiWindowMode = dp.isMultiWindowMode;
86         ued.mUuidStr = uuidStr;
87         ued.mInstantAppResolver = InstantAppResolver.newInstance(context);
88         return ued;
89     }
90 
newInstance(Context context, DeviceProfile dp)91     public static UserEventDispatcher newInstance(Context context, DeviceProfile dp) {
92         return newInstance(context, dp, null);
93     }
94 
95     public interface UserEventDelegate {
modifyUserEvent(LauncherEvent event)96         void modifyUserEvent(LauncherEvent event);
97     }
98 
99     /**
100      * Implemented by containers to provide a container source for a given child.
101      */
102     public interface LogContainerProvider {
103 
104         /**
105          * Copies data from the source to the destination proto.
106          *
107          * @param v            source of the data
108          * @param info         source of the data
109          * @param target       dest of the data
110          * @param targetParent dest of the data
111          */
fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent)112         void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent);
113     }
114 
115     /**
116      * Recursively finds the parent of the given child which implements IconLogInfoProvider
117      */
getLaunchProviderRecursive(@ullable View v)118     public static LogContainerProvider getLaunchProviderRecursive(@Nullable View v) {
119         ViewParent parent;
120         if (v != null) {
121             parent = v.getParent();
122         } else {
123             return null;
124         }
125 
126         // Optimization to only check up to 5 parents.
127         int count = MAXIMUM_VIEW_HIERARCHY_LEVEL;
128         while (parent != null && count-- > 0) {
129             if (parent instanceof LogContainerProvider) {
130                 return (LogContainerProvider) parent;
131             } else {
132                 parent = parent.getParent();
133             }
134         }
135         return null;
136     }
137 
138     private boolean mSessionStarted;
139     private long mElapsedContainerMillis;
140     private long mElapsedSessionMillis;
141     private long mActionDurationMillis;
142     private boolean mIsInMultiWindowMode;
143     private boolean mIsInLandscapeMode;
144     private String mUuidStr;
145     protected InstantAppResolver mInstantAppResolver;
146     private boolean mAppOrTaskLaunch;
147     private UserEventDelegate mDelegate;
148 
149     //                      APP_ICON    SHORTCUT    WIDGET
150     // --------------------------------------------------------------
151     // packageNameHash      required    optional    required
152     // componentNameHash    required                required
153     // intentHash                       required
154     // --------------------------------------------------------------
155 
156     /**
157      * Fills in the container data on the given event if the given view is not null.
158      * @return whether container data was added.
159      */
fillInLogContainerData(LauncherEvent event, @Nullable View v)160     protected boolean fillInLogContainerData(LauncherEvent event, @Nullable View v) {
161         // Fill in grid(x,y), pageIndex of the child and container type of the parent
162         LogContainerProvider provider = getLaunchProviderRecursive(v);
163         if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
164             return false;
165         }
166         ItemInfo itemInfo = (ItemInfo) v.getTag();
167         provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
168         return true;
169     }
170 
logAppLaunch(View v, Intent intent)171     public void logAppLaunch(View v, Intent intent) {
172         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
173                 newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
174 
175         if (fillInLogContainerData(event, v)) {
176             if (mDelegate != null) {
177                 mDelegate.modifyUserEvent(event);
178             }
179             fillIntentInfo(event.srcTarget[0], intent);
180         }
181         dispatchUserEvent(event, intent);
182         mAppOrTaskLaunch = true;
183     }
184 
logActionTip(int actionType, int viewType)185     public void logActionTip(int actionType, int viewType) { }
186 
logTaskLaunchOrDismiss(int action, int direction, int taskIndex, ComponentKey componentKey)187     public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex,
188             ComponentKey componentKey) {
189         LauncherEvent event = newLauncherEvent(newTouchAction(action), // TAP or SWIPE or FLING
190                 newTarget(Target.Type.ITEM));
191         if (action == Action.Touch.SWIPE || action == Action.Touch.FLING) {
192             // Direction DOWN means the task was launched, UP means it was dismissed.
193             event.action.dir = direction;
194         }
195         event.srcTarget[0].itemType = LauncherLogProto.ItemType.TASK;
196         event.srcTarget[0].pageIndex = taskIndex;
197         fillComponentInfo(event.srcTarget[0], componentKey.componentName);
198         dispatchUserEvent(event, null);
199         mAppOrTaskLaunch = true;
200     }
201 
fillIntentInfo(Target target, Intent intent)202     protected void fillIntentInfo(Target target, Intent intent) {
203         target.intentHash = intent.hashCode();
204         fillComponentInfo(target, intent.getComponent());
205     }
206 
fillComponentInfo(Target target, ComponentName cn)207     private void fillComponentInfo(Target target, ComponentName cn) {
208         if (cn != null) {
209             target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
210             target.componentHash = (mUuidStr + cn.flattenToString()).hashCode();
211         }
212     }
213 
logNotificationLaunch(View v, PendingIntent intent)214     public void logNotificationLaunch(View v, PendingIntent intent) {
215         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
216                 newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
217         if (fillInLogContainerData(event, v)) {
218             event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
219         }
220         dispatchUserEvent(event, null);
221     }
222 
logActionCommand(int command, Target srcTarget)223     public void logActionCommand(int command, Target srcTarget) {
224         logActionCommand(command, srcTarget, null);
225     }
226 
logActionCommand(int command, int srcContainerType, int dstContainerType)227     public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
228         logActionCommand(command, newContainerTarget(srcContainerType),
229                 dstContainerType >=0 ? newContainerTarget(dstContainerType) : null);
230     }
231 
logActionCommand(int command, Target srcTarget, Target dstTarget)232     public void logActionCommand(int command, Target srcTarget, Target dstTarget) {
233         LauncherEvent event = newLauncherEvent(newCommandAction(command), srcTarget);
234         if (command == Action.Command.STOP) {
235             if (mAppOrTaskLaunch || !mSessionStarted) {
236                 mSessionStarted = false;
237                 return;
238             }
239         }
240 
241         if (dstTarget != null) {
242             event.destTarget = new Target[1];
243             event.destTarget[0] = dstTarget;
244             event.action.isStateChange = true;
245         }
246         dispatchUserEvent(event, null);
247     }
248 
249     /**
250      * TODO: Make this function work when a container view is passed as the 2nd param.
251      */
logActionCommand(int command, View itemView, int srcContainerType)252     public void logActionCommand(int command, View itemView, int srcContainerType) {
253         LauncherEvent event = newLauncherEvent(newCommandAction(command),
254                 newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
255 
256         if (fillInLogContainerData(event, itemView)) {
257             // TODO: Remove the following two lines once fillInLogContainerData can take in a
258             // container view.
259             event.srcTarget[0].type = Target.Type.CONTAINER;
260             event.srcTarget[0].containerType = srcContainerType;
261         }
262         dispatchUserEvent(event, null);
263     }
264 
logActionOnControl(int action, int controlType)265     public void logActionOnControl(int action, int controlType) {
266         logActionOnControl(action, controlType, null, -1);
267     }
268 
logActionOnControl(int action, int controlType, int parentContainerType)269     public void logActionOnControl(int action, int controlType, int parentContainerType) {
270         logActionOnControl(action, controlType, null, parentContainerType);
271     }
272 
logActionOnControl(int action, int controlType, @Nullable View controlInContainer)273     public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer) {
274         logActionOnControl(action, controlType, controlInContainer, -1);
275     }
276 
logActionOnControl(int action, int controlType, int parentContainer, int grandParentContainer)277     public void logActionOnControl(int action, int controlType, int parentContainer,
278                                    int grandParentContainer){
279         LauncherEvent event = newLauncherEvent(newTouchAction(action),
280                 newControlTarget(controlType),
281                 newContainerTarget(parentContainer),
282                 newContainerTarget(grandParentContainer));
283         dispatchUserEvent(event, null);
284     }
285 
logActionOnControl(int action, int controlType, @Nullable View controlInContainer, int parentContainerType)286     public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer,
287                                    int parentContainerType) {
288         final LauncherEvent event = (controlInContainer == null && parentContainerType < 0)
289                 ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL))
290                 : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL),
291                         newTarget(Target.Type.CONTAINER));
292         event.srcTarget[0].controlType = controlType;
293         if (controlInContainer != null) {
294             fillInLogContainerData(event, controlInContainer);
295         }
296         if (parentContainerType >= 0) {
297             event.srcTarget[1].containerType = parentContainerType;
298         }
299         if (action == Action.Touch.DRAGDROP) {
300             event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
301         }
302         dispatchUserEvent(event, null);
303     }
304 
logActionTapOutside(Target target)305     public void logActionTapOutside(Target target) {
306         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Type.TOUCH),
307                 target);
308         event.action.isOutside = true;
309         dispatchUserEvent(event, null);
310     }
311 
logActionBounceTip(int containerType)312     public void logActionBounceTip(int containerType) {
313         LauncherEvent event = newLauncherEvent(newAction(Action.Type.TIP),
314                 newContainerTarget(containerType));
315         event.srcTarget[0].tipType = LauncherLogProto.TipType.BOUNCE;
316         dispatchUserEvent(event, null);
317     }
318 
logActionOnContainer(int action, int dir, int containerType)319     public void logActionOnContainer(int action, int dir, int containerType) {
320         logActionOnContainer(action, dir, containerType, 0);
321     }
322 
logActionOnContainer(int action, int dir, int containerType, int pageIndex)323     public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) {
324         LauncherEvent event = newLauncherEvent(newTouchAction(action),
325                 newContainerTarget(containerType));
326         event.action.dir = dir;
327         event.srcTarget[0].pageIndex = pageIndex;
328         dispatchUserEvent(event, null);
329     }
330 
331     /**
332      * Used primarily for swipe up and down when state changes when swipe up happens from the
333      * navbar bezel, the {@param srcChildContainerType} is NAVBAR and
334      * {@param srcParentContainerType} is either one of the two
335      * (1) WORKSPACE: if the launcher is the foreground activity
336      * (2) APP: if another app was the foreground activity
337      */
logStateChangeAction(int action, int dir, int srcChildTargetType, int srcParentContainerType, int dstContainerType, int pageIndex)338     public void logStateChangeAction(int action, int dir, int srcChildTargetType,
339                                      int srcParentContainerType, int dstContainerType,
340                                      int pageIndex) {
341         LauncherEvent event;
342         if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
343             event = newLauncherEvent(newTouchAction(action),
344                     newItemTarget(srcChildTargetType),
345                     newContainerTarget(srcParentContainerType));
346         } else {
347             event = newLauncherEvent(newTouchAction(action),
348                     newContainerTarget(srcChildTargetType),
349                     newContainerTarget(srcParentContainerType));
350         }
351         event.destTarget = new Target[1];
352         event.destTarget[0] = newContainerTarget(dstContainerType);
353         event.action.dir = dir;
354         event.action.isStateChange = true;
355         event.srcTarget[0].pageIndex = pageIndex;
356         dispatchUserEvent(event, null);
357         resetElapsedContainerMillis("state changed");
358     }
359 
logActionOnItem(int action, int dir, int itemType)360     public void logActionOnItem(int action, int dir, int itemType) {
361         Target itemTarget = newTarget(Target.Type.ITEM);
362         itemTarget.itemType = itemType;
363         LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
364         event.action.dir = dir;
365         dispatchUserEvent(event, null);
366     }
367 
logDeepShortcutsOpen(View icon)368     public void logDeepShortcutsOpen(View icon) {
369         LogContainerProvider provider = getLaunchProviderRecursive(icon);
370         if (icon == null || !(icon.getTag() instanceof ItemInfo)) {
371             return;
372         }
373         ItemInfo info = (ItemInfo) icon.getTag();
374         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
375                 newItemTarget(info, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
376         provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
377         dispatchUserEvent(event, null);
378 
379         resetElapsedContainerMillis("deep shortcut open");
380     }
381 
382     /* Currently we are only interested in whether this event happens or not and don't
383     * care about which screen moves to where. */
logOverviewReorder()384     public void logOverviewReorder() {
385         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
386                 newContainerTarget(ContainerType.WORKSPACE),
387                 newContainerTarget(ContainerType.OVERVIEW));
388         dispatchUserEvent(event, null);
389     }
390 
logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView)391     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
392         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
393                 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
394                 newTarget(Target.Type.CONTAINER));
395         event.destTarget = new Target[] {
396                 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
397                 newDropTarget(dropTargetAsView)
398         };
399 
400         dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
401                 event.srcTarget[0], event.srcTarget[1]);
402 
403         if (dropTargetAsView instanceof LogContainerProvider) {
404             ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null,
405                     dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
406 
407         }
408         event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
409         dispatchUserEvent(event, null);
410     }
411 
412     /**
413      * Currently logs following containers: workspace, allapps, widget tray.
414      * @param reason
415      */
resetElapsedContainerMillis(String reason)416     public final void resetElapsedContainerMillis(String reason) {
417         mElapsedContainerMillis = SystemClock.uptimeMillis();
418         if (!IS_VERBOSE) {
419             return;
420         }
421         Log.d(TAG, "resetElapsedContainerMillis reason=" + reason);
422 
423     }
424 
startSession()425     public final void startSession() {
426         mSessionStarted = true;
427         mElapsedSessionMillis = SystemClock.uptimeMillis();
428         mElapsedContainerMillis = SystemClock.uptimeMillis();
429     }
430 
resetActionDurationMillis()431     public final void resetActionDurationMillis() {
432         mActionDurationMillis = SystemClock.uptimeMillis();
433     }
434 
dispatchUserEvent(LauncherEvent ev, Intent intent)435     public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
436         mAppOrTaskLaunch = false;
437         ev.isInLandscapeMode = mIsInLandscapeMode;
438         ev.isInMultiWindowMode = mIsInMultiWindowMode;
439         ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
440         ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
441 
442         if (!IS_VERBOSE) {
443             return;
444         }
445         String log = "\n-----------------------------------------------------"
446                 + "\naction:" + LoggerUtils.getActionStr(ev.action);
447         if (ev.srcTarget != null && ev.srcTarget.length > 0) {
448             log += "\n Source " + getTargetsStr(ev.srcTarget);
449         }
450         if (ev.destTarget != null && ev.destTarget.length > 0) {
451             log += "\n Destination " + getTargetsStr(ev.destTarget);
452         }
453         log += String.format(Locale.US,
454                 "\n Elapsed container %d ms, session %d ms, action %d ms",
455                 ev.elapsedContainerMillis,
456                 ev.elapsedSessionMillis,
457                 ev.actionDurationMillis);
458         log += "\n isInLandscapeMode " + ev.isInLandscapeMode;
459         log += "\n isInMultiWindowMode " + ev.isInMultiWindowMode;
460         log += "\n\n";
461         Log.d(TAG, log);
462     }
463 
getTargetsStr(Target[] targets)464     private static String getTargetsStr(Target[] targets) {
465         String result = "child:" + LoggerUtils.getTargetStr(targets[0]);
466         for (int i = 1; i < targets.length; i++) {
467             result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]);
468         }
469         return result;
470     }
471 }
472