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