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.content.ComponentName;
     20 import android.content.Intent;
     21 import android.os.SystemClock;
     22 import android.util.Log;
     23 import android.view.View;
     24 import android.view.ViewParent;
     25 
     26 import com.android.launcher3.DropTarget;
     27 import com.android.launcher3.ItemInfo;
     28 import com.android.launcher3.Utilities;
     29 import com.android.launcher3.config.ProviderConfig;
     30 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
     31 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
     32 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
     33 import com.android.launcher3.util.ComponentKey;
     34 
     35 import java.util.List;
     36 import java.util.Locale;
     37 
     38 /**
     39  * Manages the creation of {@link LauncherEvent}.
     40  * To debug this class, execute following command before sideloading a new apk.
     41  *
     42  * $ adb shell setprop log.tag.UserEvent VERBOSE
     43  */
     44 public class UserEventDispatcher {
     45 
     46     private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
     47 
     48     private final boolean mIsVerbose;
     49 
     50     /**
     51      * TODO: change the name of this interface to LogContainerProvider
     52      * and the method name to fillInLogContainerData. Not changed to minimize CL diff
     53      * in this branch.
     54      *
     55      * Implemented by containers to provide a launch source for a given child.
     56      */
     57     public interface LaunchSourceProvider {
     58 
     59         /**
     60          * Copies data from the source to the destination proto.
     61          *
     62          * @param v            source of the data
     63          * @param info         source of the data
     64          * @param target       dest of the data
     65          * @param targetParent dest of the data
     66          */
     67         void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent);
     68     }
     69 
     70     /**
     71      * Recursively finds the parent of the given child which implements IconLogInfoProvider
     72      */
     73     public static LaunchSourceProvider getLaunchProviderRecursive(View v) {
     74         ViewParent parent = null;
     75 
     76         if (v != null) {
     77             parent = v.getParent();
     78         } else {
     79             return null;
     80         }
     81 
     82         // Optimization to only check up to 5 parents.
     83         int count = MAXIMUM_VIEW_HIERARCHY_LEVEL;
     84         while (parent != null && count-- > 0) {
     85             if (parent instanceof LaunchSourceProvider) {
     86                 return (LaunchSourceProvider) parent;
     87             } else {
     88                 parent = parent.getParent();
     89             }
     90         }
     91         return null;
     92     }
     93 
     94     private String TAG = "UserEvent";
     95 
     96     private long mElapsedContainerMillis;
     97     private long mElapsedSessionMillis;
     98     private long mActionDurationMillis;
     99 
    100     // Used for filling in predictedRank on {@link Target}s.
    101     private List<ComponentKey> mPredictedApps;
    102 
    103     public UserEventDispatcher() {
    104         if (ProviderConfig.IS_DOGFOOD_BUILD) {
    105             mIsVerbose = Utilities.isPropertyEnabled(TAG);
    106         } else {
    107             mIsVerbose = false;
    108         }
    109     }
    110 
    111     //                      APP_ICON    SHORTCUT    WIDGET
    112     // --------------------------------------------------------------
    113     // packageNameHash      required    optional    required
    114     // componentNameHash    required                required
    115     // intentHash                       required
    116     // --------------------------------------------------------------
    117 
    118     protected LauncherEvent createLauncherEvent(View v, Intent intent) {
    119         LauncherEvent event = LoggerUtils.initLauncherEvent(
    120                 Action.TOUCH, v, Target.CONTAINER);
    121         event.action.touch = Action.TAP;
    122 
    123         // Fill in grid(x,y), pageIndex of the child and container type of the parent
    124         // TODO: make this percolate up the view hierarchy if needed.
    125         int idx = 0;
    126         LaunchSourceProvider provider = getLaunchProviderRecursive(v);
    127         if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
    128             return null;
    129         }
    130         ItemInfo itemInfo = (ItemInfo) v.getTag();
    131         provider.fillInLaunchSourceData(v, itemInfo, event.srcTarget[idx], event.srcTarget[idx + 1]);
    132 
    133         event.srcTarget[idx].intentHash = intent.hashCode();
    134         ComponentName cn = intent.getComponent();
    135         if (cn != null) {
    136             event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
    137             event.srcTarget[idx].componentHash = cn.hashCode();
    138             if (mPredictedApps != null) {
    139                 event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
    140                         new ComponentKey(cn, itemInfo.user));
    141             }
    142         }
    143         return event;
    144     }
    145 
    146     public void logAppLaunch(View v, Intent intent) {
    147         LauncherEvent ev = createLauncherEvent(v, intent);
    148         if (ev == null) {
    149             return;
    150         }
    151         dispatchUserEvent(ev, intent);
    152     }
    153 
    154     public void logActionOnItem(int action, int itemType) {
    155         LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.ITEM);
    156         event.action.touch = action;
    157         event.srcTarget[0].itemType = itemType;
    158         dispatchUserEvent(event, null);
    159     }
    160 
    161     public void logActionOnControl(int action, int controlType) {
    162         LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTROL);
    163         event.action.touch = action;
    164         event.srcTarget[0].controlType = controlType;
    165         dispatchUserEvent(event, null);
    166     }
    167 
    168     public void logActionOnContainer(int action, int dir, int containerType) {
    169         LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTAINER);
    170         event.action.touch = action;
    171         event.action.dir = dir;
    172         event.srcTarget[0].containerType = containerType;
    173         dispatchUserEvent(event, null);
    174     }
    175 
    176     public void logDeepShortcutsOpen(View icon) {
    177         LauncherEvent event = LoggerUtils.initLauncherEvent(
    178                 Action.TOUCH, icon, Target.CONTAINER);
    179         LaunchSourceProvider provider = getLaunchProviderRecursive(icon);
    180         if (icon == null && !(icon.getTag() instanceof ItemInfo)) {
    181             return;
    182         }
    183         ItemInfo info = (ItemInfo) icon.getTag();
    184         provider.fillInLaunchSourceData(icon, info, event.srcTarget[0], event.srcTarget[1]);
    185         event.action.touch = Action.LONGPRESS;
    186         dispatchUserEvent(event, null);
    187     }
    188 
    189     public void setPredictedApps(List<ComponentKey> predictedApps) {
    190         mPredictedApps = predictedApps;
    191     }
    192 
    193     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
    194         LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH,
    195                 dragObj.dragView,
    196                 dragObj.originalDragInfo,
    197                 Target.CONTAINER,
    198                 dropTargetAsView);
    199         event.action.touch = Action.DRAGDROP;
    200 
    201         dragObj.dragSource.fillInLaunchSourceData(null, dragObj.originalDragInfo,
    202                 event.srcTarget[0], event.srcTarget[1]);
    203 
    204         if (dropTargetAsView instanceof LaunchSourceProvider) {
    205             ((LaunchSourceProvider) dropTargetAsView).fillInLaunchSourceData(null,
    206                     dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
    207 
    208         }
    209         event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
    210         dispatchUserEvent(event, null);
    211     }
    212 
    213     /**
    214      * Currently logs following containers: workspace, allapps, widget tray.
    215      */
    216     public final void resetElapsedContainerMillis() {
    217         mElapsedContainerMillis = SystemClock.uptimeMillis();
    218     }
    219 
    220     public final void resetElapsedSessionMillis() {
    221         mElapsedSessionMillis = SystemClock.uptimeMillis();
    222         mElapsedContainerMillis = SystemClock.uptimeMillis();
    223     }
    224 
    225     public final void resetActionDurationMillis() {
    226         mActionDurationMillis = SystemClock.uptimeMillis();
    227     }
    228 
    229     public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
    230         ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
    231         ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
    232 
    233         if (!mIsVerbose) {
    234             return;
    235         }
    236         Log.d(TAG, String.format(Locale.US,
    237                 "\naction:%s\n Source child:%s\tparent:%s",
    238                 LoggerUtils.getActionStr(ev.action),
    239                 LoggerUtils.getTargetStr(ev.srcTarget != null ? ev.srcTarget[0] : null),
    240                 LoggerUtils.getTargetStr(ev.srcTarget != null && ev.srcTarget.length > 1 ?
    241                         ev.srcTarget[1] : null)));
    242         if (ev.destTarget != null && ev.destTarget.length > 0) {
    243             Log.d(TAG, String.format(Locale.US,
    244                     " Destination child:%s\tparent:%s",
    245                     LoggerUtils.getTargetStr(ev.destTarget != null ? ev.destTarget[0] : null),
    246                     LoggerUtils.getTargetStr(ev.destTarget != null && ev.destTarget.length > 1 ?
    247                             ev.destTarget[1] : null)));
    248         }
    249         Log.d(TAG, String.format(Locale.US,
    250                 " Elapsed container %d ms session %d ms action %d ms",
    251                 ev.elapsedContainerMillis,
    252                 ev.elapsedSessionMillis,
    253                 ev.actionDurationMillis));
    254     }
    255 }
    256