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 androidx.core.util.Preconditions.checkNotNull; 20 import static androidx.core.util.Preconditions.checkState; 21 22 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE; 23 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.ALL_APPS_CONTAINER; 24 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS; 25 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER; 26 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER; 27 import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER; 28 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT; 29 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS; 30 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND; 31 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME; 32 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW; 33 34 import android.content.Context; 35 import android.util.Log; 36 import android.util.StatsEvent; 37 import android.view.View; 38 39 import androidx.annotation.NonNull; 40 import androidx.annotation.Nullable; 41 import androidx.annotation.WorkerThread; 42 import androidx.slice.SliceItem; 43 44 import com.android.launcher3.LauncherAppState; 45 import com.android.launcher3.Utilities; 46 import com.android.launcher3.logger.LauncherAtom; 47 import com.android.launcher3.logger.LauncherAtom.Attribute; 48 import com.android.launcher3.logger.LauncherAtom.ContainerInfo; 49 import com.android.launcher3.logger.LauncherAtom.FolderContainer.ParentContainerCase; 50 import com.android.launcher3.logger.LauncherAtom.FolderIcon; 51 import com.android.launcher3.logger.LauncherAtom.FromState; 52 import com.android.launcher3.logger.LauncherAtom.LauncherAttributes; 53 import com.android.launcher3.logger.LauncherAtom.ToState; 54 import com.android.launcher3.logger.LauncherAtomExtensions.DeviceSearchResultContainer; 55 import com.android.launcher3.logger.LauncherAtomExtensions.DeviceSearchResultContainer.SearchAttributes; 56 import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers; 57 import com.android.launcher3.logging.InstanceId; 58 import com.android.launcher3.logging.StatsLogManager; 59 import com.android.launcher3.model.AllAppsList; 60 import com.android.launcher3.model.BaseModelUpdateTask; 61 import com.android.launcher3.model.BgDataModel; 62 import com.android.launcher3.model.data.FolderInfo; 63 import com.android.launcher3.model.data.ItemInfo; 64 import com.android.launcher3.util.Executors; 65 import com.android.launcher3.util.IntArray; 66 import com.android.launcher3.util.LogConfig; 67 import com.android.launcher3.views.ActivityContext; 68 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 69 import com.android.systemui.shared.system.SysUiStatsLog; 70 71 import java.util.ArrayList; 72 import java.util.List; 73 import java.util.Optional; 74 import java.util.OptionalInt; 75 import java.util.concurrent.CopyOnWriteArrayList; 76 77 /** 78 * This class calls StatsLog compile time generated methods. 79 * 80 * To see if the logs are properly sent to statsd, execute following command. 81 * <ul> 82 * $ wwdebug (to turn on the logcat printout) 83 * $ wwlogcat (see logcat with grep filter on) 84 * $ statsd_testdrive (see how ww is writing the proto to statsd buffer) 85 * </ul> 86 */ 87 public class StatsLogCompatManager extends StatsLogManager { 88 89 private static final String TAG = "StatsLog"; 90 private static final String LATENCY_TAG = "StatsLatencyLog"; 91 private static final String IMPRESSION_TAG = "StatsImpressionLog"; 92 private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG); 93 private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0); 94 // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates 95 // from nano to lite, bake constant to prevent robo test failure. 96 private static final int DEFAULT_PAGE_INDEX = -2; 97 private static final int FOLDER_HIERARCHY_OFFSET = 100; 98 private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200; 99 private static final int EXTENDED_CONTAINERS_HIERARCHY_OFFSET = 300; 100 private static final int ALL_APPS_HIERARCHY_OFFSET = 400; 101 102 /** 103 * Flags for converting SearchAttribute to integer value. 104 */ 105 private static final int SEARCH_ATTRIBUTES_CORRECTED_QUERY = 1 << 0; 106 private static final int SEARCH_ATTRIBUTES_DIRECT_MATCH = 1 << 1; 107 private static final int SEARCH_ATTRIBUTES_ENTRY_STATE_ALL_APPS = 1 << 2; 108 private static final int SEARCH_ATTRIBUTES_ENTRY_STATE_QSB = 1 << 3; 109 110 public static final CopyOnWriteArrayList<StatsLogConsumer> LOGS_CONSUMER = 111 new CopyOnWriteArrayList<>(); 112 113 private final Context mContext; 114 StatsLogCompatManager(Context context)115 public StatsLogCompatManager(Context context) { 116 mContext = context; 117 } 118 119 @Override createLogger()120 protected StatsLogger createLogger() { 121 return new StatsCompatLogger(mContext, mActivityContext); 122 } 123 124 @Override createLatencyLogger()125 protected StatsLatencyLogger createLatencyLogger() { 126 return new StatsCompatLatencyLogger(); 127 } 128 129 @Override createImpressionLogger()130 protected StatsImpressionLogger createImpressionLogger() { 131 return new StatsCompatImpressionLogger(); 132 } 133 134 /** 135 * Synchronously writes an itemInfo to stats log 136 */ 137 @WorkerThread writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId)138 public static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) { 139 if (IS_VERBOSE) { 140 Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info)); 141 } 142 if (!Utilities.ATLEAST_R || Utilities.isRunningInTestHarness()) { 143 return; 144 } 145 SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT, 146 LAUNCHER_WORKSPACE_SNAPSHOT.getId() /* event_id */, 147 info.getItemCase().getNumber() /* target_id */, 148 instanceId.getId() /* instance_id */, 149 0 /* uid */, 150 getPackageName(info) /* package_name */, 151 getComponentName(info) /* component_name */, 152 getGridX(info, false) /* grid_x */, 153 getGridY(info, false) /* grid_y */, 154 getPageId(info) /* page_id */, 155 getGridX(info, true) /* grid_x_parent */, 156 getGridY(info, true) /* grid_y_parent */, 157 getParentPageId(info) /* page_id_parent */, 158 getHierarchy(info) /* hierarchy */, 159 info.getIsWork() /* is_work_profile */, 160 0 /* origin */, 161 getCardinality(info) /* cardinality */, 162 info.getWidget().getSpanX(), 163 info.getWidget().getSpanY(), 164 getFeatures(info), 165 getAttributes(info) /* attributes */ 166 ); 167 } 168 getAttributes(LauncherAtom.ItemInfo itemInfo)169 private static byte[] getAttributes(LauncherAtom.ItemInfo itemInfo) { 170 LauncherAttributes.Builder responseBuilder = LauncherAttributes.newBuilder(); 171 itemInfo.getItemAttributesList().stream().map(Attribute::getNumber).forEach( 172 responseBuilder::addItemAttributes); 173 return responseBuilder.build().toByteArray(); 174 } 175 176 /** 177 * Builds {@link StatsEvent} from {@link LauncherAtom.ItemInfo}. Used for pulled atom callback 178 * implementation. 179 */ buildStatsEvent(LauncherAtom.ItemInfo info, @Nullable InstanceId instanceId)180 public static StatsEvent buildStatsEvent(LauncherAtom.ItemInfo info, 181 @Nullable InstanceId instanceId) { 182 return SysUiStatsLog.buildStatsEvent( 183 SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT, // atom ID, 184 LAUNCHER_WORKSPACE_SNAPSHOT.getId(), // event_id = 1; 185 info.getItemCase().getNumber(), // item_id = 2; 186 instanceId == null ? 0 : instanceId.getId(), //instance_id = 3; 187 0, //uid = 4 [(is_uid) = true]; 188 getPackageName(info), // package_name = 5; 189 getComponentName(info), // component_name = 6; 190 getGridX(info, false), //grid_x = 7 [default = -1]; 191 getGridY(info, false), //grid_y = 8 [default = -1]; 192 getPageId(info), // page_id = 9 [default = -2]; 193 getGridX(info, true), //grid_x_parent = 10 [default = -1]; 194 getGridY(info, true), //grid_y_parent = 11 [default = -1]; 195 getParentPageId(info), //page_id_parent = 12 [default = -2]; 196 getHierarchy(info), // container_id = 13; 197 info.getIsWork(), // is_work_profile = 14; 198 0, // attribute_id = 15; 199 getCardinality(info), // cardinality = 16; 200 info.getWidget().getSpanX(), // span_x = 17 [default = 1]; 201 info.getWidget().getSpanY(), // span_y = 18 [default = 1]; 202 getAttributes(info) /* attributes = 19 [(log_mode) = MODE_BYTES] */, 203 info.getIsKidsMode() /* is_kids_mode = 20 */ 204 ); 205 } 206 207 /** 208 * Helps to construct and write statsd compatible log message. 209 */ 210 private static class StatsCompatLogger implements StatsLogger { 211 212 private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo(); 213 static { 214 DEFAULT_ITEM_INFO.itemType = ITEM_TYPE_NON_ACTIONABLE; 215 } 216 private final Context mContext; 217 private final Optional<ActivityContext> mActivityContext; 218 private ItemInfo mItemInfo = DEFAULT_ITEM_INFO; 219 private InstanceId mInstanceId = DEFAULT_INSTANCE_ID; 220 private OptionalInt mRank = OptionalInt.empty(); 221 private Optional<ContainerInfo> mContainerInfo = Optional.empty(); 222 private int mSrcState = LAUNCHER_STATE_UNSPECIFIED; 223 private int mDstState = LAUNCHER_STATE_UNSPECIFIED; 224 private Optional<FromState> mFromState = Optional.empty(); 225 private Optional<ToState> mToState = Optional.empty(); 226 private Optional<String> mEditText = Optional.empty(); 227 private SliceItem mSliceItem; 228 private LauncherAtom.Slice mSlice; 229 private Optional<Integer> mCardinality = Optional.empty(); 230 StatsCompatLogger(Context context, ActivityContext activityContext)231 StatsCompatLogger(Context context, ActivityContext activityContext) { 232 mContext = context; 233 mActivityContext = Optional.ofNullable(activityContext); 234 } 235 236 @Override withItemInfo(ItemInfo itemInfo)237 public StatsLogger withItemInfo(ItemInfo itemInfo) { 238 if (mContainerInfo.isPresent()) { 239 throw new IllegalArgumentException( 240 "ItemInfo and ContainerInfo are mutual exclusive; cannot log both."); 241 } 242 this.mItemInfo = itemInfo; 243 return this; 244 } 245 246 @Override withInstanceId(InstanceId instanceId)247 public StatsLogger withInstanceId(InstanceId instanceId) { 248 this.mInstanceId = instanceId; 249 return this; 250 } 251 252 @Override withRank(int rank)253 public StatsLogger withRank(int rank) { 254 this.mRank = OptionalInt.of(rank); 255 return this; 256 } 257 258 @Override withSrcState(int srcState)259 public StatsLogger withSrcState(int srcState) { 260 this.mSrcState = srcState; 261 return this; 262 } 263 264 @Override withDstState(int dstState)265 public StatsLogger withDstState(int dstState) { 266 this.mDstState = dstState; 267 return this; 268 } 269 270 @Override withContainerInfo(ContainerInfo containerInfo)271 public StatsLogger withContainerInfo(ContainerInfo containerInfo) { 272 checkState(mItemInfo == DEFAULT_ITEM_INFO, 273 "ItemInfo and ContainerInfo are mutual exclusive; cannot log both."); 274 this.mContainerInfo = Optional.of(containerInfo); 275 return this; 276 } 277 278 @Override withFromState(FromState fromState)279 public StatsLogger withFromState(FromState fromState) { 280 this.mFromState = Optional.of(fromState); 281 return this; 282 } 283 284 @Override withToState(ToState toState)285 public StatsLogger withToState(ToState toState) { 286 this.mToState = Optional.of(toState); 287 return this; 288 } 289 290 @Override withEditText(String editText)291 public StatsLogger withEditText(String editText) { 292 this.mEditText = Optional.of(editText); 293 return this; 294 } 295 296 @Override withSliceItem(@onNull SliceItem sliceItem)297 public StatsLogger withSliceItem(@NonNull SliceItem sliceItem) { 298 checkState(mItemInfo == DEFAULT_ITEM_INFO && mSlice == null, 299 "ItemInfo, Slice and SliceItem are mutual exclusive; cannot set more than one" 300 + " of them."); 301 this.mSliceItem = checkNotNull(sliceItem, "expected valid sliceItem but received null"); 302 return this; 303 } 304 305 @Override withSlice(LauncherAtom.Slice slice)306 public StatsLogger withSlice(LauncherAtom.Slice slice) { 307 checkState(mItemInfo == DEFAULT_ITEM_INFO && mSliceItem == null, 308 "ItemInfo, Slice and SliceItem are mutual exclusive; cannot set more than one" 309 + " of them."); 310 checkNotNull(slice, "expected valid slice but received null"); 311 checkNotNull(slice.getUri(), "expected valid slice uri but received null"); 312 this.mSlice = slice; 313 return this; 314 } 315 316 @Override withCardinality(int cardinality)317 public StatsLogger withCardinality(int cardinality) { 318 this.mCardinality = Optional.of(cardinality); 319 return this; 320 } 321 322 @Override log(EventEnum event)323 public void log(EventEnum event) { 324 if (!Utilities.ATLEAST_R) { 325 return; 326 } 327 LauncherAppState appState = LauncherAppState.getInstanceNoCreate(); 328 329 if (mSlice == null && mSliceItem != null) { 330 mSlice = LauncherAtom.Slice.newBuilder().setUri( 331 mSliceItem.getSlice().getUri().toString()).build(); 332 } 333 334 if (mSlice != null) { 335 Executors.MODEL_EXECUTOR.execute( 336 () -> { 337 LauncherAtom.ItemInfo.Builder itemInfoBuilder = 338 LauncherAtom.ItemInfo.newBuilder().setSlice(mSlice); 339 mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo); 340 write(event, applyOverwrites(itemInfoBuilder.build())); 341 }); 342 return; 343 } 344 345 if (mItemInfo == null) { 346 return; 347 } 348 349 if (mItemInfo.container < 0 || appState == null) { 350 // Write log on the model thread so that logs do not go out of order 351 // (for eg: drop comes after drag) 352 Executors.MODEL_EXECUTOR.execute( 353 () -> write(event, applyOverwrites(mItemInfo.buildProto()))); 354 } else { 355 // Item is inside the folder, fetch folder info in a BG thread 356 // and then write to StatsLog. 357 appState.getModel().enqueueModelUpdateTask( 358 new BaseModelUpdateTask() { 359 @Override 360 public void execute(@NonNull final LauncherAppState app, 361 @NonNull final BgDataModel dataModel, 362 @NonNull final AllAppsList apps) { 363 FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container); 364 write(event, applyOverwrites(mItemInfo.buildProto(folderInfo))); 365 } 366 }); 367 } 368 } 369 370 @Override sendToInteractionJankMonitor(EventEnum event, View view)371 public void sendToInteractionJankMonitor(EventEnum event, View view) { 372 if (!(event instanceof LauncherEvent)) { 373 return; 374 } 375 switch ((LauncherEvent) event) { 376 case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN: 377 InteractionJankMonitorWrapper.begin( 378 view, 379 InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL); 380 break; 381 case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END: 382 InteractionJankMonitorWrapper.end( 383 InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL); 384 break; 385 default: 386 break; 387 } 388 } 389 applyOverwrites(LauncherAtom.ItemInfo atomInfo)390 private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) { 391 LauncherAtom.ItemInfo.Builder itemInfoBuilder = atomInfo.toBuilder(); 392 393 mRank.ifPresent(itemInfoBuilder::setRank); 394 mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo); 395 396 mActivityContext.ifPresent(activityContext -> 397 activityContext.applyOverwritesToLogItem(itemInfoBuilder)); 398 399 if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) { 400 FolderIcon.Builder folderIconBuilder = itemInfoBuilder 401 .getFolderIcon() 402 .toBuilder(); 403 mFromState.ifPresent(folderIconBuilder::setFromLabelState); 404 mToState.ifPresent(folderIconBuilder::setToLabelState); 405 mEditText.ifPresent(folderIconBuilder::setLabelInfo); 406 itemInfoBuilder.setFolderIcon(folderIconBuilder); 407 } 408 return itemInfoBuilder.build(); 409 } 410 411 @WorkerThread write(EventEnum event, LauncherAtom.ItemInfo atomInfo)412 private void write(EventEnum event, LauncherAtom.ItemInfo atomInfo) { 413 InstanceId instanceId = mInstanceId; 414 int srcState = mSrcState; 415 int dstState = mDstState; 416 if (IS_VERBOSE) { 417 String name = (event instanceof Enum) ? ((Enum) event).name() : 418 event.getId() + ""; 419 StringBuilder logStringBuilder = new StringBuilder("\n"); 420 if (instanceId != DEFAULT_INSTANCE_ID) { 421 logStringBuilder.append(String.format("InstanceId:%s ", instanceId)); 422 } 423 logStringBuilder.append(name); 424 if (srcState != LAUNCHER_STATE_UNSPECIFIED 425 || dstState != LAUNCHER_STATE_UNSPECIFIED) { 426 logStringBuilder.append( 427 String.format("(State:%s->%s)", getStateString(srcState), 428 getStateString(dstState))); 429 } 430 if (atomInfo.hasContainerInfo()) { 431 logStringBuilder.append("\n").append(atomInfo); 432 } 433 Log.d(TAG, logStringBuilder.toString()); 434 } 435 436 for (StatsLogConsumer consumer : LOGS_CONSUMER) { 437 consumer.consume(event, atomInfo); 438 } 439 440 // TODO: remove this when b/231648228 is fixed. 441 if (Utilities.isRunningInTestHarness()) { 442 return; 443 } 444 int cardinality = mCardinality.orElseGet(() -> getCardinality(atomInfo)); 445 SysUiStatsLog.write( 446 SysUiStatsLog.LAUNCHER_EVENT, 447 SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */, 448 srcState, 449 dstState, 450 null /* launcher extensions, deprecated */, 451 false /* quickstep_enabled, deprecated */, 452 event.getId() /* event_id */, 453 atomInfo.getItemCase().getNumber() /* target_id */, 454 instanceId.getId() /* instance_id TODO */, 455 0 /* uid TODO */, 456 getPackageName(atomInfo) /* package_name */, 457 getComponentName(atomInfo) /* component_name */, 458 getGridX(atomInfo, false) /* grid_x */, 459 getGridY(atomInfo, false) /* grid_y */, 460 getPageId(atomInfo) /* page_id */, 461 getGridX(atomInfo, true) /* grid_x_parent */, 462 getGridY(atomInfo, true) /* grid_y_parent */, 463 getParentPageId(atomInfo) /* page_id_parent */, 464 getHierarchy(atomInfo) /* hierarchy */, 465 atomInfo.getIsWork() /* is_work_profile */, 466 atomInfo.getRank() /* rank */, 467 atomInfo.getFolderIcon().getFromLabelState().getNumber() /* fromState */, 468 atomInfo.getFolderIcon().getToLabelState().getNumber() /* toState */, 469 atomInfo.getFolderIcon().getLabelInfo() /* edittext */, 470 cardinality /* cardinality */, 471 getFeatures(atomInfo) /* features */, 472 getSearchAttributes(atomInfo) /* searchAttributes */, 473 getAttributes(atomInfo) /* attributes */ 474 ); 475 } 476 } 477 478 /** 479 * Helps to construct and log statsd compatible latency events. 480 */ 481 private static class StatsCompatLatencyLogger implements StatsLatencyLogger { 482 private InstanceId mInstanceId = DEFAULT_INSTANCE_ID; 483 private LatencyType mType = LatencyType.UNKNOWN; 484 private int mPackageId = 0; 485 private long mLatencyInMillis; 486 private int mQueryLength = -1; 487 private int mSubEventType = 0; 488 489 @Override withInstanceId(InstanceId instanceId)490 public StatsLatencyLogger withInstanceId(InstanceId instanceId) { 491 this.mInstanceId = instanceId; 492 return this; 493 } 494 495 @Override withType(LatencyType type)496 public StatsLatencyLogger withType(LatencyType type) { 497 this.mType = type; 498 return this; 499 } 500 501 @Override withPackageId(int packageId)502 public StatsLatencyLogger withPackageId(int packageId) { 503 this.mPackageId = packageId; 504 return this; 505 } 506 507 @Override withLatency(long latencyInMillis)508 public StatsLatencyLogger withLatency(long latencyInMillis) { 509 this.mLatencyInMillis = latencyInMillis; 510 return this; 511 } 512 513 @Override withQueryLength(int queryLength)514 public StatsLatencyLogger withQueryLength(int queryLength) { 515 this.mQueryLength = queryLength; 516 return this; 517 } 518 519 @Override withSubEventType(int type)520 public StatsLatencyLogger withSubEventType(int type) { 521 this.mSubEventType = type; 522 return this; 523 } 524 525 @Override log(EventEnum event)526 public void log(EventEnum event) { 527 if (IS_VERBOSE) { 528 String name = (event instanceof Enum) ? ((Enum) event).name() : 529 event.getId() + ""; 530 StringBuilder logStringBuilder = new StringBuilder("\n"); 531 logStringBuilder.append(String.format("InstanceId:%s ", mInstanceId)); 532 logStringBuilder.append(String.format("%s=%sms", name, mLatencyInMillis)); 533 Log.d(LATENCY_TAG, logStringBuilder.toString()); 534 } 535 536 SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_LATENCY, 537 event.getId(), // event_id 538 mInstanceId.getId(), // instance_id 539 mPackageId, // package_id 540 mLatencyInMillis, // latency_in_millis 541 mType.getId(), //type 542 mQueryLength, // query_length 543 mSubEventType // sub_event_type 544 ); 545 } 546 } 547 548 /** 549 * Helps to construct and log statsd compatible impression events. 550 */ 551 private static class StatsCompatImpressionLogger implements StatsImpressionLogger { 552 private final IntArray mResultTypeList = new IntArray(); 553 private final IntArray mResultCountList = new IntArray(); 554 private final List<Boolean> mAboveKeyboardList = new ArrayList<>(); 555 private InstanceId mInstanceId = DEFAULT_INSTANCE_ID; 556 private State mLauncherState = State.UNKNOWN; 557 private int mQueryLength = -1; 558 559 @Override withInstanceId(InstanceId instanceId)560 public StatsImpressionLogger withInstanceId(InstanceId instanceId) { 561 this.mInstanceId = instanceId; 562 return this; 563 } 564 565 @Override withState(State state)566 public StatsImpressionLogger withState(State state) { 567 this.mLauncherState = state; 568 return this; 569 } 570 571 @Override withQueryLength(int queryLength)572 public StatsImpressionLogger withQueryLength(int queryLength) { 573 this.mQueryLength = queryLength; 574 return this; 575 } 576 577 @Override withResultType(IntArray resultType)578 public StatsImpressionLogger withResultType(IntArray resultType) { 579 this.mResultTypeList.clear(); 580 this.mResultTypeList.addAll(resultType); 581 return this; 582 } 583 584 @Override withResultCount(IntArray resultCount)585 public StatsImpressionLogger withResultCount(IntArray resultCount) { 586 this.mResultCountList.clear(); 587 this.mResultCountList.addAll(resultCount); 588 return this; 589 } 590 591 @Override withAboveKeyboard(List<Boolean> aboveKeyboard)592 public StatsImpressionLogger withAboveKeyboard(List<Boolean> aboveKeyboard) { 593 this.mAboveKeyboardList.clear(); 594 this.mAboveKeyboardList.addAll(aboveKeyboard); 595 return this; 596 } 597 598 @Override log(EventEnum event)599 public void log(EventEnum event) { 600 boolean [] mAboveKeyboard = new boolean[mAboveKeyboardList.size()]; 601 for (int i = 0; i < mAboveKeyboardList.size(); i++) { 602 mAboveKeyboard[i] = mAboveKeyboardList.get(i); 603 } 604 if (IS_VERBOSE) { 605 String name = (event instanceof Enum) ? ((Enum) event).name() : 606 event.getId() + ""; 607 StringBuilder logStringBuilder = new StringBuilder("\n"); 608 logStringBuilder.append(String.format("InstanceId:%s ", mInstanceId)); 609 logStringBuilder.append(String.format("ImpressionEvent:%s ", name)); 610 logStringBuilder.append(String.format("LauncherState = %s ", mLauncherState)); 611 logStringBuilder.append(String.format("QueryLength = %s ", mQueryLength)); 612 for (int i = 0; i < mResultTypeList.size(); i++) { 613 logStringBuilder.append(String.format( 614 "\n ResultType = %s with ResultCount = %s with is_above_keyboard = %s", 615 mResultTypeList.get(i), mResultCountList.get(i), 616 mAboveKeyboard[i])); 617 } 618 Log.d(IMPRESSION_TAG, logStringBuilder.toString()); 619 } 620 621 622 623 SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_IMPRESSION_EVENT, 624 event.getId(), // event_id 625 mInstanceId.getId(), // instance_id 626 mLauncherState.getLauncherState(), // state 627 mQueryLength, // query_length 628 //result type list 629 mResultTypeList.toArray(), 630 // result count list 631 mResultCountList.toArray(), 632 // above keyboard list 633 mAboveKeyboard 634 ); 635 } 636 } 637 getCardinality(LauncherAtom.ItemInfo info)638 private static int getCardinality(LauncherAtom.ItemInfo info) { 639 if (Utilities.isRunningInTestHarness()) { 640 return 0; 641 } 642 switch (info.getContainerInfo().getContainerCase()) { 643 case PREDICTED_HOTSEAT_CONTAINER: 644 return info.getContainerInfo().getPredictedHotseatContainer().getCardinality(); 645 case TASK_BAR_CONTAINER: 646 return info.getContainerInfo().getTaskBarContainer().getCardinality(); 647 case SEARCH_RESULT_CONTAINER: 648 return info.getContainerInfo().getSearchResultContainer().getQueryLength(); 649 case EXTENDED_CONTAINERS: 650 ExtendedContainers extendedCont = info.getContainerInfo().getExtendedContainers(); 651 if (extendedCont.getContainerCase() == DEVICE_SEARCH_RESULT_CONTAINER) { 652 DeviceSearchResultContainer deviceSearchResultCont = extendedCont 653 .getDeviceSearchResultContainer(); 654 return deviceSearchResultCont.hasQueryLength() ? deviceSearchResultCont 655 .getQueryLength() : -1; 656 } 657 default: 658 return info.getFolderIcon().getCardinality(); 659 } 660 } 661 getPackageName(LauncherAtom.ItemInfo info)662 private static String getPackageName(LauncherAtom.ItemInfo info) { 663 switch (info.getItemCase()) { 664 case APPLICATION: 665 return info.getApplication().getPackageName(); 666 case SHORTCUT: 667 return info.getShortcut().getShortcutName(); 668 case WIDGET: 669 return info.getWidget().getPackageName(); 670 case TASK: 671 return info.getTask().getPackageName(); 672 case SEARCH_ACTION_ITEM: 673 return info.getSearchActionItem().getPackageName(); 674 default: 675 return null; 676 } 677 } 678 getComponentName(LauncherAtom.ItemInfo info)679 private static String getComponentName(LauncherAtom.ItemInfo info) { 680 switch (info.getItemCase()) { 681 case APPLICATION: 682 return info.getApplication().getComponentName(); 683 case SHORTCUT: 684 return info.getShortcut().getShortcutName(); 685 case WIDGET: 686 return info.getWidget().getComponentName(); 687 case TASK: 688 return info.getTask().getComponentName(); 689 case SEARCH_ACTION_ITEM: 690 return info.getSearchActionItem().getTitle(); 691 case SLICE: 692 return info.getSlice().getUri(); 693 default: 694 return null; 695 } 696 } 697 getGridX(LauncherAtom.ItemInfo info, boolean parent)698 private static int getGridX(LauncherAtom.ItemInfo info, boolean parent) { 699 LauncherAtom.ContainerInfo containerInfo = info.getContainerInfo(); 700 if (containerInfo.getContainerCase() == FOLDER) { 701 if (parent) { 702 return containerInfo.getFolder().getWorkspace().getGridX(); 703 } else { 704 return containerInfo.getFolder().getGridX(); 705 } 706 } else if (containerInfo.getContainerCase() == EXTENDED_CONTAINERS) { 707 return containerInfo.getExtendedContainers() 708 .getDeviceSearchResultContainer().getGridX(); 709 } else { 710 return containerInfo.getWorkspace().getGridX(); 711 } 712 } 713 getGridY(LauncherAtom.ItemInfo info, boolean parent)714 private static int getGridY(LauncherAtom.ItemInfo info, boolean parent) { 715 if (info.getContainerInfo().getContainerCase() == FOLDER) { 716 if (parent) { 717 return info.getContainerInfo().getFolder().getWorkspace().getGridY(); 718 } else { 719 return info.getContainerInfo().getFolder().getGridY(); 720 } 721 } else { 722 return info.getContainerInfo().getWorkspace().getGridY(); 723 } 724 } 725 getPageId(LauncherAtom.ItemInfo info)726 private static int getPageId(LauncherAtom.ItemInfo info) { 727 if (info.hasTask()) { 728 return info.getTask().getIndex(); 729 } 730 switch (info.getContainerInfo().getContainerCase()) { 731 case FOLDER: 732 return info.getContainerInfo().getFolder().getPageIndex(); 733 case HOTSEAT: 734 return info.getContainerInfo().getHotseat().getIndex(); 735 case PREDICTED_HOTSEAT_CONTAINER: 736 return info.getContainerInfo().getPredictedHotseatContainer().getIndex(); 737 case TASK_BAR_CONTAINER: 738 return info.getContainerInfo().getTaskBarContainer().getIndex(); 739 default: 740 return info.getContainerInfo().getWorkspace().getPageIndex(); 741 } 742 } 743 getParentPageId(LauncherAtom.ItemInfo info)744 private static int getParentPageId(LauncherAtom.ItemInfo info) { 745 switch (info.getContainerInfo().getContainerCase()) { 746 case FOLDER: 747 if (info.getContainerInfo().getFolder().getParentContainerCase() 748 == ParentContainerCase.HOTSEAT) { 749 return info.getContainerInfo().getFolder().getHotseat().getIndex(); 750 } 751 return info.getContainerInfo().getFolder().getWorkspace().getPageIndex(); 752 case SEARCH_RESULT_CONTAINER: 753 return info.getContainerInfo().getSearchResultContainer().getWorkspace() 754 .getPageIndex(); 755 default: 756 return info.getContainerInfo().getWorkspace().getPageIndex(); 757 } 758 } 759 getHierarchy(LauncherAtom.ItemInfo info)760 private static int getHierarchy(LauncherAtom.ItemInfo info) { 761 if (Utilities.isRunningInTestHarness()) { 762 return 0; 763 } 764 if (info.getContainerInfo().getContainerCase() == FOLDER) { 765 return info.getContainerInfo().getFolder().getParentContainerCase().getNumber() 766 + FOLDER_HIERARCHY_OFFSET; 767 } else if (info.getContainerInfo().getContainerCase() == SEARCH_RESULT_CONTAINER) { 768 return info.getContainerInfo().getSearchResultContainer().getParentContainerCase() 769 .getNumber() + SEARCH_RESULT_HIERARCHY_OFFSET; 770 } else if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS) { 771 return info.getContainerInfo().getExtendedContainers().getContainerCase().getNumber() 772 + EXTENDED_CONTAINERS_HIERARCHY_OFFSET; 773 } else if (info.getContainerInfo().getContainerCase() == ALL_APPS_CONTAINER) { 774 return info.getContainerInfo().getAllAppsContainer().getParentContainerCase() 775 .getNumber() + ALL_APPS_HIERARCHY_OFFSET; 776 } else { 777 return info.getContainerInfo().getContainerCase().getNumber(); 778 } 779 } 780 getStateString(int state)781 private static String getStateString(int state) { 782 switch (state) { 783 case LAUNCHER_UICHANGED__DST_STATE__BACKGROUND: 784 return "BACKGROUND"; 785 case LAUNCHER_UICHANGED__DST_STATE__HOME: 786 return "HOME"; 787 case LAUNCHER_UICHANGED__DST_STATE__OVERVIEW: 788 return "OVERVIEW"; 789 case LAUNCHER_UICHANGED__DST_STATE__ALLAPPS: 790 return "ALLAPPS"; 791 default: 792 return "INVALID"; 793 } 794 } 795 getFeatures(LauncherAtom.ItemInfo info)796 private static int getFeatures(LauncherAtom.ItemInfo info) { 797 if (info.getItemCase().equals(LauncherAtom.ItemInfo.ItemCase.WIDGET)) { 798 return info.getWidget().getWidgetFeatures(); 799 } 800 return 0; 801 } 802 getSearchAttributes(LauncherAtom.ItemInfo info)803 private static int getSearchAttributes(LauncherAtom.ItemInfo info) { 804 if (Utilities.isRunningInTestHarness()) { 805 return 0; 806 } 807 ContainerInfo containerInfo = info.getContainerInfo(); 808 if (containerInfo.getContainerCase() == EXTENDED_CONTAINERS 809 && containerInfo.getExtendedContainers().getContainerCase() 810 == DEVICE_SEARCH_RESULT_CONTAINER 811 && containerInfo.getExtendedContainers() 812 .getDeviceSearchResultContainer().hasSearchAttributes() 813 ) { 814 return searchAttributesToInt(containerInfo.getExtendedContainers() 815 .getDeviceSearchResultContainer().getSearchAttributes()); 816 } 817 return 0; 818 } 819 searchAttributesToInt(SearchAttributes searchAttributes)820 private static int searchAttributesToInt(SearchAttributes searchAttributes) { 821 int response = 0; 822 if (searchAttributes.getCorrectedQuery()) { 823 response = response | SEARCH_ATTRIBUTES_CORRECTED_QUERY; 824 } 825 if (searchAttributes.getDirectMatch()) { 826 response = response | SEARCH_ATTRIBUTES_DIRECT_MATCH; 827 } 828 if (searchAttributes.getEntryState() == SearchAttributes.EntryState.ALL_APPS) { 829 response = response | SEARCH_ATTRIBUTES_ENTRY_STATE_ALL_APPS; 830 } else if (searchAttributes.getEntryState() == SearchAttributes.EntryState.QSB) { 831 response = response | SEARCH_ATTRIBUTES_ENTRY_STATE_QSB; 832 } 833 834 return response; 835 } 836 837 /** 838 * Interface to get stats log while it is dispatched to the system 839 */ 840 public interface StatsLogConsumer { 841 842 @WorkerThread consume(EventEnum event, LauncherAtom.ItemInfo atomInfo)843 void consume(EventEnum event, LauncherAtom.ItemInfo atomInfo); 844 } 845 } 846