1 /* 2 * Copyright (C) 2018 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.quickstep.logging; 18 19 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER; 20 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER; 21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT; 22 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS; 23 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND; 24 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME; 25 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW; 26 27 import android.content.Context; 28 import android.util.Log; 29 30 import androidx.annotation.Nullable; 31 32 import com.android.launcher3.LauncherAppState; 33 import com.android.launcher3.Utilities; 34 import com.android.launcher3.logger.LauncherAtom; 35 import com.android.launcher3.logger.LauncherAtom.ContainerInfo; 36 import com.android.launcher3.logger.LauncherAtom.FolderIcon; 37 import com.android.launcher3.logger.LauncherAtom.FromState; 38 import com.android.launcher3.logger.LauncherAtom.ToState; 39 import com.android.launcher3.logging.InstanceId; 40 import com.android.launcher3.logging.InstanceIdSequence; 41 import com.android.launcher3.logging.StatsLogManager; 42 import com.android.launcher3.model.AllAppsList; 43 import com.android.launcher3.model.BaseModelUpdateTask; 44 import com.android.launcher3.model.BgDataModel; 45 import com.android.launcher3.model.data.FolderInfo; 46 import com.android.launcher3.model.data.ItemInfo; 47 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 48 import com.android.launcher3.model.data.WorkspaceItemInfo; 49 import com.android.launcher3.util.Executors; 50 import com.android.launcher3.util.IntSparseArrayMap; 51 import com.android.launcher3.util.LogConfig; 52 import com.android.systemui.shared.system.SysUiStatsLog; 53 54 import java.util.ArrayList; 55 import java.util.Optional; 56 import java.util.OptionalInt; 57 58 /** 59 * This class calls StatsLog compile time generated methods. 60 * 61 * To see if the logs are properly sent to statsd, execute following command. 62 * $ wwdebug (to turn on the logcat printout) 63 * $ wwlogcat (see logcat with grep filter on) 64 * $ statsd_testdrive (see how ww is writing the proto to statsd buffer) 65 */ 66 public class StatsLogCompatManager extends StatsLogManager { 67 68 private static final String TAG = "StatsLog"; 69 private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG); 70 71 private static Context sContext; 72 73 private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0); 74 // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates 75 // from nano to lite, bake constant to prevent robo test failure. 76 private static final int DEFAULT_PAGE_INDEX = -2; 77 private static final int FOLDER_HIERARCHY_OFFSET = 100; 78 private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200; 79 StatsLogCompatManager(Context context)80 public StatsLogCompatManager(Context context) { 81 sContext = context; 82 } 83 84 @Override logger()85 public StatsLogger logger() { 86 return new StatsCompatLogger(); 87 } 88 89 /** 90 * Logs a ranking event and accompanying {@link InstanceId} and package name. 91 */ 92 @Override log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName, int position)93 public void log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName, 94 int position) { 95 SysUiStatsLog.write(SysUiStatsLog.RANKING_SELECTED, 96 rankingEvent.getId() /* event_id = 1; */, 97 packageName /* package_name = 2; */, 98 instanceId.getId() /* instance_id = 3; */, 99 position /* position_picked = 4; */); 100 } 101 102 /** 103 * Logs the workspace layout information on the model thread. 104 */ 105 @Override logSnapshot()106 public void logSnapshot() { 107 LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask( 108 new SnapshotWorker()); 109 } 110 111 private class SnapshotWorker extends BaseModelUpdateTask { 112 private final InstanceId mInstanceId; SnapshotWorker()113 SnapshotWorker() { 114 mInstanceId = new InstanceIdSequence( 115 1 << 20 /*InstanceId.INSTANCE_ID_MAX*/).newInstanceId(); 116 } 117 118 @Override execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps)119 public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { 120 IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone(); 121 ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone(); 122 ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone(); 123 for (ItemInfo info : workspaceItems) { 124 LauncherAtom.ItemInfo atomInfo = info.buildProto(null); 125 writeSnapshot(atomInfo, mInstanceId); 126 } 127 for (FolderInfo fInfo : folders) { 128 try { 129 ArrayList<WorkspaceItemInfo> folderContents = 130 (ArrayList) Executors.MAIN_EXECUTOR.submit(fInfo.contents::clone).get(); 131 for (ItemInfo info : folderContents) { 132 LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo); 133 writeSnapshot(atomInfo, mInstanceId); 134 } 135 } catch (Exception e) { } 136 } 137 for (ItemInfo info : appWidgets) { 138 LauncherAtom.ItemInfo atomInfo = info.buildProto(null); 139 writeSnapshot(atomInfo, mInstanceId); 140 } 141 } 142 } 143 writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId)144 private static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) { 145 if (IS_VERBOSE) { 146 Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info)); 147 } 148 if (!Utilities.ATLEAST_R) { 149 return; 150 } 151 SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT, 152 LAUNCHER_WORKSPACE_SNAPSHOT.getId() /* event_id */, 153 info.getItemCase().getNumber() /* target_id */, 154 instanceId.getId() /* instance_id */, 155 0 /* uid */, 156 getPackageName(info) /* package_name */, 157 getComponentName(info) /* component_name */, 158 getGridX(info, false) /* grid_x */, 159 getGridY(info, false) /* grid_y */, 160 getPageId(info) /* page_id */, 161 getGridX(info, true) /* grid_x_parent */, 162 getGridY(info, true) /* grid_y_parent */, 163 getParentPageId(info) /* page_id_parent */, 164 getHierarchy(info) /* hierarchy */, 165 info.getIsWork() /* is_work_profile */, 166 info.getAttribute().getNumber() /* origin */, 167 getCardinality(info) /* cardinality */, 168 info.getWidget().getSpanX(), 169 info.getWidget().getSpanY()); 170 } 171 172 /** 173 * Helps to construct and write statsd compatible log message. 174 */ 175 private static class StatsCompatLogger implements StatsLogger { 176 177 private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo(); 178 private ItemInfo mItemInfo = DEFAULT_ITEM_INFO; 179 private InstanceId mInstanceId = DEFAULT_INSTANCE_ID; 180 private OptionalInt mRank = OptionalInt.empty(); 181 private Optional<ContainerInfo> mContainerInfo = Optional.empty(); 182 private int mSrcState = LAUNCHER_STATE_UNSPECIFIED; 183 private int mDstState = LAUNCHER_STATE_UNSPECIFIED; 184 private Optional<FromState> mFromState = Optional.empty(); 185 private Optional<ToState> mToState = Optional.empty(); 186 private Optional<String> mEditText = Optional.empty(); 187 188 @Override withItemInfo(ItemInfo itemInfo)189 public StatsLogger withItemInfo(ItemInfo itemInfo) { 190 if (mContainerInfo.isPresent()) { 191 throw new IllegalArgumentException( 192 "ItemInfo and ContainerInfo are mutual exclusive; cannot log both."); 193 } 194 this.mItemInfo = itemInfo; 195 return this; 196 } 197 198 @Override withInstanceId(InstanceId instanceId)199 public StatsLogger withInstanceId(InstanceId instanceId) { 200 this.mInstanceId = instanceId; 201 return this; 202 } 203 204 @Override withRank(int rank)205 public StatsLogger withRank(int rank) { 206 this.mRank = OptionalInt.of(rank); 207 return this; 208 } 209 210 @Override withSrcState(int srcState)211 public StatsLogger withSrcState(int srcState) { 212 this.mSrcState = srcState; 213 return this; 214 } 215 216 @Override withDstState(int dstState)217 public StatsLogger withDstState(int dstState) { 218 this.mDstState = dstState; 219 return this; 220 } 221 222 @Override withContainerInfo(ContainerInfo containerInfo)223 public StatsLogger withContainerInfo(ContainerInfo containerInfo) { 224 if (mItemInfo != DEFAULT_ITEM_INFO) { 225 throw new IllegalArgumentException( 226 "ItemInfo and ContainerInfo are mutual exclusive; cannot log both."); 227 } 228 this.mContainerInfo = Optional.of(containerInfo); 229 return this; 230 } 231 232 @Override withFromState(FromState fromState)233 public StatsLogger withFromState(FromState fromState) { 234 this.mFromState = Optional.of(fromState); 235 return this; 236 } 237 238 @Override withToState(ToState toState)239 public StatsLogger withToState(ToState toState) { 240 this.mToState = Optional.of(toState); 241 return this; 242 } 243 244 @Override withEditText(String editText)245 public StatsLogger withEditText(String editText) { 246 this.mEditText = Optional.of(editText); 247 return this; 248 } 249 250 @Override log(EventEnum event)251 public void log(EventEnum event) { 252 if (!Utilities.ATLEAST_R) { 253 return; 254 } 255 256 if (mItemInfo.container < 0) { 257 // Item is not within a folder. Write to StatsLog in same thread. 258 write(event, mInstanceId, applyOverwrites(mItemInfo.buildProto()), mSrcState, 259 mDstState); 260 } else { 261 // Item is inside the folder, fetch folder info in a BG thread 262 // and then write to StatsLog. 263 LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask( 264 new BaseModelUpdateTask() { 265 @Override 266 public void execute(LauncherAppState app, BgDataModel dataModel, 267 AllAppsList apps) { 268 FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container); 269 write(event, mInstanceId, 270 applyOverwrites(mItemInfo.buildProto(folderInfo)), 271 mSrcState, mDstState); 272 } 273 }); 274 } 275 } 276 applyOverwrites(LauncherAtom.ItemInfo atomInfo)277 private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) { 278 LauncherAtom.ItemInfo.Builder itemInfoBuilder = 279 (LauncherAtom.ItemInfo.Builder) atomInfo.toBuilder(); 280 281 mRank.ifPresent(itemInfoBuilder::setRank); 282 mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo); 283 284 if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) { 285 FolderIcon.Builder folderIconBuilder = (FolderIcon.Builder) itemInfoBuilder 286 .getFolderIcon() 287 .toBuilder(); 288 mFromState.ifPresent(folderIconBuilder::setFromLabelState); 289 mToState.ifPresent(folderIconBuilder::setToLabelState); 290 mEditText.ifPresent(folderIconBuilder::setLabelInfo); 291 itemInfoBuilder.setFolderIcon(folderIconBuilder); 292 } 293 return itemInfoBuilder.build(); 294 } 295 write(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo, int srcState, int dstState)296 private void write(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo, 297 int srcState, int dstState) { 298 if (IS_VERBOSE) { 299 String name = (event instanceof Enum) ? ((Enum) event).name() : 300 event.getId() + ""; 301 302 Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID 303 ? String.format("\n%s (State:%s->%s)\n%s", name, getStateString(srcState), 304 getStateString(dstState), atomInfo) 305 : String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s", name, 306 getStateString(srcState), getStateString(dstState), instanceId, 307 atomInfo)); 308 } 309 310 SysUiStatsLog.write( 311 SysUiStatsLog.LAUNCHER_EVENT, 312 SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */, 313 srcState, 314 dstState, 315 null /* launcher extensions, deprecated */, 316 false /* quickstep_enabled, deprecated */, 317 event.getId() /* event_id */, 318 atomInfo.getItemCase().getNumber() /* target_id */, 319 instanceId.getId() /* instance_id TODO */, 320 0 /* uid TODO */, 321 getPackageName(atomInfo) /* package_name */, 322 getComponentName(atomInfo) /* component_name */, 323 getGridX(atomInfo, false) /* grid_x */, 324 getGridY(atomInfo, false) /* grid_y */, 325 getPageId(atomInfo) /* page_id */, 326 getGridX(atomInfo, true) /* grid_x_parent */, 327 getGridY(atomInfo, true) /* grid_y_parent */, 328 getParentPageId(atomInfo) /* page_id_parent */, 329 getHierarchy(atomInfo) /* hierarchy */, 330 atomInfo.getIsWork() /* is_work_profile */, 331 atomInfo.getRank() /* rank */, 332 atomInfo.getFolderIcon().getFromLabelState().getNumber() /* fromState */, 333 atomInfo.getFolderIcon().getToLabelState().getNumber() /* toState */, 334 atomInfo.getFolderIcon().getLabelInfo() /* edittext */, 335 getCardinality(atomInfo) /* cardinality */); 336 } 337 } 338 getCardinality(LauncherAtom.ItemInfo info)339 private static int getCardinality(LauncherAtom.ItemInfo info) { 340 switch (info.getContainerInfo().getContainerCase()){ 341 case PREDICTED_HOTSEAT_CONTAINER: 342 return info.getContainerInfo().getPredictedHotseatContainer().getCardinality(); 343 case SEARCH_RESULT_CONTAINER: 344 return info.getContainerInfo().getSearchResultContainer().getQueryLength(); 345 default: 346 return info.getFolderIcon().getCardinality(); 347 } 348 } 349 getPackageName(LauncherAtom.ItemInfo info)350 private static String getPackageName(LauncherAtom.ItemInfo info) { 351 switch (info.getItemCase()) { 352 case APPLICATION: 353 return info.getApplication().getPackageName(); 354 case SHORTCUT: 355 return info.getShortcut().getShortcutName(); 356 case WIDGET: 357 return info.getWidget().getPackageName(); 358 case TASK: 359 return info.getTask().getPackageName(); 360 default: 361 return null; 362 } 363 } 364 getComponentName(LauncherAtom.ItemInfo info)365 private static String getComponentName(LauncherAtom.ItemInfo info) { 366 switch (info.getItemCase()) { 367 case APPLICATION: 368 return info.getApplication().getComponentName(); 369 case SHORTCUT: 370 return info.getShortcut().getShortcutName(); 371 case WIDGET: 372 return info.getWidget().getComponentName(); 373 case TASK: 374 return info.getTask().getComponentName(); 375 default: 376 return null; 377 } 378 } 379 getGridX(LauncherAtom.ItemInfo info, boolean parent)380 private static int getGridX(LauncherAtom.ItemInfo info, boolean parent) { 381 if (info.getContainerInfo().getContainerCase() == FOLDER) { 382 if (parent) { 383 return info.getContainerInfo().getFolder().getWorkspace().getGridX(); 384 } else { 385 return info.getContainerInfo().getFolder().getGridX(); 386 } 387 } else { 388 return info.getContainerInfo().getWorkspace().getGridX(); 389 } 390 } 391 getGridY(LauncherAtom.ItemInfo info, boolean parent)392 private static int getGridY(LauncherAtom.ItemInfo info, boolean parent) { 393 if (info.getContainerInfo().getContainerCase() == FOLDER) { 394 if (parent) { 395 return info.getContainerInfo().getFolder().getWorkspace().getGridY(); 396 } else { 397 return info.getContainerInfo().getFolder().getGridY(); 398 } 399 } else { 400 return info.getContainerInfo().getWorkspace().getGridY(); 401 } 402 } 403 getPageId(LauncherAtom.ItemInfo info)404 private static int getPageId(LauncherAtom.ItemInfo info) { 405 switch (info.getContainerInfo().getContainerCase()) { 406 case FOLDER: 407 return info.getContainerInfo().getFolder().getPageIndex(); 408 default: 409 return info.getContainerInfo().getWorkspace().getPageIndex(); 410 } 411 } 412 getParentPageId(LauncherAtom.ItemInfo info)413 private static int getParentPageId(LauncherAtom.ItemInfo info) { 414 switch (info.getContainerInfo().getContainerCase()) { 415 case FOLDER: 416 return info.getContainerInfo().getFolder().getWorkspace().getPageIndex(); 417 case SEARCH_RESULT_CONTAINER: 418 return info.getContainerInfo().getSearchResultContainer().getWorkspace() 419 .getPageIndex(); 420 default: 421 return info.getContainerInfo().getWorkspace().getPageIndex(); 422 } 423 } 424 getHierarchy(LauncherAtom.ItemInfo info)425 private static int getHierarchy(LauncherAtom.ItemInfo info) { 426 if (info.getContainerInfo().getContainerCase() == FOLDER) { 427 return info.getContainerInfo().getFolder().getParentContainerCase().getNumber() 428 + FOLDER_HIERARCHY_OFFSET; 429 } else if (info.getContainerInfo().getContainerCase() == SEARCH_RESULT_CONTAINER) { 430 return info.getContainerInfo().getSearchResultContainer().getParentContainerCase() 431 .getNumber() + SEARCH_RESULT_HIERARCHY_OFFSET; 432 } else { 433 return info.getContainerInfo().getContainerCase().getNumber(); 434 } 435 } 436 getStateString(int state)437 private static String getStateString(int state) { 438 switch (state) { 439 case LAUNCHER_UICHANGED__DST_STATE__BACKGROUND: 440 return "BACKGROUND"; 441 case LAUNCHER_UICHANGED__DST_STATE__HOME: 442 return "HOME"; 443 case LAUNCHER_UICHANGED__DST_STATE__OVERVIEW: 444 return "OVERVIEW"; 445 case LAUNCHER_UICHANGED__DST_STATE__ALLAPPS: 446 return "ALLAPPS"; 447 default: 448 return "INVALID"; 449 450 } 451 } 452 } 453