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