diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java index be57dece79..c3f5c00d75 100644 --- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java +++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -15,11 +15,15 @@ */ package com.android.launcher3.model; +import static android.text.format.DateUtils.DAY_IN_MILLIS; +import static android.text.format.DateUtils.formatElapsedTime; + import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 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 android.app.prediction.AppPredictionContext; @@ -29,10 +33,12 @@ import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.os.UserHandle; +import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; @@ -40,12 +46,16 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.logging.InstanceId; +import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.AppInfo; +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; @@ -61,6 +71,10 @@ import java.util.stream.IntStream; public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener { public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state"; + private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS"; + + private static final boolean IS_DEBUG = false; + private static final String TAG = "QuickstepModelDelegate"; private final PredictorState mAllAppsState = new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions"); @@ -81,6 +95,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange } @Override + @WorkerThread public void loadItems(UserManagerState ums, Map pinnedShortcuts) { // TODO: Implement caching and preloading super.loadItems(ums, pinnedShortcuts); @@ -105,6 +120,38 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange recreatePredictors(); } + @Override + @WorkerThread + public void modelLoadComplete() { + super.modelLoadComplete(); + + // Log snapshot of the model + SharedPreferences prefs = getDevicePrefs(mApp.getContext()); + long lastSnapshotTimeMillis = prefs.getLong(LAST_SNAPSHOT_TIME_MILLIS, 0); + // Log snapshot only if previous snapshot was older than a day + long now = System.currentTimeMillis(); + if (now - lastSnapshotTimeMillis < DAY_IN_MILLIS) { + if (IS_DEBUG) { + String elapsedTime = formatElapsedTime((now - lastSnapshotTimeMillis) / 1000); + Log.d(TAG, String.format( + "Skipped snapshot logging since previous snapshot was %s old.", + elapsedTime)); + } + } else { + IntSparseArrayMap itemsIdMap; + synchronized (mDataModel) { + itemsIdMap = mDataModel.itemsIdMap.clone(); + } + InstanceId instanceId = new InstanceIdSequence().newInstanceId(); + for (ItemInfo info : itemsIdMap) { + FolderInfo parent = info.container > 0 + ? (FolderInfo) itemsIdMap.get(info.container) : null; + StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId); + } + prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply(); + } + } + @Override public void validateData() { super.validateData(); diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java index 32268a49b5..80308a58e2 100644 --- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java +++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java @@ -15,19 +15,25 @@ */ package com.android.quickstep; +import android.annotation.TargetApi; import android.content.Context; import android.content.pm.PackageManager; +import android.os.Build; import android.os.UserManager; import android.util.Log; import com.android.launcher3.BuildConfig; import com.android.launcher3.MainProcessInitializer; +import com.android.launcher3.util.Executors; +import com.android.quickstep.logging.SettingsChangeLogger; import com.android.systemui.shared.system.ThreadedRendererCompat; @SuppressWarnings("unused") +@TargetApi(Build.VERSION_CODES.R) public class QuickstepProcessInitializer extends MainProcessInitializer { private static final String TAG = "QuickstepProcessInitializer"; + private static final int SETUP_DELAY_MILLIS = 5000; public QuickstepProcessInitializer(Context context) { } @@ -51,5 +57,9 @@ public class QuickstepProcessInitializer extends MainProcessInitializer { // Elevate GPU priority for Quickstep and Remote animations. ThreadedRendererCompat.setContextPriority( ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG); + + // Initialize settings logger after a default timeout + Executors.MAIN_EXECUTOR.getHandler() + .postDelayed(() -> new SettingsChangeLogger(context), SETUP_DELAY_MILLIS); } } diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java new file mode 100644 index 0000000000..0bb0bbc6c8 --- /dev/null +++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.logging; + +import static com.android.launcher3.Utilities.getDevicePrefs; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_DISABLED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED; +import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE; +import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.content.res.TypedArray; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Xml; + +import com.android.launcher3.AutoInstallsLayout; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.logging.InstanceIdSequence; +import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.logging.StatsLogManager.StatsLogger; +import com.android.launcher3.util.SecureSettingsObserver; +import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * Utility class to log launcher settings changes + */ +public class SettingsChangeLogger implements + NavigationModeChangeListener, OnSharedPreferenceChangeListener { + + private static final String TAG = "SettingsChangeLogger"; + private static final String ROOT_TAG = "androidx.preference.PreferenceScreen"; + private static final String BOOLEAN_PREF = "SwitchPreference"; + + private final Context mContext; + private final ArrayMap mLoggablePrefs; + + private Mode mNavMode; + private boolean mNotificationDotsEnabled; + + public SettingsChangeLogger(Context context) { + mContext = context; + mLoggablePrefs = loadPrefKeys(context); + mNavMode = SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this); + + Utilities.getPrefs(context).registerOnSharedPreferenceChangeListener(this); + getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this); + + SecureSettingsObserver dotsObserver = + newNotificationSettingsObserver(context, this::onNotificationDotsChanged); + mNotificationDotsEnabled = dotsObserver.getValue(); + dispatchUserEvent(); + + } + + private static ArrayMap loadPrefKeys(Context context) { + XmlPullParser parser = context.getResources().getXml(R.xml.launcher_preferences); + ArrayMap result = new ArrayMap<>(); + + try { + AutoInstallsLayout.beginDocument(parser, ROOT_TAG); + final int depth = parser.getDepth(); + int type; + while (((type = parser.next()) != XmlPullParser.END_TAG + || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + if (BOOLEAN_PREF.equals(parser.getName())) { + TypedArray a = context.obtainStyledAttributes( + Xml.asAttributeSet(parser), R.styleable.LoggablePref); + String key = a.getString(R.styleable.LoggablePref_android_key); + LoggablePref pref = new LoggablePref(); + pref.defaultValue = + a.getBoolean(R.styleable.LoggablePref_android_defaultValue, true); + pref.eventIdOn = a.getInt(R.styleable.LoggablePref_logIdOn, 0); + pref.eventIdOff = a.getInt(R.styleable.LoggablePref_logIdOff, 0); + if (pref.eventIdOff > 0 && pref.eventIdOn > 0) { + result.put(key, pref); + } + } + } + } catch (XmlPullParserException | IOException e) { + Log.e(TAG, "Error parsing preference xml", e); + } + return result; + } + + private void onNotificationDotsChanged(boolean isDotsEnabled) { + mNotificationDotsEnabled = isDotsEnabled; + dispatchUserEvent(); + } + + @Override + public void onNavigationModeChanged(Mode newMode) { + mNavMode = newMode; + dispatchUserEvent(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { + if (LAST_PREDICTION_ENABLED_STATE.equals(key) || mLoggablePrefs.containsKey(key)) { + dispatchUserEvent(); + } + } + + private void dispatchUserEvent() { + StatsLogger logger = StatsLogManager.newInstance(mContext).logger() + .withInstanceId(new InstanceIdSequence().newInstanceId()); + + logger.log(mNotificationDotsEnabled + ? LAUNCHER_NOTIFICATION_DOT_ENABLED + : LAUNCHER_NOTIFICATION_DOT_DISABLED); + logger.log(mNavMode.launcherEvent); + logger.log(getDevicePrefs(mContext).getBoolean(LAST_PREDICTION_ENABLED_STATE, true) + ? LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED + : LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED); + + SharedPreferences prefs = Utilities.getPrefs(mContext); + mLoggablePrefs.forEach((key, lp) -> logger.log(() -> + prefs.getBoolean(key, lp.defaultValue) ? lp.eventIdOn : lp.eventIdOff)); + } + + private static class LoggablePref { + public boolean defaultValue; + public int eventIdOn; + public int eventIdOff; + } +} diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index 059d158ae6..d949126981 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -16,10 +16,6 @@ package com.android.quickstep.logging; -import static android.text.format.DateUtils.DAY_IN_MILLIS; -import static android.text.format.DateUtils.formatElapsedTime; - -import static com.android.launcher3.Utilities.getDevicePrefs; import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER; import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT; @@ -28,8 +24,6 @@ import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGE import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME; import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW; -import static java.lang.System.currentTimeMillis; - import android.content.Context; import android.util.Log; @@ -44,22 +38,16 @@ import com.android.launcher3.logger.LauncherAtom.FolderIcon; import com.android.launcher3.logger.LauncherAtom.FromState; import com.android.launcher3.logger.LauncherAtom.ToState; import com.android.launcher3.logging.InstanceId; -import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BaseModelUpdateTask; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.LauncherAppWidgetInfo; -import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.Executors; -import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.LogConfig; import com.android.systemui.shared.system.SysUiStatsLog; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import java.util.OptionalInt; import java.util.concurrent.CopyOnWriteArrayList; @@ -78,7 +66,6 @@ public class StatsLogCompatManager extends StatsLogManager { private static final String TAG = "StatsLog"; private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG); - private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS"; private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0); // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates // from nano to lite, bake constant to prevent robo test failure. @@ -101,71 +88,10 @@ public class StatsLogCompatManager extends StatsLogManager { } /** - * Logs impression of the current workspace with additional launcher events. + * Synchronously writes an itemInfo to stats log */ - @Override - public void logSnapshot(List extraEvents) { - LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask( - new SnapshotWorker(extraEvents)); - } - - private class SnapshotWorker extends BaseModelUpdateTask { - private final InstanceId mInstanceId; - private final List mExtraEvents; - - SnapshotWorker(List extraEvents) { - mInstanceId = new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/) - .newInstanceId(); - this.mExtraEvents = extraEvents; - } - - @Override - public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { - long lastSnapshotTimeMillis = getDevicePrefs(mContext) - .getLong(LAST_SNAPSHOT_TIME_MILLIS, 0); - // Log snapshot only if previous snapshot was older than a day - if (currentTimeMillis() - lastSnapshotTimeMillis < DAY_IN_MILLIS) { - if (IS_VERBOSE) { - String elapsedTime = formatElapsedTime( - (currentTimeMillis() - lastSnapshotTimeMillis) / 1000); - Log.d(TAG, String.format( - "Skipped snapshot logging since previous snapshot was %s old.", - elapsedTime)); - } - return; - } - - IntSparseArrayMap folders = dataModel.folders.clone(); - ArrayList workspaceItems = (ArrayList) dataModel.workspaceItems.clone(); - ArrayList appWidgets = (ArrayList) dataModel.appWidgets.clone(); - for (ItemInfo info : workspaceItems) { - LauncherAtom.ItemInfo atomInfo = info.buildProto(null); - writeSnapshot(atomInfo, mInstanceId); - } - for (FolderInfo fInfo : folders) { - try { - ArrayList folderContents = - (ArrayList) Executors.MAIN_EXECUTOR.submit(fInfo.contents::clone).get(); - for (ItemInfo info : folderContents) { - LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo); - writeSnapshot(atomInfo, mInstanceId); - } - } catch (Exception e) { - } - } - for (ItemInfo info : appWidgets) { - LauncherAtom.ItemInfo atomInfo = info.buildProto(null); - writeSnapshot(atomInfo, mInstanceId); - } - mExtraEvents - .forEach(eventName -> logger().withInstanceId(mInstanceId).log(eventName)); - - getDevicePrefs(mContext).edit() - .putLong(LAST_SNAPSHOT_TIME_MILLIS, currentTimeMillis()).apply(); - } - } - - private void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) { + @WorkerThread + public static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) { if (IS_VERBOSE) { Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info)); } diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 6b0f30082c..c3ad1eb823 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -174,6 +174,14 @@ + + + + + + + + diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml index 7e7220813a..e4bea508b3 100644 --- a/res/xml/launcher_preferences.xml +++ b/res/xml/launcher_preferences.xml @@ -15,7 +15,8 @@ --> + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res-auto"> + + android:persistent="true" + launcher:logIdOn="613" + launcher:logIdOff="614" /> + + android:persistent="true" + launcher:logIdOn="615" + launcher:logIdOff="616" /> additionalEvents) { - } } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index d47fafd40b..b108788061 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -289,6 +289,7 @@ public class LoaderTask implements Runnable { updateHandler.finish(); logger.addSplit("finish icon update"); + mModelDelegate.modelLoadComplete(); transaction.commit(); } catch (CancellationException e) { // Loader stopped, ignore diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java index 3ed880906d..92bea5b14c 100644 --- a/src/com/android/launcher3/model/ModelDelegate.java +++ b/src/com/android/launcher3/model/ModelDelegate.java @@ -77,6 +77,12 @@ public class ModelDelegate implements ResourceBasedOverride { @WorkerThread public void workspaceLoadComplete() { } + /** + * Called at the end of model load task + */ + @WorkerThread + public void modelLoadComplete() { } + /** * Called when the delegate is no loner needed */