Files
Lawnchair/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
T
Vinit Nayak 7caf5f0ea4 Show distinct TaskMenus for each task in GroupedTaskView
* For now we only show the App Info option
for each task icon clicked for GroupedTaskView
* Have TaskMenuView operate on a specific task instead
of only a TaskView
* NOTE: getItemInfo() in TaskIdAttributeContainer needs to
dynamically call getItemInfo(Task) because at the time of
creation of the container in TaskView#bind(), the task
object provided initially is a palceholder task which only
has a taskId and no other attributes set.
getItemInfo() needs a non-null baseIntent, which the
placeholder task doesn't have.
Right fix for this is to have GestureState hold onto
multiple running tasks when in split screen that are
provided by ActivityManagerWrapper when the gesture starts
(but that change is extensive and out of scope w/ this
workaround available)

Bug: 181704764
Test: Open TaskMenuView's w/ either task icon for
GroupedTaskView, opens up corresponding tasks menu.

Change-Id: I70b7c13394ad4980cabbd611cb928bb03d8a2924
2021-09-21 17:47:54 -07:00

366 lines
16 KiB
Java

/*
* Copyright (C) 2018 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;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
import android.app.Activity;
import android.app.ActivityOptions;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.window.SplashScreen;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.Collections;
import java.util.List;
/**
* Represents a system shortcut that can be shown for a recent task.
*/
public interface TaskShortcutFactory {
SystemShortcut getShortcut(BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer);
default boolean showForSplitscreen() {
return false;
}
TaskShortcutFactory APP_INFO = new TaskShortcutFactory() {
@Override
public SystemShortcut getShortcut(BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer) {
return new AppInfo(activity, taskContainer.getItemInfo());
}
@Override
public boolean showForSplitscreen() {
return true;
}
};
abstract class MultiWindowFactory implements TaskShortcutFactory {
private final int mIconRes;
private final int mTextRes;
private final LauncherEvent mLauncherEvent;
MultiWindowFactory(int iconRes, int textRes, LauncherEvent launcherEvent) {
mIconRes = iconRes;
mTextRes = textRes;
mLauncherEvent = launcherEvent;
}
protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
protected abstract ActivityOptions makeLaunchOptions(Activity activity);
protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
@Override
public SystemShortcut getShortcut(BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer) {
final Task task = taskContainer.getTask();
if (!task.isDockable) {
return null;
}
if (!isAvailable(activity, task.key.displayId)) {
return null;
}
return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskContainer, this,
mLauncherEvent);
}
}
class SplitSelectSystemShortcut extends SystemShortcut {
private final TaskView mTaskView;
private SplitPositionOption mSplitPositionOption;
public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
SplitPositionOption option) {
super(option.iconResId, option.textResId, target, taskView.getItemInfo());
mTaskView = taskView;
mSplitPositionOption = option;
setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
}
@Override
public void onClick(View view) {
mTaskView.initiateSplitSelect(mSplitPositionOption);
}
}
class MultiWindowSystemShortcut extends SystemShortcut {
private Handler mHandler;
private final RecentsView mRecentsView;
private final TaskThumbnailView mThumbnailView;
private final TaskView mTaskView;
private final MultiWindowFactory mFactory;
private final LauncherEvent mLauncherEvent;
public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer, MultiWindowFactory factory,
LauncherEvent launcherEvent) {
super(iconRes, textRes, activity, taskContainer.getItemInfo());
mLauncherEvent = launcherEvent;
mHandler = new Handler(Looper.getMainLooper());
mTaskView = taskContainer.getTaskView();
mRecentsView = activity.getOverviewPanel();
mThumbnailView = taskContainer.getThumbnailView();
mFactory = factory;
}
@Override
public void onClick(View view) {
Task.TaskKey taskKey = mTaskView.getTask().key;
final int taskId = taskKey.id;
final View.OnLayoutChangeListener onLayoutChangeListener =
new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int l, int t, int r, int b,
int oldL, int oldT, int oldR, int oldB) {
mTaskView.getRootView().removeOnLayoutChangeListener(this);
mRecentsView.clearIgnoreResetTask(taskId);
// Start animating in the side pages once launcher has been resized
mRecentsView.dismissTask(mTaskView, false, false);
}
};
final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
new DeviceProfile.OnDeviceProfileChangeListener() {
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
mTarget.removeOnDeviceProfileChangeListener(this);
if (dp.isMultiWindowMode) {
mTaskView.getRootView().addOnLayoutChangeListener(
onLayoutChangeListener);
}
}
};
dismissTaskMenuView(mTarget);
ActivityOptions options = mFactory.makeLaunchOptions(mTarget);
if (options != null) {
options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
}
if (options != null
&& ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
options)) {
if (!mFactory.onActivityStarted(mTarget)) {
return;
}
// Add a device profile change listener to kick off animating the side tasks
// once we enter multiwindow mode and relayout
mTarget.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
final Runnable animStartedListener = () -> {
// Hide the task view and wait for the window to be resized
// TODO: Consider animating in launcher and do an in-place start activity
// afterwards
mRecentsView.setIgnoreResetTask(taskId);
mTaskView.setAlpha(0f);
};
final int[] position = new int[2];
mThumbnailView.getLocationOnScreen(position);
final int width = (int) (mThumbnailView.getWidth() * mTaskView.getScaleX());
final int height = (int) (mThumbnailView.getHeight() * mTaskView.getScaleY());
final Rect taskBounds = new Rect(position[0], position[1],
position[0] + width, position[1] + height);
// Take the thumbnail of the task without a scrim and apply it back after
float alpha = mThumbnailView.getDimAlpha();
mThumbnailView.setDimAlpha(0);
Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
taskBounds.width(), taskBounds.height(), mThumbnailView, 1f,
Color.BLACK);
mThumbnailView.setDimAlpha(alpha);
AppTransitionAnimationSpecsFuture future =
new AppTransitionAnimationSpecsFuture(mHandler) {
@Override
public List<AppTransitionAnimationSpecCompat> composeSpecs() {
return Collections.singletonList(new AppTransitionAnimationSpecCompat(
taskId, thumbnail, taskBounds));
}
};
WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
future, animStartedListener, mHandler, true /* scaleUp */,
taskKey.displayId);
mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
.log(mLauncherEvent);
}
}
}
TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen,
R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) {
@Override
protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
// Don't show menu-item if already in multi-window and the task is from
// the secondary display.
// TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
// implementation is enabled
return !activity.getDeviceProfile().isMultiWindowMode
&& (displayId == -1 || displayId == DEFAULT_DISPLAY);
}
@Override
public SystemShortcut getShortcut(BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer) {
SystemShortcut shortcut = super.getShortcut(activity, taskContainer);
if (shortcut != null && FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
// Disable if there's only one recent app for split screen
shortcut.setEnabled(taskContainer.getTaskView().
getRecentsView().getTaskViewCount() > 1);
}
return shortcut;
}
@Override
protected ActivityOptions makeLaunchOptions(Activity activity) {
final ActivityCompat act = new ActivityCompat(activity);
final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
act.getDisplayId());
if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
return null;
}
boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
}
@Override
protected boolean onActivityStarted(BaseDraggingActivity activity) {
return true;
}
};
TaskShortcutFactory FREE_FORM = new MultiWindowFactory(R.drawable.ic_split_screen,
R.string.recent_task_option_freeform, LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP) {
@Override
protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity);
}
@Override
protected ActivityOptions makeLaunchOptions(Activity activity) {
ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
// Arbitrary bounds only because freeform is in dev mode right now
Rect r = new Rect(50, 50, 200, 200);
activityOptions.setLaunchBounds(r);
return activityOptions;
}
@Override
protected boolean onActivityStarted(BaseDraggingActivity activity) {
activity.returnToHomescreen();
return true;
}
};
TaskShortcutFactory PIN = (activity, taskContainer) -> {
if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
return null;
}
if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
return null;
}
if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
// We shouldn't be able to pin while an app is locked.
return null;
}
return new PinSystemShortcut(activity, taskContainer);
};
class PinSystemShortcut extends SystemShortcut {
private static final String TAG = "PinSystemShortcut";
private final TaskView mTaskView;
public PinSystemShortcut(BaseDraggingActivity target,
TaskIdAttributeContainer taskContainer) {
super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
taskContainer.getItemInfo());
mTaskView = taskContainer.getTaskView();
}
@Override
public void onClick(View view) {
if (mTaskView.launchTaskAnimated() != null) {
SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(mTaskView.getTask().key.id);
}
dismissTaskMenuView(mTarget);
mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
.log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
}
}
TaskShortcutFactory INSTALL = (activity, taskContainer) ->
InstantAppResolver.newInstance(activity).isInstantApp(activity,
taskContainer.getTask().getTopComponent().getPackageName())
? new SystemShortcut.Install(activity, taskContainer.getItemInfo()) : null;
TaskShortcutFactory WELLBEING = (activity, taskContainer) ->
WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo());
TaskShortcutFactory SCREENSHOT = (activity, taskContainer) ->
taskContainer.getThumbnailView().getTaskOverlay()
.getScreenshotShortcut(activity, taskContainer.getItemInfo());
TaskShortcutFactory MODAL = (activity, taskContainer) -> {
if (ENABLE_OVERVIEW_SELECTIONS.get()) {
return taskContainer.getThumbnailView()
.getTaskOverlay().getModalStateSystemShortcut(taskContainer.getItemInfo());
}
return null;
};
}