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