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 newInstance(Context context, boolean isInLandscapeMode, boolean isInMultiWindowMode)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 */ fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent)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 */ getLaunchProviderRecursive(View v)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 createLauncherEvent(View v, int intentHashCode, ComponentName cn)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 fillInLogContainerData(LauncherEvent event, View v)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 logAppLaunch(View v, Intent intent)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 logNotificationLaunch(View v, PendingIntent intent)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 logActionCommand(int command, int containerType)178 public void logActionCommand(int command, int containerType) { 179 logActionCommand(command, containerType, 0); 180 } 181 logActionCommand(int command, int containerType, int pageIndex)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 */ logActionCommand(int command, View itemView, int containerType)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 logActionOnControl(int action, int controlType)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 logActionTapOutside(Target target)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 logActionOnContainer(int action, int dir, int containerType)219 public void logActionOnContainer(int action, int dir, int containerType) { 220 logActionOnContainer(action, dir, containerType, 0); 221 } 222 logActionOnContainer(int action, int dir, int containerType, int pageIndex)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 logActionOnItem(int action, int dir, int itemType)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 logDeepShortcutsOpen(View icon)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 setPredictedApps(List<ComponentKey> predictedApps)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. */ logOverviewReorder()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 logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView)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 */ resetElapsedContainerMillis()288 public final void resetElapsedContainerMillis() { 289 mElapsedContainerMillis = SystemClock.uptimeMillis(); 290 } 291 resetElapsedSessionMillis()292 public final void resetElapsedSessionMillis() { 293 mElapsedSessionMillis = SystemClock.uptimeMillis(); 294 mElapsedContainerMillis = SystemClock.uptimeMillis(); 295 } 296 resetActionDurationMillis()297 public final void resetActionDurationMillis() { 298 mActionDurationMillis = SystemClock.uptimeMillis(); 299 } 300 dispatchUserEvent(LauncherEvent ev, Intent intent)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 getTargetsStr(Target[] targets)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