diff --git a/Android.bp b/Android.bp index 43d28c9ad2..c8d9186010 100644 --- a/Android.bp +++ b/Android.bp @@ -33,6 +33,9 @@ license { android_library { name: "launcher-aosp-tapl", + libs: [ + "framework-statsd", + ], static_libs: [ "androidx.annotation_annotation", "androidx.test.runner", @@ -192,6 +195,9 @@ android_library { resource_dirs: [ "quickstep/res", ], + libs: [ + "framework-statsd", + ], static_libs: [ "Launcher3ResLib", "SystemUISharedLib", @@ -261,6 +267,9 @@ android_library { resource_dirs: [ "quickstep/res", ], + libs: [ + "framework-statsd", + ], static_libs: [ "SystemUI-statsd", "SystemUISharedLib", diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java index 5769f0bf65..55a140dffb 100644 --- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java +++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -25,7 +25,9 @@ import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICA import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.Utilities.getDevicePrefs; import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import android.app.StatsManager; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionManager; import android.app.prediction.AppPredictor; @@ -39,12 +41,14 @@ import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.os.UserHandle; import android.util.Log; +import android.util.StatsEvent; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.model.BgDataModel.FixedContainerItems; @@ -53,10 +57,10 @@ import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.util.Executors; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.PersistedItemArray; import com.android.quickstep.logging.StatsLogCompatManager; +import com.android.systemui.shared.system.SysUiStatsLog; import java.util.Collections; import java.util.List; @@ -85,6 +89,7 @@ public class QuickstepModelDelegate extends ModelDelegate { private final InvariantDeviceProfile mIDP; private final AppEventProducer mAppEventProducer; + private final StatsManager mStatsManager; protected boolean mActive = false; @@ -93,6 +98,7 @@ public class QuickstepModelDelegate extends ModelDelegate { mIDP = InvariantDeviceProfile.INSTANCE.get(context); StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer); + mStatsManager = context.getSystemService(StatsManager.class); } @Override @@ -155,10 +161,60 @@ public class QuickstepModelDelegate extends ModelDelegate { additionalSnapshotEvents(instanceId); prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply(); } + + // Only register for launcher snapshot logging if this is the primary ModelDelegate + // instance, as there will be additional instances that may be destroyed at any time. + if (mIsPrimaryInstance) { + registerSnapshotLoggingCallback(); + } } protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){} + /** + * Registers a callback to log launcher workspace layout using Statsd pulled atom. + */ + protected void registerSnapshotLoggingCallback() { + if (mStatsManager == null) { + Log.d(TAG, "Failed to get StatsManager"); + } + + try { + mStatsManager.setPullAtomCallback( + SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT, + null /* PullAtomMetadata */, + MODEL_EXECUTOR, + (i, eventList) -> { + InstanceId instanceId = new InstanceIdSequence().newInstanceId(); + IntSparseArrayMap itemsIdMap; + synchronized (mDataModel) { + itemsIdMap = mDataModel.itemsIdMap.clone(); + } + + for (ItemInfo info : itemsIdMap) { + FolderInfo parent = info.container > 0 + ? (FolderInfo) itemsIdMap.get(info.container) : null; + LauncherAtom.ItemInfo itemInfo = info.buildProto(parent); + Log.d(TAG, itemInfo.toString()); + StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo, + instanceId); + eventList.add(statsEvent); + } + Log.d(TAG, + String.format( + "Successfully logged %d workspace items with instanceId=%d", + itemsIdMap.size(), instanceId.getId())); + additionalSnapshotEvents(instanceId); + return StatsManager.PULL_SUCCESS; + } + ); + Log.d(TAG, "Successfully registered for launcher snapshot logging!"); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to register launcher snapshot logging callback with StatsManager", + e); + } + } + @Override public void validateData() { super.validateData(); @@ -175,7 +231,9 @@ public class QuickstepModelDelegate extends ModelDelegate { super.destroy(); mActive = false; StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer); - + if (mIsPrimaryInstance) { + mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT); + } destroyPredictors(); } @@ -221,7 +279,7 @@ public class QuickstepModelDelegate extends ModelDelegate { private void registerPredictor(PredictorState state, AppPredictor predictor) { state.predictor = predictor; state.predictor.registerPredictionUpdates( - Executors.MODEL_EXECUTOR, t -> handleUpdate(state, t)); + MODEL_EXECUTOR, t -> handleUpdate(state, t)); state.predictor.requestPredictionUpdate(); } @@ -236,7 +294,7 @@ public class QuickstepModelDelegate extends ModelDelegate { private void registerWidgetsPredictor(AppPredictor predictor) { mWidgetsRecommendationState.predictor = predictor; mWidgetsRecommendationState.predictor.registerPredictionUpdates( - Executors.MODEL_EXECUTOR, targets -> { + MODEL_EXECUTOR, targets -> { if (mWidgetsRecommendationState.setTargets(targets)) { // No diff, skip return; diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index 6575996d69..7893f8d5a1 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -31,9 +31,11 @@ import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGE import android.content.Context; import android.util.Log; +import android.util.StatsEvent; import android.view.View; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import androidx.slice.SliceItem; @@ -134,6 +136,36 @@ public class StatsLogCompatManager extends StatsLogManager { getFeatures(info)); } + /** + * Builds {@link StatsEvent} from {@link LauncherAtom.ItemInfo}. Used for pulled atom callback + * implementation. + */ + public static StatsEvent buildStatsEvent(LauncherAtom.ItemInfo info, + @Nullable InstanceId instanceId) { + return SysUiStatsLog.buildStatsEvent( + SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT, // atom ID, + LAUNCHER_WORKSPACE_SNAPSHOT.getId(), // event_id = 1; + info.getAttribute().getNumber() * ATTRIBUTE_MULTIPLIER + + info.getItemCase().getNumber(), // item_id = 2; + instanceId == null ? 0 : instanceId.getId(), //instance_id = 3; + 0, //uid = 4 [(is_uid) = true]; + getPackageName(info), // package_name = 5; + getComponentName(info), // component_name = 6; + getGridX(info, false), //grid_x = 7 [default = -1]; + getGridY(info, false), //grid_y = 8 [default = -1]; + getPageId(info), // page_id = 9 [default = -2]; + getGridX(info, true), //grid_x_parent = 10 [default = -1]; + getGridY(info, true), //grid_y_parent = 11 [default = -1]; + getParentPageId(info), //page_id_parent = 12 [default = -2]; + getHierarchy(info), // container_id = 13; + info.getIsWork(), // is_work_profile = 14; + info.getAttribute().getNumber(), // attribute_id = 15; + getCardinality(info), // cardinality = 16; + info.getWidget().getSpanX(), // span_x = 17 [default = 1]; + info.getWidget().getSpanY() // span_y = 18 [default = 1]; + ); + } + /** * Helps to construct and write statsd compatible log message. */ diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 86217d743e..10023b43d9 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -138,10 +138,11 @@ public class LauncherAppState implements SafeCloseable { mContext = context; mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context); - mIconProvider = new IconProvider(context, Themes.isThemedIconEnabled(context)); + mIconProvider = new IconProvider(context, Themes.isThemedIconEnabled(context)); mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName, mIconProvider); - mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext)); + mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext), + iconCacheFileName != null); mOnTerminateCallback.add(mIconCache::close); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 9ebec0a2ea..f38f66282a 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -126,10 +126,12 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi } }; - LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter) { + LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter, + boolean isPrimaryInstance) { mApp = app; mBgAllAppsList = new AllAppsList(iconCache, appFilter); - mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel); + mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel, + isPrimaryInstance); } public ModelDelegate getModelDelegate() { diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java index 13ec1ecefb..765141a0e2 100644 --- a/src/com/android/launcher3/model/ModelDelegate.java +++ b/src/com/android/launcher3/model/ModelDelegate.java @@ -40,19 +40,21 @@ public class ModelDelegate implements ResourceBasedOverride { * Creates and initializes a new instance of the delegate */ public static ModelDelegate newInstance( - Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel) { + Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel, + boolean isPrimaryInstance) { ModelDelegate delegate = Overrides.getObject( ModelDelegate.class, context, R.string.model_delegate_class); - delegate.mApp = app; delegate.mAppsList = appsList; delegate.mDataModel = dataModel; + delegate.mIsPrimaryInstance = isPrimaryInstance; return delegate; } protected LauncherAppState mApp; protected AllAppsList mAppsList; protected BgDataModel mDataModel; + protected boolean mIsPrimaryInstance; public ModelDelegate() { }