Home | History | Annotate | Download | only in logging
      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 
     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 
     91     public static UserEventDispatcher newInstance(Context context, DeviceProfile dp) {
     92         return newInstance(context, dp, null);
     93     }
     94 
     95     public interface UserEventDelegate {
     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          */
    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      */
    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      */
    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 
    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 
    185     public void logActionTip(int actionType, int viewType) { }
    186 
    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 
    202     protected void fillIntentInfo(Target target, Intent intent) {
    203         target.intentHash = intent.hashCode();
    204         fillComponentInfo(target, intent.getComponent());
    205     }
    206 
    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 
    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 
    223     public void logActionCommand(int command, Target srcTarget) {
    224         logActionCommand(command, srcTarget, null);
    225     }
    226 
    227     public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
    228         logActionCommand(command, newContainerTarget(srcContainerType),
    229                 dstContainerType >=0 ? newContainerTarget(dstContainerType) : null);
    230     }
    231 
    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      */
    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 
    265     public void logActionOnControl(int action, int controlType) {
    266         logActionOnControl(action, controlType, null, -1);
    267     }
    268 
    269     public void logActionOnControl(int action, int controlType, int parentContainerType) {
    270         logActionOnControl(action, controlType, null, parentContainerType);
    271     }
    272 
    273     public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer) {
    274         logActionOnControl(action, controlType, controlInContainer, -1);
    275     }
    276 
    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 
    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 
    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 
    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 
    319     public void logActionOnContainer(int action, int dir, int containerType) {
    320         logActionOnContainer(action, dir, containerType, 0);
    321     }
    322 
    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      */
    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 
    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 
    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. */
    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 
    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      */
    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 
    425     public final void startSession() {
    426         mSessionStarted = true;
    427         mElapsedSessionMillis = SystemClock.uptimeMillis();
    428         mElapsedContainerMillis = SystemClock.uptimeMillis();
    429     }
    430 
    431     public final void resetActionDurationMillis() {
    432         mActionDurationMillis = SystemClock.uptimeMillis();
    433     }
    434 
    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 
    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