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