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.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
23 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
24 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
25 import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
26 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
27 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS;
28 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
29 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
30 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
31 
32 import android.content.Context;
33 import android.util.Log;
34 import android.util.StatsEvent;
35 import android.view.View;
36 
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 import androidx.annotation.WorkerThread;
40 import androidx.slice.SliceItem;
41 
42 import com.android.launcher3.LauncherAppState;
43 import com.android.launcher3.Utilities;
44 import com.android.launcher3.logger.LauncherAtom;
45 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
46 import com.android.launcher3.logger.LauncherAtom.FolderContainer.ParentContainerCase;
47 import com.android.launcher3.logger.LauncherAtom.FolderIcon;
48 import com.android.launcher3.logger.LauncherAtom.FromState;
49 import com.android.launcher3.logger.LauncherAtom.ToState;
50 import com.android.launcher3.logger.LauncherAtomExtensions.DeviceSearchResultContainer;
51 import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
52 import com.android.launcher3.logging.InstanceId;
53 import com.android.launcher3.logging.StatsLogManager;
54 import com.android.launcher3.model.AllAppsList;
55 import com.android.launcher3.model.BaseModelUpdateTask;
56 import com.android.launcher3.model.BgDataModel;
57 import com.android.launcher3.model.data.FolderInfo;
58 import com.android.launcher3.model.data.ItemInfo;
59 import com.android.launcher3.util.Executors;
60 import com.android.launcher3.util.LogConfig;
61 import com.android.launcher3.views.ActivityContext;
62 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
63 import com.android.systemui.shared.system.SysUiStatsLog;
64 
65 import java.util.Optional;
66 import java.util.OptionalInt;
67 import java.util.concurrent.CopyOnWriteArrayList;
68 
69 /**
70  * This class calls StatsLog compile time generated methods.
71  *
72  * To see if the logs are properly sent to statsd, execute following command.
73  * <ul>
74  * $ wwdebug (to turn on the logcat printout)
75  * $ wwlogcat (see logcat with grep filter on)
76  * $ statsd_testdrive (see how ww is writing the proto to statsd buffer)
77  * </ul>
78  */
79 public class StatsLogCompatManager extends StatsLogManager {
80 
81     private static final String TAG = "StatsLog";
82     private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
83     private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
84     // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
85     // from nano to lite, bake constant to prevent robo test failure.
86     private static final int DEFAULT_PAGE_INDEX = -2;
87     private static final int FOLDER_HIERARCHY_OFFSET = 100;
88     private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200;
89     private static final int EXTENDED_CONTAINERS_HIERARCHY_OFFSET = 300;
90     private static final int ATTRIBUTE_MULTIPLIER = 100;
91 
92     public static final CopyOnWriteArrayList<StatsLogConsumer> LOGS_CONSUMER =
93             new CopyOnWriteArrayList<>();
94 
95     private final Context mContext;
96 
StatsLogCompatManager(Context context)97     public StatsLogCompatManager(Context context) {
98         mContext = context;
99     }
100 
101     @Override
createLogger()102     protected StatsLogger createLogger() {
103         return new StatsCompatLogger(mContext, mActivityContext);
104     }
105 
106     /**
107      * Synchronously writes an itemInfo to stats log
108      */
109     @WorkerThread
writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId)110     public static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
111         if (IS_VERBOSE) {
112             Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
113         }
114         if (!Utilities.ATLEAST_R) {
115             return;
116         }
117         SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT,
118                 LAUNCHER_WORKSPACE_SNAPSHOT.getId() /* event_id */,
119                 info.getAttribute().getNumber() * ATTRIBUTE_MULTIPLIER
120                         + info.getItemCase().getNumber()  /* target_id */,
121                 instanceId.getId() /* instance_id */,
122                 0 /* uid */,
123                 getPackageName(info) /* package_name */,
124                 getComponentName(info) /* component_name */,
125                 getGridX(info, false) /* grid_x */,
126                 getGridY(info, false) /* grid_y */,
127                 getPageId(info) /* page_id */,
128                 getGridX(info, true) /* grid_x_parent */,
129                 getGridY(info, true) /* grid_y_parent */,
130                 getParentPageId(info) /* page_id_parent */,
131                 getHierarchy(info) /* hierarchy */,
132                 info.getIsWork() /* is_work_profile */,
133                 info.getAttribute().getNumber() /* origin */,
134                 getCardinality(info) /* cardinality */,
135                 info.getWidget().getSpanX(),
136                 info.getWidget().getSpanY(),
137                 getFeatures(info));
138     }
139 
140     /**
141      * Builds {@link StatsEvent} from {@link LauncherAtom.ItemInfo}. Used for pulled atom callback
142      * implementation.
143      */
buildStatsEvent(LauncherAtom.ItemInfo info, @Nullable InstanceId instanceId)144     public static StatsEvent buildStatsEvent(LauncherAtom.ItemInfo info,
145             @Nullable InstanceId instanceId) {
146         return SysUiStatsLog.buildStatsEvent(
147                 SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT, // atom ID,
148                 LAUNCHER_WORKSPACE_SNAPSHOT.getId(), // event_id = 1;
149                 info.getAttribute().getNumber() * ATTRIBUTE_MULTIPLIER
150                         + info.getItemCase().getNumber(), // item_id = 2;
151                 instanceId == null ? 0 : instanceId.getId(), //instance_id = 3;
152                 0, //uid = 4 [(is_uid) = true];
153                 getPackageName(info), // package_name = 5;
154                 getComponentName(info), // component_name = 6;
155                 getGridX(info, false), //grid_x = 7 [default = -1];
156                 getGridY(info, false), //grid_y = 8 [default = -1];
157                 getPageId(info), // page_id = 9 [default = -2];
158                 getGridX(info, true), //grid_x_parent = 10 [default = -1];
159                 getGridY(info, true), //grid_y_parent = 11 [default = -1];
160                 getParentPageId(info), //page_id_parent = 12 [default = -2];
161                 getHierarchy(info), // container_id = 13;
162                 info.getIsWork(), // is_work_profile = 14;
163                 info.getAttribute().getNumber(), // attribute_id = 15;
164                 getCardinality(info), // cardinality = 16;
165                 info.getWidget().getSpanX(), // span_x = 17 [default = 1];
166                 info.getWidget().getSpanY() // span_y = 18 [default = 1];
167         );
168     }
169 
170     /**
171      * Helps to construct and write statsd compatible log message.
172      */
173     private static class StatsCompatLogger implements StatsLogger {
174 
175         private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo();
176 
177         private final Context mContext;
178         private final Optional<ActivityContext> mActivityContext;
179         private ItemInfo mItemInfo = DEFAULT_ITEM_INFO;
180         private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
181         private OptionalInt mRank = OptionalInt.empty();
182         private Optional<ContainerInfo> mContainerInfo = Optional.empty();
183         private int mSrcState = LAUNCHER_STATE_UNSPECIFIED;
184         private int mDstState = LAUNCHER_STATE_UNSPECIFIED;
185         private Optional<FromState> mFromState = Optional.empty();
186         private Optional<ToState> mToState = Optional.empty();
187         private Optional<String> mEditText = Optional.empty();
188         private SliceItem mSliceItem;
189         private LauncherAtom.Slice mSlice;
190 
StatsCompatLogger(Context context, ActivityContext activityContext)191         StatsCompatLogger(Context context, ActivityContext activityContext) {
192             mContext = context;
193             mActivityContext = Optional.ofNullable(activityContext);
194         }
195 
196         @Override
withItemInfo(ItemInfo itemInfo)197         public StatsLogger withItemInfo(ItemInfo itemInfo) {
198             if (mContainerInfo.isPresent()) {
199                 throw new IllegalArgumentException(
200                         "ItemInfo and ContainerInfo are mutual exclusive; cannot log both.");
201             }
202             this.mItemInfo = itemInfo;
203             return this;
204         }
205 
206         @Override
withInstanceId(InstanceId instanceId)207         public StatsLogger withInstanceId(InstanceId instanceId) {
208             this.mInstanceId = instanceId;
209             return this;
210         }
211 
212         @Override
withRank(int rank)213         public StatsLogger withRank(int rank) {
214             this.mRank = OptionalInt.of(rank);
215             return this;
216         }
217 
218         @Override
withSrcState(int srcState)219         public StatsLogger withSrcState(int srcState) {
220             this.mSrcState = srcState;
221             return this;
222         }
223 
224         @Override
withDstState(int dstState)225         public StatsLogger withDstState(int dstState) {
226             this.mDstState = dstState;
227             return this;
228         }
229 
230         @Override
withContainerInfo(ContainerInfo containerInfo)231         public StatsLogger withContainerInfo(ContainerInfo containerInfo) {
232             checkState(mItemInfo == DEFAULT_ITEM_INFO,
233                     "ItemInfo and ContainerInfo are mutual exclusive; cannot log both.");
234             this.mContainerInfo = Optional.of(containerInfo);
235             return this;
236         }
237 
238         @Override
withFromState(FromState fromState)239         public StatsLogger withFromState(FromState fromState) {
240             this.mFromState = Optional.of(fromState);
241             return this;
242         }
243 
244         @Override
withToState(ToState toState)245         public StatsLogger withToState(ToState toState) {
246             this.mToState = Optional.of(toState);
247             return this;
248         }
249 
250         @Override
withEditText(String editText)251         public StatsLogger withEditText(String editText) {
252             this.mEditText = Optional.of(editText);
253             return this;
254         }
255 
256         @Override
withSliceItem(@onNull SliceItem sliceItem)257         public StatsLogger withSliceItem(@NonNull SliceItem sliceItem) {
258             checkState(mItemInfo == DEFAULT_ITEM_INFO && mSlice == null,
259                     "ItemInfo, Slice and SliceItem are mutual exclusive; cannot set more than one"
260                             + " of them.");
261             this.mSliceItem = checkNotNull(sliceItem, "expected valid sliceItem but received null");
262             return this;
263         }
264 
265         @Override
withSlice(LauncherAtom.Slice slice)266         public StatsLogger withSlice(LauncherAtom.Slice slice) {
267             checkState(mItemInfo == DEFAULT_ITEM_INFO && mSliceItem == null,
268                     "ItemInfo, Slice and SliceItem are mutual exclusive; cannot set more than one"
269                             + " of them.");
270             checkNotNull(slice, "expected valid slice but received null");
271             checkNotNull(slice.getUri(), "expected valid slice uri but received null");
272             this.mSlice = slice;
273             return this;
274         }
275 
276         @Override
log(EventEnum event)277         public void log(EventEnum event) {
278             if (!Utilities.ATLEAST_R) {
279                 return;
280             }
281             LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
282 
283             if (mSlice == null && mSliceItem != null) {
284                 mSlice = LauncherAtom.Slice.newBuilder().setUri(
285                         mSliceItem.getSlice().getUri().toString()).build();
286             }
287 
288             if (mSlice != null) {
289                 Executors.MODEL_EXECUTOR.execute(
290                         () -> {
291                             LauncherAtom.ItemInfo.Builder itemInfoBuilder =
292                                     LauncherAtom.ItemInfo.newBuilder().setSlice(mSlice);
293                             mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
294                             write(event, applyOverwrites(itemInfoBuilder.build()));
295                         });
296                 return;
297             }
298 
299             if (mItemInfo.container < 0 || appState == null) {
300                 // Write log on the model thread so that logs do not go out of order
301                 // (for eg: drop comes after drag)
302                 Executors.MODEL_EXECUTOR.execute(
303                         () -> write(event, applyOverwrites(mItemInfo.buildProto())));
304             } else {
305                 // Item is inside the folder, fetch folder info in a BG thread
306                 // and then write to StatsLog.
307                 appState.getModel().enqueueModelUpdateTask(
308                         new BaseModelUpdateTask() {
309                             @Override
310                             public void execute(LauncherAppState app, BgDataModel dataModel,
311                                     AllAppsList apps) {
312                                 FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
313                                 write(event, applyOverwrites(mItemInfo.buildProto(folderInfo)));
314                             }
315                         });
316             }
317         }
318 
319         @Override
sendToInteractionJankMonitor(EventEnum event, View view)320         public void sendToInteractionJankMonitor(EventEnum event, View view) {
321             if (!(event instanceof LauncherEvent)) {
322                 return;
323             }
324             switch ((LauncherEvent) event) {
325                 case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN:
326                     InteractionJankMonitorWrapper.begin(
327                             view,
328                             InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
329                     break;
330                 case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END:
331                     InteractionJankMonitorWrapper.end(
332                             InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
333                     break;
334                 default:
335                     break;
336             }
337         }
338 
applyOverwrites(LauncherAtom.ItemInfo atomInfo)339         private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) {
340             LauncherAtom.ItemInfo.Builder itemInfoBuilder = atomInfo.toBuilder();
341 
342             mRank.ifPresent(itemInfoBuilder::setRank);
343             mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
344 
345             mActivityContext.ifPresent(activityContext ->
346                     activityContext.applyOverwritesToLogItem(itemInfoBuilder));
347 
348             if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) {
349                 FolderIcon.Builder folderIconBuilder = itemInfoBuilder
350                         .getFolderIcon()
351                         .toBuilder();
352                 mFromState.ifPresent(folderIconBuilder::setFromLabelState);
353                 mToState.ifPresent(folderIconBuilder::setToLabelState);
354                 mEditText.ifPresent(folderIconBuilder::setLabelInfo);
355                 itemInfoBuilder.setFolderIcon(folderIconBuilder);
356             }
357             return itemInfoBuilder.build();
358         }
359 
360         @WorkerThread
write(EventEnum event, LauncherAtom.ItemInfo atomInfo)361         private void write(EventEnum event, LauncherAtom.ItemInfo atomInfo) {
362             InstanceId instanceId = mInstanceId;
363             int srcState = mSrcState;
364             int dstState = mDstState;
365             if (IS_VERBOSE) {
366                 String name = (event instanceof Enum) ? ((Enum) event).name() :
367                         event.getId() + "";
368 
369                 Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID
370                         ? String.format("\n%s (State:%s->%s)\n%s", name, getStateString(srcState),
371                         getStateString(dstState), atomInfo)
372                         : String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s", name,
373                                 getStateString(srcState), getStateString(dstState), instanceId,
374                                 atomInfo));
375             }
376 
377             for (StatsLogConsumer consumer : LOGS_CONSUMER) {
378                 consumer.consume(event, atomInfo);
379             }
380 
381             SysUiStatsLog.write(
382                     SysUiStatsLog.LAUNCHER_EVENT,
383                     SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */,
384                     srcState,
385                     dstState,
386                     null /* launcher extensions, deprecated */,
387                     false /* quickstep_enabled, deprecated */,
388                     event.getId() /* event_id */,
389                     atomInfo.getAttribute().getNumber() * ATTRIBUTE_MULTIPLIER
390                             + atomInfo.getItemCase().getNumber() /* target_id */,
391                     instanceId.getId() /* instance_id TODO */,
392                     0 /* uid TODO */,
393                     getPackageName(atomInfo) /* package_name */,
394                     getComponentName(atomInfo) /* component_name */,
395                     getGridX(atomInfo, false) /* grid_x */,
396                     getGridY(atomInfo, false) /* grid_y */,
397                     getPageId(atomInfo) /* page_id */,
398                     getGridX(atomInfo, true) /* grid_x_parent */,
399                     getGridY(atomInfo, true) /* grid_y_parent */,
400                     getParentPageId(atomInfo) /* page_id_parent */,
401                     getHierarchy(atomInfo) /* hierarchy */,
402                     atomInfo.getIsWork() /* is_work_profile */,
403                     atomInfo.getRank() /* rank */,
404                     atomInfo.getFolderIcon().getFromLabelState().getNumber() /* fromState */,
405                     atomInfo.getFolderIcon().getToLabelState().getNumber() /* toState */,
406                     atomInfo.getFolderIcon().getLabelInfo() /* edittext */,
407                     getCardinality(atomInfo) /* cardinality */,
408                     getFeatures(atomInfo) /* features */);
409         }
410     }
411 
getCardinality(LauncherAtom.ItemInfo info)412     private static int getCardinality(LauncherAtom.ItemInfo info) {
413         switch (info.getContainerInfo().getContainerCase()) {
414             case PREDICTED_HOTSEAT_CONTAINER:
415                 return info.getContainerInfo().getPredictedHotseatContainer().getCardinality();
416             case TASK_BAR_CONTAINER:
417                 return info.getContainerInfo().getTaskBarContainer().getCardinality();
418             case SEARCH_RESULT_CONTAINER:
419                 return info.getContainerInfo().getSearchResultContainer().getQueryLength();
420             case EXTENDED_CONTAINERS:
421                 ExtendedContainers extendedCont = info.getContainerInfo().getExtendedContainers();
422                 if (extendedCont.getContainerCase() == DEVICE_SEARCH_RESULT_CONTAINER) {
423                     DeviceSearchResultContainer deviceSearchResultCont = extendedCont
424                             .getDeviceSearchResultContainer();
425                     return deviceSearchResultCont.hasQueryLength() ? deviceSearchResultCont
426                             .getQueryLength() : -1;
427                 }
428             default:
429                 return info.getFolderIcon().getCardinality();
430         }
431     }
432 
getPackageName(LauncherAtom.ItemInfo info)433     private static String getPackageName(LauncherAtom.ItemInfo info) {
434         switch (info.getItemCase()) {
435             case APPLICATION:
436                 return info.getApplication().getPackageName();
437             case SHORTCUT:
438                 return info.getShortcut().getShortcutName();
439             case WIDGET:
440                 return info.getWidget().getPackageName();
441             case TASK:
442                 return info.getTask().getPackageName();
443             case SEARCH_ACTION_ITEM:
444                 return info.getSearchActionItem().getPackageName();
445             default:
446                 return null;
447         }
448     }
449 
getComponentName(LauncherAtom.ItemInfo info)450     private static String getComponentName(LauncherAtom.ItemInfo info) {
451         switch (info.getItemCase()) {
452             case APPLICATION:
453                 return info.getApplication().getComponentName();
454             case SHORTCUT:
455                 return info.getShortcut().getShortcutName();
456             case WIDGET:
457                 return info.getWidget().getComponentName();
458             case TASK:
459                 return info.getTask().getComponentName();
460             case SEARCH_ACTION_ITEM:
461                 return info.getSearchActionItem().getTitle();
462             case SLICE:
463                 return info.getSlice().getUri();
464             default:
465                 return null;
466         }
467     }
468 
getGridX(LauncherAtom.ItemInfo info, boolean parent)469     private static int getGridX(LauncherAtom.ItemInfo info, boolean parent) {
470         if (info.getContainerInfo().getContainerCase() == FOLDER) {
471             if (parent) {
472                 return info.getContainerInfo().getFolder().getWorkspace().getGridX();
473             } else {
474                 return info.getContainerInfo().getFolder().getGridX();
475             }
476         } else {
477             return info.getContainerInfo().getWorkspace().getGridX();
478         }
479     }
480 
getGridY(LauncherAtom.ItemInfo info, boolean parent)481     private static int getGridY(LauncherAtom.ItemInfo info, boolean parent) {
482         if (info.getContainerInfo().getContainerCase() == FOLDER) {
483             if (parent) {
484                 return info.getContainerInfo().getFolder().getWorkspace().getGridY();
485             } else {
486                 return info.getContainerInfo().getFolder().getGridY();
487             }
488         } else {
489             return info.getContainerInfo().getWorkspace().getGridY();
490         }
491     }
492 
getPageId(LauncherAtom.ItemInfo info)493     private static int getPageId(LauncherAtom.ItemInfo info) {
494         if (info.hasTask()) {
495             return info.getTask().getIndex();
496         }
497         switch (info.getContainerInfo().getContainerCase()) {
498             case FOLDER:
499                 return info.getContainerInfo().getFolder().getPageIndex();
500             case HOTSEAT:
501                 return info.getContainerInfo().getHotseat().getIndex();
502             case PREDICTED_HOTSEAT_CONTAINER:
503                 return info.getContainerInfo().getPredictedHotseatContainer().getIndex();
504             case TASK_BAR_CONTAINER:
505                 return info.getContainerInfo().getTaskBarContainer().getIndex();
506             default:
507                 return info.getContainerInfo().getWorkspace().getPageIndex();
508         }
509     }
510 
getParentPageId(LauncherAtom.ItemInfo info)511     private static int getParentPageId(LauncherAtom.ItemInfo info) {
512         switch (info.getContainerInfo().getContainerCase()) {
513             case FOLDER:
514                 if (info.getContainerInfo().getFolder().getParentContainerCase()
515                         == ParentContainerCase.HOTSEAT) {
516                     return info.getContainerInfo().getFolder().getHotseat().getIndex();
517                 }
518                 return info.getContainerInfo().getFolder().getWorkspace().getPageIndex();
519             case SEARCH_RESULT_CONTAINER:
520                 return info.getContainerInfo().getSearchResultContainer().getWorkspace()
521                         .getPageIndex();
522             default:
523                 return info.getContainerInfo().getWorkspace().getPageIndex();
524         }
525     }
526 
getHierarchy(LauncherAtom.ItemInfo info)527     private static int getHierarchy(LauncherAtom.ItemInfo info) {
528         if (info.getContainerInfo().getContainerCase() == FOLDER) {
529             return info.getContainerInfo().getFolder().getParentContainerCase().getNumber()
530                     + FOLDER_HIERARCHY_OFFSET;
531         } else if (info.getContainerInfo().getContainerCase() == SEARCH_RESULT_CONTAINER) {
532             return info.getContainerInfo().getSearchResultContainer().getParentContainerCase()
533                     .getNumber() + SEARCH_RESULT_HIERARCHY_OFFSET;
534         } else if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS) {
535             return info.getContainerInfo().getExtendedContainers().getContainerCase().getNumber()
536                     + EXTENDED_CONTAINERS_HIERARCHY_OFFSET;
537         } else {
538             return info.getContainerInfo().getContainerCase().getNumber();
539         }
540     }
541 
getStateString(int state)542     private static String getStateString(int state) {
543         switch (state) {
544             case LAUNCHER_UICHANGED__DST_STATE__BACKGROUND:
545                 return "BACKGROUND";
546             case LAUNCHER_UICHANGED__DST_STATE__HOME:
547                 return "HOME";
548             case LAUNCHER_UICHANGED__DST_STATE__OVERVIEW:
549                 return "OVERVIEW";
550             case LAUNCHER_UICHANGED__DST_STATE__ALLAPPS:
551                 return "ALLAPPS";
552             default:
553                 return "INVALID";
554         }
555     }
556 
getFeatures(LauncherAtom.ItemInfo info)557     private static int getFeatures(LauncherAtom.ItemInfo info) {
558         if (info.getItemCase().equals(LauncherAtom.ItemInfo.ItemCase.WIDGET)) {
559             return info.getWidget().getWidgetFeatures();
560         }
561         return 0;
562     }
563 
564 
565     /**
566      * Interface to get stats log while it is dispatched to the system
567      */
568     public interface StatsLogConsumer {
569 
570         @WorkerThread
consume(EventEnum event, LauncherAtom.ItemInfo atomInfo)571         void consume(EventEnum event, LauncherAtom.ItemInfo atomInfo);
572     }
573 }
574