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

Commit 852537fd authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Using StatsLog for notifyingAppPredictor

> Adding a listener in StartsLogManager for listening to events.
  This allows events to be directored to the predictor only if
  it is already running, instead of creating it.
> Unifying the event format to be same as hotseat predictor

Bug: 160748731
Change-Id: Ib00e6249ff642c030f00bcad5b748255e704d16a
parent 2c0cdfb1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -131,6 +131,7 @@ message Application {
// Legacy shortcuts and shortcuts handled by ShortcutManager
message Shortcut {
  optional string shortcut_name = 1;
  optional string shortcut_id = 2;
}

// AppWidgets handled by AppWidgetManager
+144 −88
Original line number Diff line number Diff line
@@ -16,6 +16,12 @@
package com.android.launcher3.appprediction;

import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;

import android.annotation.TargetApi;
@@ -31,29 +37,38 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;

import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.FolderContainer;
import com.android.launcher3.logger.LauncherAtom.HotseatContainer;
import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer;
import com.android.launcher3.logging.StatsLogManager.EventEnum;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.systemui.plugins.AppLaunchEventsPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.launcher3.pm.UserCache;
import com.android.quickstep.logging.StatsLogCompatManager;
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Predicate;

/**
 * Subclass of app tracker which publishes the data to the prediction engine and gets back results.
 */
@TargetApi(Build.VERSION_CODES.Q)
public class PredictionAppTracker extends AppLaunchTracker
        implements PluginListener<AppLaunchEventsPlugin> {
public class PredictionAppTracker extends AppLaunchTracker implements StatsLogConsumer {

    private static final String TAG = "PredictionAppTracker";
    private static final boolean DBG = false;
@@ -65,7 +80,6 @@ public class PredictionAppTracker extends AppLaunchTracker

    protected final Context mContext;
    private final Handler mMessageHandler;
    private final List<AppLaunchEventsPlugin> mAppLaunchEventsPluginsList;

    // Accessed only on worker thread
    private AppPredictor mHomeAppPredictor;
@@ -76,10 +90,6 @@ public class PredictionAppTracker extends AppLaunchTracker
        InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);

        mMessageHandler.sendEmptyMessage(MSG_INIT);

        mAppLaunchEventsPluginsList = new ArrayList<>();
        PluginManagerWrapper.INSTANCE.get(context)
                .addPluginListener(this, AppLaunchEventsPlugin.class, true);
    }

    @UiThread
@@ -96,6 +106,7 @@ public class PredictionAppTracker extends AppLaunchTracker
            mHomeAppPredictor.destroy();
            mHomeAppPredictor = null;
        }
        StatsLogCompatManager.LOGS_CONSUMER.remove(this);
    }

    @WorkerThread
