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 android.app.PendingIntent;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.SharedPreferences;
     24 import android.os.SystemClock;
     25 import android.support.annotation.Nullable;
     26 import android.util.Log;
     27 import android.view.View;
     28 import android.view.ViewParent;
     29 
     30 import com.android.launcher3.DropTarget;
     31 import com.android.launcher3.ItemInfo;
     32 import com.android.launcher3.R;
     33 import com.android.launcher3.Utilities;
     34 import com.android.launcher3.config.FeatureFlags;
     35 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
     36 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
     37 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
     38 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
     39 import com.android.launcher3.util.LogConfig;
     40 
     41 import java.util.Locale;
     42 import java.util.UUID;
     43 
     44 import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
     45 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
     46 import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
     47 import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
     48 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
     49 import static com.android.launcher3.logging.LoggerUtils.newTarget;
     50 import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
     51 
     52 /**
     53  * Manages the creation of {@link LauncherEvent}.
     54  * To debug this class, execute following command before side loading a new apk.
     55  *
     56  * $ adb shell setprop log.tag.UserEvent VERBOSE
     57  */
     58 public class UserEventDispatcher {
     59 
     60     private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
     61 
     62     private static final String TAG = "UserEvent";
     63     private static final boolean IS_VERBOSE =
     64             FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
     65     private static final String UUID_STORAGE = "uuid";
     66 
     67     public static UserEventDispatcher newInstance(Context context, boolean isInLandscapeMode,
     68             boolean isInMultiWindowMode) {
     69         SharedPreferences sharedPrefs = Utilities.getDevicePrefs(context);
     70         String uuidStr = sharedPrefs.getString(UUID_STORAGE, null);
     71         if (uuidStr == null) {
     72             uuidStr = UUID.randomUUID().toString();
     73             sharedPrefs.edit().putString(UUID_STORAGE, uuidStr).apply();
     74         }
     75         UserEventDispatcher ued = Utilities.getOverrideObject(UserEventDispatcher.class,
     76                 context.getApplicationContext(), R.string.user_event_dispatcher_class);
     77         ued.mIsInLandscapeMode = isInLandscapeMode;
     78         ued.mIsInMultiWindowMode = isInMultiWindowMode;
     79         ued.mUuidStr = uuidStr;
     80         return ued;
     81     }
     82 
     83     /**
     84      * Implemented by containers to provide a container source for a given child.
     85      */
     86     public interface LogContainerProvider {
     87 
     88         /**
     89          * Copies data from the source to the destination proto.
     90          *
     91          * @param v            source of the data
     92          * @param info         source of the data
     93          * @param target       dest of the data
     94          * @param targetParent dest of the data
     95          */
     96         void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent);
     97     }
     98 
     99     /**
    100      * Recursively finds the parent of the given child which implements IconLogInfoProvider
    101      */
    102     public static LogContainerProvider getLaunchProviderRecursive(@Nullable View v) {
    103         ViewParent parent;
    104         if (v != null) {
    105             parent = v.getParent();
    106         } else {
    107             return null;
    108         }
    109 
    110         // Optimization to only check up to 5 parents.
    111         int count = MAXIMUM_VIEW_HIERARCHY_LEVEL;
    112         while (parent != null && count-- > 0) {
    113             if (parent instanceof LogContainerProvider) {
    114                 return (LogContainerProvider) parent;
    115             } else {
    116                 parent = parent.getParent();
    117             }
    118         }
    119         return null;
    120     }
    121 
    122     private long mElapsedContainerMillis;
    123     private long mElapsedSessionMillis;
    124     private long mActionDurationMillis;
    125     private boolean mIsInMultiWindowMode;
    126     private boolean mIsInLandscapeMode;
    127     private String mUuidStr;
    128 
    129     //                      APP_ICON    SHORTCUT    WIDGET
    130     // --------------------------------------------------------------
    131     // packageNameHash      required    optional    required
    132     // componentNameHash    required                required
    133     // intentHash                       required
    134     // --------------------------------------------------------------
    135 
    136     /**
    137      * Fills in the container data on the given event if the given view is not null.
    138      * @return whether container data was added.
    139      */
    140     protected boolean fillInLogContainerData(LauncherEvent event, @Nullable View v) {
    141         // Fill in grid(x,y), pageIndex of the child and container type of the parent
    142         LogContainerProvider provider = getLaunchProviderRecursive(v);
    143         if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
    144             return false;
    145         }
    146         ItemInfo itemInfo = (ItemInfo) v.getTag();
    147         provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
    148         return true;
    149     }
    150 
    151     public void logAppLaunch(View v, Intent intent) {
    152         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
    153                 newItemTarget(v), newTarget(Target.Type.CONTAINER));
    154 
    155         if (fillInLogContainerData(event, v)) {
    156             fillIntentInfo(event.srcTarget[0], intent);
    157         }
    158         dispatchUserEvent(event, intent);
    159     }
    160 
    161     protected void fillIntentInfo(Target target, Intent intent) {
    162         target.intentHash = intent.hashCode();
    163         ComponentName cn = intent.getComponent();
    164         if (cn != null) {
    165             target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
    166             target.componentHash = (mUuidStr + cn.flattenToString()).hashCode();
    167         }
    168     }
    169 
    170     public void logNotificationLaunch(View v, PendingIntent intent) {
    171         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
    172                 newItemTarget(v), newTarget(Target.Type.CONTAINER));
    173         if (fillInLogContainerData(event, v)) {
    174             event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
    175         }
    176         dispatchUserEvent(event, null);
    177     }
    178 
    179     public void logActionCommand(int command, int containerType) {
    180         logActionCommand(command, containerType, 0);
    181     }
    182 
    183     public void logActionCommand(int command, int containerType, int pageIndex) {
    184         LauncherEvent event = newLauncherEvent(
    185                 newCommandAction(command), newContainerTarget(containerType));
    186         event.srcTarget[0].pageIndex = pageIndex;
    187         dispatchUserEvent(event, null);
    188     }
    189 
    190     /**
    191      * TODO: Make this function work when a container view is passed as the 2nd param.
    192      */
    193     public void logActionCommand(int command, View itemView, int containerType) {
    194         LauncherEvent event = newLauncherEvent(newCommandAction(command),
    195                 newItemTarget(itemView), newTarget(Target.Type.CONTAINER));
    196 
    197         if (fillInLogContainerData(event, itemView)) {
    198             // TODO: Remove the following two lines once fillInLogContainerData can take in a
    199             // container view.
    200             event.srcTarget[0].type = Target.Type.CONTAINER;
    201             event.srcTarget[0].containerType = containerType;
    202         }
    203         dispatchUserEvent(event, null);
    204     }
    205 
    206     public void logActionOnControl(int action, int controlType) {
    207         logActionOnControl(action, controlType, null);
    208     }
    209 
    210     public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer) {
    211         final LauncherEvent event = controlInContainer == null
    212                 ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL))
    213                 : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL),
    214                         newTarget(Target.Type.CONTAINER));
    215         event.srcTarget[0].controlType = controlType;
    216         fillInLogContainerData(event, controlInContainer);
    217         dispatchUserEvent(event, null);
    218     }
    219 
    220     public void logActionTapOutside(Target target) {
    221         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Type.TOUCH),
    222                 target);
    223         event.action.isOutside = true;
    224         dispatchUserEvent(event, null);
    225     }
    226 
    227     public void logActionOnContainer(int action, int dir, int containerType) {
    228         logActionOnContainer(action, dir, containerType, 0);
    229     }
    230 
    231     public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) {
    232         LauncherEvent event = newLauncherEvent(newTouchAction(action),
    233                 newContainerTarget(containerType));
    234         event.action.dir = dir;
    235         event.srcTarget[0].pageIndex = pageIndex;
    236         dispatchUserEvent(event, null);
    237     }
    238 
    239     public void logActionOnItem(int action, int dir, int itemType) {
    240         Target itemTarget = newTarget(Target.Type.ITEM);
    241         itemTarget.itemType = itemType;
    242         LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
    243         event.action.dir = dir;
    244         dispatchUserEvent(event, null);
    245     }
    246 
    247     public void logDeepShortcutsOpen(View icon) {
    248         LogContainerProvider provider = getLaunchProviderRecursive(icon);
    249         if (icon == null || !(icon.getTag() instanceof ItemInfo)) {
    250             return;
    251         }
    252         ItemInfo info = (ItemInfo) icon.getTag();
    253         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
    254                 newItemTarget(info), newTarget(Target.Type.CONTAINER));
    255         provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
    256         dispatchUserEvent(event, null);
    257 
    258         resetElapsedContainerMillis();
    259     }
    260 
    261     /* Currently we are only interested in whether this event happens or not and don't
    262     * care about which screen moves to where. */
    263     public void logOverviewReorder() {
    264         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
    265                 newContainerTarget(ContainerType.WORKSPACE),
    266                 newContainerTarget(ContainerType.OVERVIEW));
    267         dispatchUserEvent(event, null);
    268     }
    269 
    270     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
    271         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
    272                 newItemTarget(dragObj.originalDragInfo), newTarget(Target.Type.CONTAINER));
    273         event.destTarget = new Target[] {
    274                 newItemTarget(dragObj.originalDragInfo), newDropTarget(dropTargetAsView)
    275         };
    276 
    277         dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
    278                 event.srcTarget[0], event.srcTarget[1]);
    279 
    280         if (dropTargetAsView instanceof LogContainerProvider) {
    281             ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null,
    282                     dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
    283 
    284         }
    285         event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
    286         dispatchUserEvent(event, null);
    287     }
    288 
    289     /**
    290      * Currently logs following containers: workspace, allapps, widget tray.
    291      */
    292     public final void resetElapsedContainerMillis() {
    293         mElapsedContainerMillis = SystemClock.uptimeMillis();
    294     }
    295 
    296     public final void resetElapsedSessionMillis() {
    297         mElapsedSessionMillis = SystemClock.uptimeMillis();
    298         mElapsedContainerMillis = SystemClock.uptimeMillis();
    299     }
    300 
    301     public final void resetActionDurationMillis() {
    302         mActionDurationMillis = SystemClock.uptimeMillis();
    303     }
    304 
    305     public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
    306         ev.isInLandscapeMode = mIsInLandscapeMode;
    307         ev.isInMultiWindowMode = mIsInMultiWindowMode;
    308         ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
    309         ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
    310 
    311         if (!IS_VERBOSE) {
    312             return;
    313         }
    314         String log = "action:" + LoggerUtils.getActionStr(ev.action);
    315         if (ev.srcTarget != null && ev.srcTarget.length > 0) {
    316             log += "\n Source " + getTargetsStr(ev.srcTarget);
    317         }
    318         if (ev.destTarget != null && ev.destTarget.length > 0) {
    319             log += "\n Destination " + getTargetsStr(ev.destTarget);
    320         }
    321         log += String.format(Locale.US,
    322                 "\n Elapsed container %d ms session %d ms action %d ms",
    323                 ev.elapsedContainerMillis,
    324                 ev.elapsedSessionMillis,
    325                 ev.actionDurationMillis);
    326         log += "\n isInLandscapeMode " + ev.isInLandscapeMode;
    327         log += "\n isInMultiWindowMode " + ev.isInMultiWindowMode;
    328         Log.d(TAG, log);
    329     }
    330 
    331     private static String getTargetsStr(Target[] targets) {
    332         String result = "child:" + LoggerUtils.getTargetStr(targets[0]);
    333         for (int i = 1; i < targets.length; i++) {
    334             result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]);
    335         }
    336         return result;
    337     }
    338 }
    339