Files
lawnchair/quickstep/src/com/android/launcher3/model/AppEventProducer.java
Brian Isganitis fdd044e16e Expose IS_RUNNING_IN_TEST_HARNESS as static method.
This variable is now mutable, making the uppercase format misleading.
For instance, users might assume they can use this value in other
immutable properties, when they really should be accessing the latest
value every time they need it.

Context: https://source.android.com/docs/setup/contribute/code-style#follow-field-naming-conventions

Test: Manual
Bug: 271160958
Change-Id: Iaaa51d9153cb8a7d686c72e1210b1948029dcfd5
2023-03-03 00:17:33 +00:00

327 lines
14 KiB
Java

/*
* Copyright (C) 2019 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.launcher3.model;
import static android.app.prediction.AppTargetEvent.ACTION_DISMISS;
import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;
import static android.app.prediction.AppTargetEvent.ACTION_PIN;
import static android.app.prediction.AppTargetEvent.ACTION_UNDISMISS;
import static android.app.prediction.AppTargetEvent.ACTION_UNPIN;
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.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
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.model.PredictionHelper.isTrackedForHotseatPrediction;
import static com.android.launcher3.model.PredictionHelper.isTrackedForWidgetPrediction;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.annotation.TargetApi;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.Build;
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 androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.Utilities;
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.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
import java.util.Locale;
import java.util.Optional;
import java.util.function.ObjIntConsumer;
import java.util.function.Predicate;
/**
* Utility class to track stats log and emit corresponding app events
*/
@TargetApi(Build.VERSION_CODES.R)
public class AppEventProducer implements StatsLogConsumer {
private static final int MSG_LAUNCH = 0;
private final Context mContext;
private final Handler mMessageHandler;
private final ObjIntConsumer<AppTargetEvent> mCallback;
private LauncherAtom.ItemInfo mLastDragItem;
public AppEventProducer(Context context, ObjIntConsumer<AppTargetEvent> callback) {
mContext = context;
mMessageHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleMessage);
mCallback = callback;
}
@WorkerThread
private boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_LAUNCH: {
mCallback.accept((AppTargetEvent) msg.obj, msg.arg1);
return true;
}
}
return false;
}
@AnyThread
private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId, int targetPredictor) {
sendEvent(toAppTarget(atomInfo), atomInfo, eventId, targetPredictor);
}
@AnyThread
private void sendEvent(AppTarget target, LauncherAtom.ItemInfo locationInfo, int eventId,
int targetPredictor) {
// TODO: remove the running test check when b/231648228 is fixed.
if (target != null && !Utilities.isRunningInTestHarness()) {
AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
.setLaunchLocation(getContainer(locationInfo))
.build();
mMessageHandler.obtainMessage(MSG_LAUNCH, targetPredictor, 0, event).sendToTarget();
}
}
@Override
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, ACTION_LAUNCH, CONTAINER_PREDICTION);
} else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) {
sendEvent(atomInfo, ACTION_DISMISS, CONTAINER_PREDICTION);
} else if (event == LAUNCHER_ITEM_DRAG_STARTED) {
mLastDragItem = atomInfo;
} else if (event == LAUNCHER_ITEM_DROP_COMPLETED) {
if (mLastDragItem == null) {
return;
}
if (isTrackedForHotseatPrediction(mLastDragItem)) {
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
}
if (isTrackedForHotseatPrediction(atomInfo)) {
sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
}
if (isTrackedForWidgetPrediction(atomInfo)) {
sendEvent(atomInfo, ACTION_PIN, CONTAINER_WIDGETS_PREDICTION);
}
mLastDragItem = null;
} else if (event == LAUNCHER_ITEM_DROP_FOLDER_CREATED) {
if (isTrackedForHotseatPrediction(atomInfo)) {
sendEvent(createTempFolderTarget(), atomInfo, ACTION_PIN,
CONTAINER_HOTSEAT_PREDICTION);
sendEvent(atomInfo, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
}
} else if (event == LAUNCHER_FOLDER_CONVERTED_TO_ICON) {
if (isTrackedForHotseatPrediction(atomInfo)) {
sendEvent(createTempFolderTarget(), atomInfo, ACTION_UNPIN,
CONTAINER_HOTSEAT_PREDICTION);
sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
}
} else if (event == LAUNCHER_ITEM_DROPPED_ON_REMOVE) {
if (mLastDragItem != null && isTrackedForHotseatPrediction(mLastDragItem)) {
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
}
if (mLastDragItem != null && isTrackedForWidgetPrediction(mLastDragItem)) {
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_WIDGETS_PREDICTION);
}
} else if (event == LAUNCHER_HOTSEAT_PREDICTION_PINNED) {
if (isTrackedForHotseatPrediction(atomInfo)) {
sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
}
} else if (event == LAUNCHER_ONRESUME) {
AppTarget target = new AppTarget.Builder(new AppTargetId("launcher:launcher"),
mContext.getPackageName(), Process.myUserHandle())
.build();
sendEvent(target, atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
} else if (event == LAUNCHER_DISMISS_PREDICTION_UNDO) {
sendEvent(atomInfo, ACTION_UNDISMISS, CONTAINER_HOTSEAT_PREDICTION);
}
}
@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;
ShortcutInfo shortcutInfo = 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) {
Optional<ShortcutInfo> opt = new ShortcutRequest(mContext,
userHandle).forPackage(cn.getPackageName(), si.getShortcutId()).query(
ShortcutRequest.ALL).stream().findFirst();
if (opt.isPresent()) {
shortcutInfo = opt.get();
} else {
return 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:
return createTempFolderTarget();
}
if (id != null && cn != null) {
if (shortcutInfo != null) {
return new AppTarget.Builder(new AppTargetId(id), shortcutInfo).build();
}
return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
.setClassName(cn.getClassName())
.build();
}
return null;
}
private AppTarget createTempFolderTarget() {
return new AppTarget.Builder(new AppTargetId("folder:" + SystemClock.uptimeMillis()),
mContext.getPackageName(), Process.myUserHandle())
.build();
}
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 PREDICTED_HOTSEAT_CONTAINER: {
return "predictions/hotseat";
}
case PREDICTION_CONTAINER: {
return "predictions";
}
case SHORTCUTS_CONTAINER: {
return "deep-shortcuts";
}
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";
}
case SEARCH_RESULT_CONTAINER:
return "search-results";
case EXTENDED_CONTAINERS: {
if (ci.getExtendedContainers().getContainerCase()
== DEVICE_SEARCH_RESULT_CONTAINER) {
return "search-results";
}
}
default: // fall out
}
return "";
}
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);
}
private static String getHotseatContainerString(HotseatContainer hc) {
return String.format(Locale.ENGLISH, "hotseat/%1$d/[%1$d,0]/[1,1]", hc.getIndex());
}
private static ComponentName parseNullable(String componentNameString) {
return TextUtils.isEmpty(componentNameString)
? null : ComponentName.unflattenFromString(componentNameString);
}
}