@@ -137,6 +148,7 @@ public class PredictionAppTracker extends AppLaunchTracker
                // Initialize the clients
                int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns;
                mHomeAppPredictor = createPredictor(Client.HOME, count);
                StatsLogCompatManager.LOGS_CONSUMER.add(this);
                return true;
            }
            case MSG_DESTROY: {
@@ -168,98 +180,142 @@ public class PredictionAppTracker extends AppLaunchTracker
        if (DBG) {
            Log.d(TAG, String.format("Sent immediate message to update %s", client));
        }

        // Relay onReturnedToHome to every plugin.
        mAppLaunchEventsPluginsList.forEach(AppLaunchEventsPlugin::onReturnedToHome);
    }

    @Override
    @UiThread
    public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
                                String container) {
        // TODO: Use the full shortcut info
        AppTarget target = new AppTarget.Builder(
                new AppTargetId("shortcut:" + shortcutId), packageName, user)
                .setClassName(shortcutId)
    @AnyThread
    private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId) {
        AppTarget target = toAppTarget(atomInfo);
        if (target != null) {
            AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
                    .setLaunchLocation(getContainer(atomInfo))
                    .build();

        sendLaunch(target, container);

        // Relay onStartShortcut info to every connected plugin.
        mAppLaunchEventsPluginsList
                .forEach(plugin -> plugin.onStartShortcut(
                        packageName,
                        shortcutId,
                        user,
                        container != null ? container : CONTAINER_DEFAULT)
        );

            Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
        }
    }

    @Override
    @UiThread
    public void onStartApp(ComponentName cn, UserHandle user, String container) {
        if (cn != null) {
            AppTarget target = new AppTarget.Builder(
                    new AppTargetId("app:" + cn), cn.getPackageName(), user)
                    .setClassName(cn.getClassName())
                    .build();
            sendLaunch(target, container);

            // Relay onStartApp to every connected plugin.
            mAppLaunchEventsPluginsList
                    .forEach(plugin -> plugin.onStartApp(
                            cn,
                            user,
                            container != null ? container : CONTAINER_DEFAULT)
            );
    public void consume(EventEnum event, LauncherAtom.ItemInfo atomInfo) {
        if (event == LAUNCHER_APP_LAUNCH_TAP
                || event == LAUNCHER_TASK_LAUNCH_SWIPE_DOWN
                || event == LAUNCHER_TASK_LAUNCH_TAP
                || event == LAUNCHER_QUICKSWITCH_RIGHT
                || event == LAUNCHER_QUICKSWITCH_LEFT) {
            sendEvent(atomInfo, AppTargetEvent.ACTION_LAUNCH);
        } else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) {
            sendEvent(atomInfo, AppTargetEvent.ACTION_DISMISS);
        }
    }

    @Override
    @UiThread
    public void onDismissApp(ComponentName cn, UserHandle user, String container) {
        if (cn == null) return;
        AppTarget target = new AppTarget.Builder(
                new AppTargetId("app: " + cn), cn.getPackageName(), user)
    @Nullable
    private AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
        UserHandle userHandle = Process.myUserHandle();
        if (info.getIsWork()) {
            userHandle = UserCache.INSTANCE.get(mContext).getUserProfiles().stream()
                    .filter(((Predicate<UserHandle>) userHandle::equals).negate())
                    .findAny()
                    .orElse(null);
        }
        if (userHandle == null) {
            return null;
        }
        ComponentName cn = null;
        String id = null;

        switch (info.getItemCase()) {
            case APPLICATION: {
                LauncherAtom.Application app = info.getApplication();
                if ((cn = parseNullable(app.getComponentName())) != null) {
                    id = "app:" + cn.getPackageName();
                }
                break;
            }
            case SHORTCUT: {
                LauncherAtom.Shortcut si = info.getShortcut();
                if (!TextUtils.isEmpty(si.getShortcutId())
                        && (cn = parseNullable(si.getShortcutName())) != null) {
                    id = "shortcut:" + si.getShortcutId();
                }
                break;
            }
            case WIDGET: {
                LauncherAtom.Widget widget = info.getWidget();
                if ((cn = parseNullable(widget.getComponentName())) != null) {
                    id = "widget:" + cn.getPackageName();
                }
                break;
            }
            case TASK: {
                LauncherAtom.Task task = info.getTask();
                if ((cn = parseNullable(task.getComponentName())) != null) {
                    id = "app:" + cn.getPackageName();
                }
                break;
            }
            case FOLDER_ICON: {
                id = "folder:" + SystemClock.uptimeMillis();
                cn = new ComponentName(mContext.getPackageName(), "#folder");
            }
        }
        if (id != null && cn != null) {
            return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
                    .setClassName(cn.getClassName())
                    .build();
        sendDismiss(target, container);

        // Relay onDismissApp to every connected plugin.
        mAppLaunchEventsPluginsList
                .forEach(plugin -> plugin.onDismissApp(
                        cn,
                        user,
                        container != null ? container : CONTAINER_DEFAULT)
        );
        }

    @UiThread
    private void sendEvent(AppTarget target, String container, int eventId) {
        AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
                .setLaunchLocation(container == null ? CONTAINER_DEFAULT : container)
                .build();
        Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
        return null;
    }

    @UiThread
    private void sendLaunch(AppTarget target, String container) {
        sendEvent(target, container, AppTargetEvent.ACTION_LAUNCH);
    private String getContainer(LauncherAtom.ItemInfo info) {
        ContainerInfo ci = info.getContainerInfo();
        switch (ci.getContainerCase()) {
            case WORKSPACE: {
                // In case the item type is not widgets, the spaceX and spanY default to 1.
                int spanX = info.getWidget().getSpanX();
                int spanY = info.getWidget().getSpanY();
                return getWorkspaceContainerString(ci.getWorkspace(), spanX, spanY);
            }
            case HOTSEAT: {
                return getHotseatContainerString(ci.getHotseat());
            }
            case TASK_SWITCHER_CONTAINER: {
                return "task-switcher";
            }
            case ALL_APPS_CONTAINER: {
                return "all-apps";
            }
            case SEARCH_RESULT_CONTAINER: {
                return "search-results";
            }
            case PREDICTED_HOTSEAT_CONTAINER: {
                return "predictions/hotseat";
            }
            case PREDICTION_CONTAINER: {
                return "predictions";
            }
            case FOLDER: {
                FolderContainer fc = ci.getFolder();
                switch (fc.getParentContainerCase()) {
                    case WORKSPACE:
                        return "folder/" + getWorkspaceContainerString(fc.getWorkspace(), 1, 1);
                    case HOTSEAT:
                        return "folder/" + getHotseatContainerString(fc.getHotseat());
                }
                return "folder";
            }
        }
        return "";
    }

    @UiThread
    private void sendDismiss(AppTarget target, String container) {
        sendEvent(target, container, AppTargetEvent.ACTION_DISMISS);
    private static String getWorkspaceContainerString(WorkspaceContainer wc, int spanX, int spanY) {
        return String.format(Locale.ENGLISH, "workspace/%d/[%d,%d]/[%d,%d]",
                wc.getPageIndex(), wc.getGridX(), wc.getGridY(), spanX, spanY);
    }

    @Override
    public void onPluginConnected(AppLaunchEventsPlugin appLaunchEventsPlugin, Context context) {
        mAppLaunchEventsPluginsList.add(appLaunchEventsPlugin);
    private static String getHotseatContainerString(HotseatContainer hc) {
        return String.format(Locale.ENGLISH, "hotseat/%d", hc.getIndex());
    }

    @Override
    public void onPluginDisconnected(AppLaunchEventsPlugin appLaunchEventsPlugin) {
        mAppLaunchEventsPluginsList.remove(appLaunchEventsPlugin);
    private static ComponentName parseNullable(String componentNameString) {
        return TextUtils.isEmpty(componentNameString)
                ? null : ComponentName.unflattenFromString(componentNameString);
    }
}
+1 −5
Original line number Diff line number Diff line
@@ -56,7 +56,6 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -93,9 +92,6 @@ public class PredictionRowView extends LinearLayout implements
    private static final Interpolator ALPHA_FACTOR_INTERPOLATOR =
            (t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f;

    private static final OnClickListener PREDICTION_CLICK_LISTENER =
            ItemClickHandler.getInstance(AppLaunchTracker.CONTAINER_PREDICTIONS);

    private final Launcher mLauncher;
    private final PredictionUiStateManager mPredictionUiStateManager;
    private int mNumPredictedAppsPerRow;
@@ -246,7 +242,7 @@ public class PredictionRowView extends LinearLayout implements
            while (getChildCount() < mNumPredictedAppsPerRow) {
                BubbleTextView icon = (BubbleTextView) inflater.inflate(
                        R.layout.all_apps_icon, this, false);
                icon.setOnClickListener(PREDICTION_CLICK_LISTENER);
                icon.setOnClickListener(ItemClickHandler.INSTANCE);
                icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
                icon.setLongPressTimeoutFactor(1f);
                icon.setOnFocusChangeListener(mFocusHelper);
+2 −5
Original line number Diff line number Diff line
@@ -34,8 +34,6 @@ import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
@@ -129,12 +127,11 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
    }

    @Override
    public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
            @Nullable String sourceContainer) {
    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
        if (mHotseatPredictionController != null) {
            mHotseatPredictionController.setPauseUIUpdate(true);
        }
        return super.startActivitySafely(v, intent, item, sourceContainer);
        return super.startActivitySafely(v, intent, item);
    }

    @Override
+30 −23
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import android.view.ViewTreeObserver.OnDrawListener;
import android.view.WindowInsets;
import android.view.animation.Interpolator;

import androidx.annotation.Nullable;
import androidx.annotation.UiThread;

import com.android.launcher3.AbstractFloatingView;
@@ -69,6 +70,7 @@ import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -876,22 +878,7 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q exte
        animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
    }

    private void doLogGesture(GestureEndTarget endTarget) {
        DeviceProfile dp = mDp;
        if (dp == null || mDownPos == null) {
            // We probably never received an animation controller, skip logging.
            return;
        }

        int pageIndex = endTarget == LAST_TASK
                ? LOG_NO_OP_PAGE_INDEX
                : mRecentsView.getNextPage();
        UserEventDispatcher.newInstance(mContext).logStateChangeAction(
                mLogAction, mLogDirection,
                (int) mDownPos.x, (int) mDownPos.y,
                ContainerType.NAVBAR, ContainerType.APP,
                endTarget.containerType,
                pageIndex);
    private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
        StatsLogManager.EventEnum event;
        switch (endTarget) {
            case HOME:
@@ -909,10 +896,29 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q exte
            default:
                event = IGNORE;
        }
        StatsLogManager.newInstance(mContext).logger()
        StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
                .withSrcState(LAUNCHER_STATE_BACKGROUND)
                .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType))
                .log(event);
                .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType));
        if (targetTask != null) {
            logger.withItemInfo(targetTask.getItemInfo());
        }
        logger.log(event);


        DeviceProfile dp = mDp;
        if (dp == null || mDownPos == null) {
            // We probably never received an animation controller, skip logging.
            return;
        }
        int pageIndex = endTarget == LAST_TASK
                ? LOG_NO_OP_PAGE_INDEX
                : mRecentsView.getNextPage();
        UserEventDispatcher.newInstance(mContext).logStateChangeAction(
                mLogAction, mLogDirection,
                (int) mDownPos.x, (int) mDownPos.y,
                ContainerType.NAVBAR, ContainerType.APP,
                endTarget.containerType,
                pageIndex);
    }

    /** Animates to the given progress, where 0 is the current app and 1 is overview. */
@@ -1117,7 +1123,7 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q exte
    private void resumeLastTask() {
        mRecentsAnimationController.finish(false /* toRecents */, null);
        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
        doLogGesture(LAST_TASK);
        doLogGesture(LAST_TASK, null);
        reset();
    }

@@ -1132,6 +1138,7 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q exte

    @UiThread
    private void startNewTaskInternal() {
        TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
        startNewTask(success -> {
            if (!success) {
                reset();
@@ -1140,7 +1147,7 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q exte
                endLauncherTransitionController();
                updateSysUiFlags(1 /* windowProgress == overview */);
            }
            doLogGesture(NEW_TASK);
            doLogGesture(NEW_TASK, taskToLaunch);
        });
    }

@@ -1285,7 +1292,7 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q exte
                    () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
        }
        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
        doLogGesture(HOME);
        doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
    }

    protected abstract void finishRecentsControllerToHome(Runnable callback);
@@ -1300,7 +1307,7 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q exte
        mRecentsView.onSwipeUpAnimationSuccess();

        SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
        doLogGesture(RECENTS);
        doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
        reset();
    }

Loading