Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b694a69f authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Integrating snapshot logging in Launcher

> Workspace snapshot is logged when the workspace loads (at most once a day)
> Removing unnecessary thread jumping when logging folders
> Preference snapshot is logged on process start and whenever something changes

Change-Id: I93767de89b11522d843c0e8300d1f108c78f6d90
parent ff8febab
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -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<ShortcutKey, ShortcutInfo> 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<ItemInfo> 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();
+10 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+155 −0
Original line number Diff line number Diff line
/*
 * 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<String, LoggablePref> 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<String, LoggablePref> loadPrefKeys(Context context) {
        XmlPullParser parser = context.getResources().getXml(R.xml.launcher_preferences);
        ArrayMap<String, LoggablePref> 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;
    }
}
+3 −77
Original line number Diff line number Diff line
@@ -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<EventEnum> extraEvents) {
        LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
                new SnapshotWorker(extraEvents));
    }

    private class SnapshotWorker extends BaseModelUpdateTask {
        private final InstanceId mInstanceId;
        private final List<EventEnum> mExtraEvents;

        SnapshotWorker(List<EventEnum> 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<FolderInfo> folders = dataModel.folders.clone();
            ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
            ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
            for (ItemInfo info : workspaceItems) {
                LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
                writeSnapshot(atomInfo, mInstanceId);
            }
            for (FolderInfo fInfo : folders) {
                try {
                    ArrayList<WorkspaceItemInfo> 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));
        }
+8 −0
Original line number Diff line number Diff line
@@ -174,6 +174,14 @@
        <attr name="canThumbDetach" format="boolean" />
    </declare-styleable>

    <declare-styleable name="LoggablePref">
        <attr name="android:key" />
        <attr name="android:defaultValue" />
        <!-- Ground truth of this Pref integer can be found in StatsLogManager -->
        <attr name="logIdOn" format="integer" />
        <attr name="logIdOff" format="integer" />
    </declare-styleable>

    <declare-styleable name="PreviewFragment">
        <attr name="android:name" />
        <attr name="android:id" />
Loading