UserEventDispatcher.java revision ffad962092e2b42e336918c2f1e8f5ec48cc08a9
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 17package com.android.launcher3.logging; 18 19import android.content.ComponentName; 20import android.content.Intent; 21import android.os.SystemClock; 22import android.util.Log; 23import android.view.View; 24import android.view.ViewParent; 25 26import com.android.launcher3.DropTarget; 27import com.android.launcher3.ItemInfo; 28import com.android.launcher3.Utilities; 29import com.android.launcher3.config.ProviderConfig; 30import com.android.launcher3.userevent.nano.LauncherLogProto.Action; 31import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent; 32import com.android.launcher3.userevent.nano.LauncherLogProto.Target; 33import com.android.launcher3.util.ComponentKey; 34 35import java.util.List; 36import 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 */ 44public 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 144 // Fill in the duration of time spent navigating in Launcher and the container. 145 event.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis; 146 event.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis; 147 return event; 148 } 149 150 public void logAppLaunch(View v, Intent intent) { 151 LauncherEvent ev = createLauncherEvent(v, intent); 152 if (ev == null) { 153 return; 154 } 155 dispatchUserEvent(ev, intent); 156 } 157 158 public void logActionOnItem(int action, int itemType) { 159 LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.ITEM); 160 event.action.touch = action; 161 event.srcTarget[0].itemType = itemType; 162 event.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis; 163 event.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis; 164 dispatchUserEvent(event, null); 165 } 166 167 public void logActionOnControl(int action, int controlType) { 168 LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTROL); 169 event.action.touch = action; 170 event.srcTarget[0].controlType = controlType; 171 event.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis; 172 event.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis; 173 dispatchUserEvent(event, null); 174 } 175 176 public void logActionOnContainer(int action, int dir, int containerType) { 177 LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTAINER); 178 event.action.touch = action; 179 event.action.dir = dir; 180 event.srcTarget[0].containerType = containerType; 181 event.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis; 182 event.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis; 183 dispatchUserEvent(event, null); 184 } 185 186 public void logDeepShortcutsOpen(View icon) { 187 LauncherEvent event = LoggerUtils.initLauncherEvent( 188 Action.TOUCH, icon, Target.CONTAINER); 189 LaunchSourceProvider provider = getLaunchProviderRecursive(icon); 190 if (icon == null && !(icon.getTag() instanceof ItemInfo)) { 191 return; 192 } 193 ItemInfo info = (ItemInfo) icon.getTag(); 194 provider.fillInLaunchSourceData(icon, info, event.srcTarget[0], event.srcTarget[1]); 195 event.action.touch = Action.LONGPRESS; 196 event.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis; 197 event.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis; 198 dispatchUserEvent(event, null); 199 } 200 201 public void setPredictedApps(List<ComponentKey> predictedApps) { 202 mPredictedApps = predictedApps; 203 } 204 205 public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) { 206 LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, 207 dragObj.dragView, 208 dragObj.originalDragInfo, 209 Target.CONTAINER, 210 dropTargetAsView); 211 event.action.touch = Action.DRAGDROP; 212 213 dragObj.dragSource.fillInLaunchSourceData(null, dragObj.originalDragInfo, 214 event.srcTarget[0], event.srcTarget[1]); 215 216 if (dropTargetAsView instanceof LaunchSourceProvider) { 217 ((LaunchSourceProvider) dropTargetAsView).fillInLaunchSourceData(null, 218 dragObj.dragInfo, event.destTarget[0], event.destTarget[1]); 219 220 } 221 222 event.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis; 223 event.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis; 224 event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis; 225 dispatchUserEvent(event, null); 226 } 227 228 /** 229 * Currently logs following containers: workspace, allapps, widget tray. 230 */ 231 public final void resetElapsedContainerMillis() { 232 mElapsedContainerMillis = SystemClock.uptimeMillis(); 233 } 234 235 public final void resetElapsedSessionMillis() { 236 mElapsedSessionMillis = SystemClock.uptimeMillis(); 237 mElapsedContainerMillis = SystemClock.uptimeMillis(); 238 } 239 240 public final void resetActionDurationMillis() { 241 mActionDurationMillis = SystemClock.uptimeMillis(); 242 } 243 244 public void dispatchUserEvent(LauncherEvent ev, Intent intent) { 245 if (!mIsVerbose) { 246 return; 247 } 248 Log.d(TAG, String.format(Locale.US, 249 "\naction:%s\n Source child:%s\tparent:%s", 250 LoggerUtils.getActionStr(ev.action), 251 LoggerUtils.getTargetStr(ev.srcTarget != null ? ev.srcTarget[0] : null), 252 LoggerUtils.getTargetStr(ev.srcTarget != null && ev.srcTarget.length > 1 ? 253 ev.srcTarget[1] : null))); 254 if (ev.destTarget != null && ev.destTarget.length > 0) { 255 Log.d(TAG, String.format(Locale.US, 256 " Destination child:%s\tparent:%s", 257 LoggerUtils.getTargetStr(ev.destTarget != null ? ev.destTarget[0] : null), 258 LoggerUtils.getTargetStr(ev.destTarget != null && ev.destTarget.length > 1 ? 259 ev.destTarget[1] : null))); 260 } 261 Log.d(TAG, String.format(Locale.US, 262 " Elapsed container %d ms session %d ms action %d ms", 263 ev.elapsedContainerMillis, 264 ev.elapsedSessionMillis, 265 ev.actionDurationMillis)); 266 } 267} 268