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 static com.android.launcher3.logging.LoggerUtils.newAction; 20 import static com.android.launcher3.logging.LoggerUtils.newCommandAction; 21 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; 22 import static com.android.launcher3.logging.LoggerUtils.newControlTarget; 23 import static com.android.launcher3.logging.LoggerUtils.newDropTarget; 24 import static com.android.launcher3.logging.LoggerUtils.newItemTarget; 25 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; 26 import static com.android.launcher3.logging.LoggerUtils.newTarget; 27 import static com.android.launcher3.logging.LoggerUtils.newTouchAction; 28 29 import android.app.PendingIntent; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.SharedPreferences; 34 import android.os.SystemClock; 35 import android.util.Log; 36 import android.view.View; 37 38 import androidx.annotation.Nullable; 39 40 import com.android.launcher3.DropTarget; 41 import com.android.launcher3.ItemInfo; 42 import com.android.launcher3.R; 43 import com.android.launcher3.Utilities; 44 import com.android.launcher3.config.FeatureFlags; 45 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider; 46 import com.android.launcher3.userevent.nano.LauncherLogProto; 47 import com.android.launcher3.userevent.nano.LauncherLogProto.Action; 48 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent; 49 import com.android.launcher3.userevent.nano.LauncherLogProto.Target; 50 import com.android.launcher3.util.ComponentKey; 51 import com.android.launcher3.util.InstantAppResolver; 52 import com.android.launcher3.util.LogConfig; 53 import com.android.launcher3.util.ResourceBasedOverride; 54 55 import java.util.Locale; 56 import java.util.UUID; 57 58 /** 59 * Manages the creation of {@link LauncherEvent}. 60 * To debug this class, execute following command before side loading a new apk. 61 * 62 * $ adb shell setprop log.tag.UserEvent VERBOSE 63 */ 64 public class UserEventDispatcher implements ResourceBasedOverride { 65 66 private static final String TAG = "UserEvent"; 67 private static final boolean IS_VERBOSE = 68 FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT); 69 private static final String UUID_STORAGE = "uuid"; 70 newInstance(Context context, UserEventDelegate delegate)71 public static UserEventDispatcher newInstance(Context context, 72 UserEventDelegate delegate) { 73 SharedPreferences sharedPrefs = Utilities.getDevicePrefs(context); 74 String uuidStr = sharedPrefs.getString(UUID_STORAGE, null); 75 if (uuidStr == null) { 76 uuidStr = UUID.randomUUID().toString(); 77 sharedPrefs.edit().putString(UUID_STORAGE, uuidStr).apply(); 78 } 79 UserEventDispatcher ued = Overrides.getObject(UserEventDispatcher.class, 80 context.getApplicationContext(), R.string.user_event_dispatcher_class); 81 ued.mDelegate = delegate; 82 ued.mUuidStr = uuidStr; 83 ued.mInstantAppResolver = InstantAppResolver.newInstance(context); 84 return ued; 85 } 86 newInstance(Context context)87 public static UserEventDispatcher newInstance(Context context) { 88 return newInstance(context, null); 89 } 90 91 public interface UserEventDelegate { modifyUserEvent(LauncherEvent event)92 void modifyUserEvent(LauncherEvent event); 93 } 94 95 /** 96 * Fills in the container data on the given event if the given view is not null. 97 * @return whether container data was added. 98 */ fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v)99 public static boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) { 100 // Fill in grid(x,y), pageIndex of the child and container type of the parent 101 LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v); 102 if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) { 103 return false; 104 } 105 ItemInfo itemInfo = (ItemInfo) v.getTag(); 106 provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]); 107 return true; 108 } 109 110 private boolean mSessionStarted; 111 private long mElapsedContainerMillis; 112 private long mElapsedSessionMillis; 113 private long mActionDurationMillis; 114 private String mUuidStr; 115 protected InstantAppResolver mInstantAppResolver; 116 private boolean mAppOrTaskLaunch; 117 private UserEventDelegate mDelegate; 118 119 // APP_ICON SHORTCUT WIDGET 120 // -------------------------------------------------------------- 121 // packageNameHash required optional required 122 // componentNameHash required required 123 // intentHash required 124 // -------------------------------------------------------------- 125 126 @Deprecated logAppLaunch(View v, Intent intent)127 public void logAppLaunch(View v, Intent intent) { 128 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP), 129 newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER)); 130 131 if (fillInLogContainerData(event, v)) { 132 if (mDelegate != null) { 133 mDelegate.modifyUserEvent(event); 134 } 135 fillIntentInfo(event.srcTarget[0], intent); 136 } 137 dispatchUserEvent(event, intent); 138 mAppOrTaskLaunch = true; 139 } 140 logActionTip(int actionType, int viewType)141 public void logActionTip(int actionType, int viewType) { } 142 143 @Deprecated logTaskLaunchOrDismiss(int action, int direction, int taskIndex, ComponentKey componentKey)144 public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex, 145 ComponentKey componentKey) { 146 LauncherEvent event = newLauncherEvent(newTouchAction(action), // TAP or SWIPE or FLING 147 newTarget(Target.Type.ITEM)); 148 if (action == Action.Touch.SWIPE || action == Action.Touch.FLING) { 149 // Direction DOWN means the task was launched, UP means it was dismissed. 150 event.action.dir = direction; 151 } 152 event.srcTarget[0].itemType = LauncherLogProto.ItemType.TASK; 153 event.srcTarget[0].pageIndex = taskIndex; 154 fillComponentInfo(event.srcTarget[0], componentKey.componentName); 155 dispatchUserEvent(event, null); 156 mAppOrTaskLaunch = true; 157 } 158 fillIntentInfo(Target target, Intent intent)159 protected void fillIntentInfo(Target target, Intent intent) { 160 target.intentHash = intent.hashCode(); 161 fillComponentInfo(target, intent.getComponent()); 162 } 163 fillComponentInfo(Target target, ComponentName cn)164 private void fillComponentInfo(Target target, ComponentName cn) { 165 if (cn != null) { 166 target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode(); 167 target.componentHash = (mUuidStr + cn.flattenToString()).hashCode(); 168 } 169 } 170 logNotificationLaunch(View v, PendingIntent intent)171 public void logNotificationLaunch(View v, PendingIntent intent) { 172 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP), 173 newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER)); 174 if (fillInLogContainerData(event, v)) { 175 event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode(); 176 } 177 dispatchUserEvent(event, null); 178 } 179 logActionCommand(int command, Target srcTarget)180 public void logActionCommand(int command, Target srcTarget) { 181 logActionCommand(command, srcTarget, null); 182 } 183 logActionCommand(int command, int srcContainerType, int dstContainerType)184 public void logActionCommand(int command, int srcContainerType, int dstContainerType) { 185 logActionCommand(command, newContainerTarget(srcContainerType), 186 dstContainerType >=0 ? newContainerTarget(dstContainerType) : null); 187 } 188 logActionCommand(int command, Target srcTarget, Target dstTarget)189 public void logActionCommand(int command, Target srcTarget, Target dstTarget) { 190 LauncherEvent event = newLauncherEvent(newCommandAction(command), srcTarget); 191 if (command == Action.Command.STOP) { 192 if (mAppOrTaskLaunch || !mSessionStarted) { 193 mSessionStarted = false; 194 return; 195 } 196 } 197 198 if (dstTarget != null) { 199 event.destTarget = new Target[1]; 200 event.destTarget[0] = dstTarget; 201 event.action.isStateChange = true; 202 } 203 dispatchUserEvent(event, null); 204 } 205 206 /** 207 * TODO: Make this function work when a container view is passed as the 2nd param. 208 */ logActionCommand(int command, View itemView, int srcContainerType)209 public void logActionCommand(int command, View itemView, int srcContainerType) { 210 LauncherEvent event = newLauncherEvent(newCommandAction(command), 211 newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER)); 212 213 if (fillInLogContainerData(event, itemView)) { 214 // TODO: Remove the following two lines once fillInLogContainerData can take in a 215 // container view. 216 event.srcTarget[0].type = Target.Type.CONTAINER; 217 event.srcTarget[0].containerType = srcContainerType; 218 } 219 dispatchUserEvent(event, null); 220 } 221 logActionOnControl(int action, int controlType)222 public void logActionOnControl(int action, int controlType) { 223 logActionOnControl(action, controlType, null, -1); 224 } 225 logActionOnControl(int action, int controlType, int parentContainerType)226 public void logActionOnControl(int action, int controlType, int parentContainerType) { 227 logActionOnControl(action, controlType, null, parentContainerType); 228 } 229 logActionOnControl(int action, int controlType, @Nullable View controlInContainer)230 public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer) { 231 logActionOnControl(action, controlType, controlInContainer, -1); 232 } 233 logActionOnControl(int action, int controlType, int parentContainer, int grandParentContainer)234 public void logActionOnControl(int action, int controlType, int parentContainer, 235 int grandParentContainer){ 236 LauncherEvent event = newLauncherEvent(newTouchAction(action), 237 newControlTarget(controlType), 238 newContainerTarget(parentContainer), 239 newContainerTarget(grandParentContainer)); 240 dispatchUserEvent(event, null); 241 } 242 logActionOnControl(int action, int controlType, @Nullable View controlInContainer, int parentContainerType)243 public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer, 244 int parentContainerType) { 245 final LauncherEvent event = (controlInContainer == null && parentContainerType < 0) 246 ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL)) 247 : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL), 248 newTarget(Target.Type.CONTAINER)); 249 event.srcTarget[0].controlType = controlType; 250 if (controlInContainer != null) { 251 fillInLogContainerData(event, controlInContainer); 252 } 253 if (parentContainerType >= 0) { 254 event.srcTarget[1].containerType = parentContainerType; 255 } 256 if (action == Action.Touch.DRAGDROP) { 257 event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis; 258 } 259 dispatchUserEvent(event, null); 260 } 261 logActionTapOutside(Target target)262 public void logActionTapOutside(Target target) { 263 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Type.TOUCH), 264 target); 265 event.action.isOutside = true; 266 dispatchUserEvent(event, null); 267 } 268 logActionBounceTip(int containerType)269 public void logActionBounceTip(int containerType) { 270 LauncherEvent event = newLauncherEvent(newAction(Action.Type.TIP), 271 newContainerTarget(containerType)); 272 event.srcTarget[0].tipType = LauncherLogProto.TipType.BOUNCE; 273 dispatchUserEvent(event, null); 274 } 275 logActionOnContainer(int action, int dir, int containerType)276 public void logActionOnContainer(int action, int dir, int containerType) { 277 logActionOnContainer(action, dir, containerType, 0); 278 } 279 logActionOnContainer(int action, int dir, int containerType, int pageIndex)280 public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) { 281 LauncherEvent event = newLauncherEvent(newTouchAction(action), 282 newContainerTarget(containerType)); 283 event.action.dir = dir; 284 event.srcTarget[0].pageIndex = pageIndex; 285 dispatchUserEvent(event, null); 286 } 287 288 /** 289 * Used primarily for swipe up and down when state changes when swipe up happens from the 290 * navbar bezel, the {@param srcChildContainerType} is NAVBAR and 291 * {@param srcParentContainerType} is either one of the two 292 * (1) WORKSPACE: if the launcher is the foreground activity 293 * (2) APP: if another app was the foreground activity 294 */ logStateChangeAction(int action, int dir, int downX, int downY, int srcChildTargetType, int srcParentContainerType, int dstContainerType, int pageIndex)295 public void logStateChangeAction(int action, int dir, int downX, int downY, int srcChildTargetType, 296 int srcParentContainerType, int dstContainerType, 297 int pageIndex) { 298 LauncherEvent event; 299 if (srcChildTargetType == LauncherLogProto.ItemType.TASK) { 300 event = newLauncherEvent(newTouchAction(action), 301 newItemTarget(srcChildTargetType), 302 newContainerTarget(srcParentContainerType)); 303 } else { 304 event = newLauncherEvent(newTouchAction(action), 305 newContainerTarget(srcChildTargetType), 306 newContainerTarget(srcParentContainerType)); 307 } 308 event.destTarget = new Target[1]; 309 event.destTarget[0] = newContainerTarget(dstContainerType); 310 event.action.dir = dir; 311 event.action.isStateChange = true; 312 event.srcTarget[0].pageIndex = pageIndex; 313 event.srcTarget[0].spanX = downX; 314 event.srcTarget[0].spanY = downY; 315 dispatchUserEvent(event, null); 316 resetElapsedContainerMillis("state changed"); 317 } 318 logActionOnItem(int action, int dir, int itemType)319 public void logActionOnItem(int action, int dir, int itemType) { 320 Target itemTarget = newTarget(Target.Type.ITEM); 321 itemTarget.itemType = itemType; 322 LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget); 323 event.action.dir = dir; 324 dispatchUserEvent(event, null); 325 } 326 logDeepShortcutsOpen(View icon)327 public void logDeepShortcutsOpen(View icon) { 328 LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(icon); 329 if (icon == null || !(icon.getTag() instanceof ItemInfo || provider == null)) { 330 return; 331 } 332 ItemInfo info = (ItemInfo) icon.getTag(); 333 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS), 334 newItemTarget(info, mInstantAppResolver), newTarget(Target.Type.CONTAINER)); 335 provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]); 336 dispatchUserEvent(event, null); 337 338 resetElapsedContainerMillis("deep shortcut open"); 339 } 340 logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView)341 public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) { 342 LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP), 343 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver), 344 newTarget(Target.Type.CONTAINER)); 345 event.destTarget = new Target[] { 346 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver), 347 newDropTarget(dropTargetAsView) 348 }; 349 350 dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo, 351 event.srcTarget[0], event.srcTarget[1]); 352 353 if (dropTargetAsView instanceof LogContainerProvider) { 354 ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null, 355 dragObj.dragInfo, event.destTarget[0], event.destTarget[1]); 356 357 } 358 event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis; 359 dispatchUserEvent(event, null); 360 } 361 logActionBack(boolean completed, int downX, int downY, boolean isButton, boolean gestureSwipeLeft, int containerType)362 public void logActionBack(boolean completed, int downX, int downY, boolean isButton, 363 boolean gestureSwipeLeft, int containerType) { 364 int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE; 365 Action action = newCommandAction(actionTouch); 366 action.command = Action.Command.BACK; 367 action.dir = isButton 368 ? Action.Direction.NONE 369 : gestureSwipeLeft 370 ? Action.Direction.LEFT 371 : Action.Direction.RIGHT; 372 Target target = newControlTarget(isButton 373 ? LauncherLogProto.ControlType.BACK_BUTTON 374 : LauncherLogProto.ControlType.BACK_GESTURE); 375 target.spanX = downX; 376 target.spanY = downY; 377 target.cardinality = completed ? 1 : 0; 378 LauncherEvent event = newLauncherEvent(action, target, newContainerTarget(containerType)); 379 380 dispatchUserEvent(event, null); 381 } 382 383 /** 384 * Currently logs following containers: workspace, allapps, widget tray. 385 * @param reason 386 */ resetElapsedContainerMillis(String reason)387 public final void resetElapsedContainerMillis(String reason) { 388 mElapsedContainerMillis = SystemClock.uptimeMillis(); 389 if (!IS_VERBOSE) { 390 return; 391 } 392 Log.d(TAG, "resetElapsedContainerMillis reason=" + reason); 393 394 } 395 startSession()396 public final void startSession() { 397 mSessionStarted = true; 398 mElapsedSessionMillis = SystemClock.uptimeMillis(); 399 mElapsedContainerMillis = SystemClock.uptimeMillis(); 400 } 401 resetActionDurationMillis()402 public final void resetActionDurationMillis() { 403 mActionDurationMillis = SystemClock.uptimeMillis(); 404 } 405 dispatchUserEvent(LauncherEvent ev, Intent intent)406 public void dispatchUserEvent(LauncherEvent ev, Intent intent) { 407 mAppOrTaskLaunch = false; 408 ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis; 409 ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis; 410 411 if (!IS_VERBOSE) { 412 return; 413 } 414 String log = "\n-----------------------------------------------------" 415 + "\naction:" + LoggerUtils.getActionStr(ev.action); 416 if (ev.srcTarget != null && ev.srcTarget.length > 0) { 417 log += "\n Source " + getTargetsStr(ev.srcTarget); 418 } 419 if (ev.destTarget != null && ev.destTarget.length > 0) { 420 log += "\n Destination " + getTargetsStr(ev.destTarget); 421 } 422 log += String.format(Locale.US, 423 "\n Elapsed container %d ms, session %d ms, action %d ms", 424 ev.elapsedContainerMillis, 425 ev.elapsedSessionMillis, 426 ev.actionDurationMillis); 427 log += "\n\n"; 428 Log.d(TAG, log); 429 } 430 getTargetsStr(Target[] targets)431 private static String getTargetsStr(Target[] targets) { 432 String result = "child:" + LoggerUtils.getTargetStr(targets[0]); 433 for (int i = 1; i < targets.length; i++) { 434 result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]); 435 } 436 return result; 437 } 438 } 439