Separating SystemShortcut and Factory
> This makes easier to extend the factory and callbacks separately > Availability checks are only performed once when getting the shortcut Change-Id: I413541eabfb2b9e987c852d5171c6696b1853958
This commit is contained in:
@@ -19,11 +19,11 @@ package com.android.quickstep;
|
||||
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.ResourceBasedOverride;
|
||||
import com.android.quickstep.views.TaskThumbnailView;
|
||||
@@ -40,25 +40,24 @@ import java.util.List;
|
||||
public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
|
||||
/** Note that these will be shown in order from top to bottom, if available for the task. */
|
||||
private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
|
||||
new TaskSystemShortcut.AppInfo(),
|
||||
new TaskSystemShortcut.SplitScreen(),
|
||||
new TaskSystemShortcut.Pin(),
|
||||
new TaskSystemShortcut.Install(),
|
||||
new TaskSystemShortcut.Freeform()
|
||||
private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
|
||||
TaskShortcutFactory.APP_INFO,
|
||||
TaskShortcutFactory.SPLIT_SCREEN,
|
||||
TaskShortcutFactory.PIN,
|
||||
TaskShortcutFactory.INSTALL,
|
||||
TaskShortcutFactory.FREE_FORM
|
||||
};
|
||||
|
||||
public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
|
||||
forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
|
||||
|
||||
public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
|
||||
final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
|
||||
public List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
|
||||
final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
|
||||
final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
|
||||
for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
|
||||
View.OnClickListener onClickListener =
|
||||
menuOption.getOnClickListener(activity, taskView);
|
||||
if (onClickListener != null) {
|
||||
shortcuts.add(menuOption);
|
||||
for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
|
||||
SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
|
||||
if (shortcut != null) {
|
||||
shortcuts.add(shortcut);
|
||||
}
|
||||
}
|
||||
return shortcuts;
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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.userevent.nano.LauncherLogProto.Action.Touch.TAP;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.WorkspaceItemInfo;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.popup.SystemShortcut.AppInfo;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.InstantAppResolver;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskThumbnailView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
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;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Represents a system shortcut that can be shown for a recent task.
|
||||
*/
|
||||
public interface TaskShortcutFactory {
|
||||
|
||||
SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
|
||||
|
||||
static WorkspaceItemInfo dummyInfo(TaskView view) {
|
||||
Task task = view.getTask();
|
||||
|
||||
WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
|
||||
dummyInfo.intent = new Intent();
|
||||
ComponentName component = task.getTopComponent();
|
||||
dummyInfo.intent.setComponent(component);
|
||||
dummyInfo.user = UserHandle.of(task.key.userId);
|
||||
dummyInfo.title = TaskUtils.getTitle(view.getContext(), task);
|
||||
return dummyInfo;
|
||||
}
|
||||
|
||||
TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, dummyInfo(view));
|
||||
|
||||
abstract class MultiWindowFactory implements TaskShortcutFactory {
|
||||
|
||||
private final int mIconRes;
|
||||
private final int mTextRes;
|
||||
|
||||
MultiWindowFactory(int iconRes, int textRes) {
|
||||
mIconRes = iconRes;
|
||||
mTextRes = textRes;
|
||||
}
|
||||
|
||||
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, TaskView taskView) {
|
||||
final Task task = taskView.getTask();
|
||||
if (!task.isDockable) {
|
||||
return null;
|
||||
}
|
||||
if (!isAvailable(activity, task.key.displayId)) {
|
||||
return null;
|
||||
}
|
||||
return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this);
|
||||
}
|
||||
}
|
||||
|
||||
class MultiWindowSystemShortcut extends SystemShortcut {
|
||||
|
||||
private Handler mHandler;
|
||||
|
||||
private final RecentsView mRecentsView;
|
||||
private final TaskThumbnailView mThumbnailView;
|
||||
private final TaskView mTaskView;
|
||||
private final MultiWindowFactory mFactory;
|
||||
|
||||
public MultiWindowSystemShortcut(int iconRes, int textRes,
|
||||
BaseDraggingActivity activity, TaskView taskView, MultiWindowFactory factory) {
|
||||
super(iconRes, textRes, activity, dummyInfo(taskView));
|
||||
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mTaskView = taskView;
|
||||
mRecentsView = activity.getOverviewPanel();
|
||||
mThumbnailView = taskView.getThumbnail();
|
||||
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
|
||||
&& 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(
|
||||
R.drawable.ic_split_screen, R.string.recent_task_option_split_screen) {
|
||||
|
||||
@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
|
||||
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) {
|
||||
SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked();
|
||||
activity.getUserEventDispatcher().logActionOnControl(TAP,
|
||||
LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TaskShortcutFactory FREE_FORM = new MultiWindowFactory(
|
||||
R.drawable.ic_split_screen, R.string.recent_task_option_freeform) {
|
||||
|
||||
@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, tv) -> {
|
||||
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, tv);
|
||||
};
|
||||
|
||||
class PinSystemShortcut extends SystemShortcut {
|
||||
|
||||
private static final String TAG = "PinSystemShortcut";
|
||||
|
||||
private final TaskView mTaskView;
|
||||
|
||||
public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) {
|
||||
super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, dummyInfo(tv));
|
||||
mTaskView = tv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Consumer<Boolean> resultCallback = success -> {
|
||||
if (success) {
|
||||
SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(
|
||||
mTaskView.getTask().key.id);
|
||||
} else {
|
||||
mTaskView.notifyTaskLaunchFailed(TAG);
|
||||
}
|
||||
};
|
||||
mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler());
|
||||
dismissTaskMenuView(mTarget);
|
||||
}
|
||||
}
|
||||
|
||||
TaskShortcutFactory INSTALL = (activity, view) ->
|
||||
InstantAppResolver.newInstance(activity).isInstantApp(activity,
|
||||
view.getTask().getTopComponent().getPackageName())
|
||||
? new SystemShortcut.Install(activity, dummyInfo(view)) : null;
|
||||
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
/*
|
||||
* 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.userevent.nano.LauncherLogProto.Action.Touch.TAP;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.WorkspaceItemInfo;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.launcher3.util.InstantAppResolver;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskThumbnailView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
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;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Represents a system shortcut that can be shown for a recent task.
|
||||
*/
|
||||
public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut {
|
||||
|
||||
private static final String TAG = "TaskSystemShortcut";
|
||||
|
||||
protected T mSystemShortcut;
|
||||
|
||||
public TaskSystemShortcut(T systemShortcut) {
|
||||
super(systemShortcut);
|
||||
mSystemShortcut = systemShortcut;
|
||||
}
|
||||
|
||||
protected TaskSystemShortcut(int iconResId, int labelResId) {
|
||||
super(iconResId, labelResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getOnClickListener(
|
||||
BaseDraggingActivity activity, ItemInfo itemInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) {
|
||||
Task task = view.getTask();
|
||||
|
||||
WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
|
||||
dummyInfo.intent = new Intent();
|
||||
ComponentName component = task.getTopComponent();
|
||||
dummyInfo.intent.setComponent(component);
|
||||
dummyInfo.user = UserHandle.of(task.key.userId);
|
||||
dummyInfo.title = TaskUtils.getTitle(activity, task);
|
||||
|
||||
return getOnClickListenerForTask(activity, task, dummyInfo);
|
||||
}
|
||||
|
||||
protected View.OnClickListener getOnClickListenerForTask(
|
||||
BaseDraggingActivity activity, Task task, ItemInfo dummyInfo) {
|
||||
return mSystemShortcut.getOnClickListener(activity, dummyInfo);
|
||||
}
|
||||
|
||||
public static class AppInfo extends TaskSystemShortcut<SystemShortcut.AppInfo> {
|
||||
public AppInfo() {
|
||||
super(new SystemShortcut.AppInfo());
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class MultiWindow extends TaskSystemShortcut {
|
||||
|
||||
private Handler mHandler;
|
||||
|
||||
public MultiWindow(int iconRes, int textRes) {
|
||||
super(iconRes, textRes);
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
|
||||
protected abstract ActivityOptions makeLaunchOptions(Activity activity);
|
||||
protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getOnClickListener(
|
||||
BaseDraggingActivity activity, TaskView taskView) {
|
||||
final Task task = taskView.getTask();
|
||||
final int taskId = task.key.id;
|
||||
final int displayId = task.key.displayId;
|
||||
if (!task.isDockable) {
|
||||
return null;
|
||||
}
|
||||
if (!isAvailable(activity, displayId)) {
|
||||
return null;
|
||||
}
|
||||
final RecentsView recentsView = activity.getOverviewPanel();
|
||||
|
||||
final TaskThumbnailView thumbnailView = taskView.getThumbnail();
|
||||
return (v -> {
|
||||
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) {
|
||||
taskView.getRootView().removeOnLayoutChangeListener(this);
|
||||
recentsView.clearIgnoreResetTask(taskId);
|
||||
|
||||
// Start animating in the side pages once launcher has been resized
|
||||
recentsView.dismissTask(taskView, false, false);
|
||||
}
|
||||
};
|
||||
|
||||
final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
|
||||
new DeviceProfile.OnDeviceProfileChangeListener() {
|
||||
@Override
|
||||
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||
activity.removeOnDeviceProfileChangeListener(this);
|
||||
if (dp.isMultiWindowMode) {
|
||||
taskView.getRootView().addOnLayoutChangeListener(
|
||||
onLayoutChangeListener);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
dismissTaskMenuView(activity);
|
||||
|
||||
ActivityOptions options = makeLaunchOptions(activity);
|
||||
if (options != null
|
||||
&& ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
|
||||
options)) {
|
||||
if (!onActivityStarted(activity)) {
|
||||
return;
|
||||
}
|
||||
// Add a device profile change listener to kick off animating the side tasks
|
||||
// once we enter multiwindow mode and relayout
|
||||
activity.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
|
||||
recentsView.setIgnoreResetTask(taskId);
|
||||
taskView.setAlpha(0f);
|
||||
};
|
||||
|
||||
final int[] position = new int[2];
|
||||
thumbnailView.getLocationOnScreen(position);
|
||||
final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX());
|
||||
final int height = (int) (thumbnailView.getHeight() * taskView.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 = thumbnailView.getDimAlpha();
|
||||
thumbnailView.setDimAlpha(0);
|
||||
Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
|
||||
taskBounds.width(), taskBounds.height(), thumbnailView, 1f,
|
||||
Color.BLACK);
|
||||
thumbnailView.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 */, displayId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class SplitScreen extends MultiWindow {
|
||||
public SplitScreen() {
|
||||
super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
|
||||
}
|
||||
|
||||
@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
|
||||
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) {
|
||||
SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked();
|
||||
activity.getUserEventDispatcher().logActionOnControl(TAP,
|
||||
LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Freeform extends MultiWindow {
|
||||
public Freeform() {
|
||||
super(R.drawable.ic_split_screen, R.string.recent_task_option_freeform);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Pin extends TaskSystemShortcut {
|
||||
|
||||
private static final String TAG = Pin.class.getSimpleName();
|
||||
|
||||
private Handler mHandler;
|
||||
|
||||
public Pin() {
|
||||
super(R.drawable.ic_pin, R.string.recent_task_option_pin);
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getOnClickListener(
|
||||
BaseDraggingActivity activity, TaskView taskView) {
|
||||
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 view -> {
|
||||
Consumer<Boolean> resultCallback = success -> {
|
||||
if (success) {
|
||||
SystemUiProxy.INSTANCE.get(activity).startScreenPinning(
|
||||
taskView.getTask().key.id);
|
||||
} else {
|
||||
taskView.notifyTaskLaunchFailed(TAG);
|
||||
}
|
||||
};
|
||||
taskView.launchTask(true, resultCallback, mHandler);
|
||||
dismissTaskMenuView(activity);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class Install extends TaskSystemShortcut<SystemShortcut.Install> {
|
||||
public Install() {
|
||||
super(new SystemShortcut.Install());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View.OnClickListener getOnClickListenerForTask(
|
||||
BaseDraggingActivity activity, Task task, ItemInfo itemInfo) {
|
||||
if (InstantAppResolver.newInstance(activity).isInstantApp(activity,
|
||||
task.getTopComponent().getPackageName())) {
|
||||
return mSystemShortcut.createOnClickListener(activity, itemInfo);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.quickstep.views;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
||||
import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
|
||||
|
||||
import android.animation.Animator;
|
||||
@@ -26,7 +25,6 @@ import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
@@ -41,16 +39,13 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.quickstep.TaskOverlayFactory;
|
||||
import com.android.quickstep.TaskSystemShortcut;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.views.IconView.OnScaleUpdateListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Contains options for a recent task when long-pressing its icon.
|
||||
*/
|
||||
@@ -197,22 +192,16 @@ public class TaskMenuView extends AbstractFloatingView {
|
||||
params.topMargin = (int) -mThumbnailTopMargin;
|
||||
mTaskIcon.setLayoutParams(params);
|
||||
|
||||
final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext());
|
||||
final List<TaskSystemShortcut> shortcuts =
|
||||
TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(taskView);
|
||||
final int count = shortcuts.size();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
final TaskSystemShortcut menuOption = shortcuts.get(i);
|
||||
addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView));
|
||||
}
|
||||
TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(taskView)
|
||||
.forEach(this::addMenuOption);
|
||||
}
|
||||
|
||||
private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
|
||||
private void addMenuOption(SystemShortcut menuOption) {
|
||||
ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate(
|
||||
R.layout.task_view_menu_option, this, false);
|
||||
menuOption.setIconAndLabelFor(
|
||||
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
|
||||
menuOptionView.setOnClickListener(onClickListener);
|
||||
menuOptionView.setOnClickListener(menuOption);
|
||||
mOptionLayout.addView(menuOptionView);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.logging.UserEventDispatcher;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
|
||||
@@ -61,7 +62,6 @@ import com.android.launcher3.util.ViewPool.Reusable;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.TaskIconCache;
|
||||
import com.android.quickstep.TaskOverlayFactory;
|
||||
import com.android.quickstep.TaskSystemShortcut;
|
||||
import com.android.quickstep.TaskThumbnailCache;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.util.TaskCornerRadius;
|
||||
@@ -713,15 +713,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
getContext().getText(R.string.accessibility_close_task)));
|
||||
|
||||
final Context context = getContext();
|
||||
final List<TaskSystemShortcut> shortcuts =
|
||||
TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this);
|
||||
final int count = shortcuts.size();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
final TaskSystemShortcut menuOption = shortcuts.get(i);
|
||||
OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this);
|
||||
if (onClickListener != null) {
|
||||
info.addAction(menuOption.createAccessibilityAction(context));
|
||||
}
|
||||
for (SystemShortcut s : TaskOverlayFactory.INSTANCE.get(getContext())
|
||||
.getEnabledShortcuts(this)) {
|
||||
info.addAction(s.createAccessibilityAction(context));
|
||||
}
|
||||
|
||||
if (mDigitalWellBeingToast.hasLimit()) {
|
||||
@@ -752,16 +746,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
return true;
|
||||
}
|
||||
|
||||
final List<TaskSystemShortcut> shortcuts =
|
||||
TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this);
|
||||
final int count = shortcuts.size();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
final TaskSystemShortcut menuOption = shortcuts.get(i);
|
||||
if (menuOption.hasHandlerForAction(action)) {
|
||||
OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this);
|
||||
if (onClickListener != null) {
|
||||
onClickListener.onClick(this);
|
||||
}
|
||||
for (SystemShortcut s : TaskOverlayFactory.INSTANCE.get(getContext())
|
||||
.getEnabledShortcuts(this)) {
|
||||
if (s.hasHandlerForAction(action)) {
|
||||
s.onClick(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,8 +382,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
|
||||
@Override
|
||||
public void onWidgetsBound() {
|
||||
ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
|
||||
SystemShortcut widgetInfo = new SystemShortcut.Widgets();
|
||||
View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
|
||||
SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
|
||||
View widgetsView = null;
|
||||
int count = mSystemShortcutContainer.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -394,7 +393,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
|
||||
}
|
||||
}
|
||||
|
||||
if (onClickListener != null && widgetsView == null) {
|
||||
if (widgetInfo != null && widgetsView == null) {
|
||||
// We didn't have any widgets cached but now there are some, so enable the shortcut.
|
||||
if (mSystemShortcutContainer != this) {
|
||||
initializeSystemShortcut(
|
||||
@@ -407,7 +406,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
|
||||
close(false);
|
||||
PopupContainerWithArrow.showForIcon(mOriginalIcon);
|
||||
}
|
||||
} else if (onClickListener == null && widgetsView != null) {
|
||||
} else if (widgetInfo == null && widgetsView != null) {
|
||||
// No widgets exist, but we previously added the shortcut so remove it.
|
||||
if (mSystemShortcutContainer != this) {
|
||||
mSystemShortcutContainer.removeView(widgetsView);
|
||||
@@ -430,8 +429,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
|
||||
info.setIconAndContentDescriptionFor((ImageView) view);
|
||||
}
|
||||
view.setTag(info);
|
||||
view.setOnClickListener(info.getOnClickListener(mLauncher,
|
||||
(ItemInfo) mOriginalIcon.getTag()));
|
||||
view.setOnClickListener(info);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,13 +16,19 @@
|
||||
|
||||
package com.android.launcher3.popup;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteAction;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
@@ -32,55 +38,75 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.Q)
|
||||
public class RemoteActionShortcut extends SystemShortcut<BaseDraggingActivity> {
|
||||
private static final String TAG = "RemoteActionShortcut";
|
||||
private static final boolean DEBUG = Utilities.IS_DEBUG_DEVICE;
|
||||
|
||||
private final RemoteAction mAction;
|
||||
|
||||
public RemoteActionShortcut(RemoteAction action) {
|
||||
super(action.getIcon(), action.getTitle(), action.getContentDescription(),
|
||||
R.id.action_remote_action_shortcut);
|
||||
public RemoteActionShortcut(RemoteAction action,
|
||||
BaseDraggingActivity activity, ItemInfo itemInfo) {
|
||||
super(0, R.id.action_remote_action_shortcut, activity, itemInfo);
|
||||
mAction = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getOnClickListener(
|
||||
final BaseDraggingActivity activity, final ItemInfo itemInfo) {
|
||||
return view -> {
|
||||
AbstractFloatingView.closeAllOpenViews(activity);
|
||||
public void setIconAndLabelFor(View iconView, TextView labelView) {
|
||||
mAction.getIcon().loadDrawableAsync(iconView.getContext(),
|
||||
iconView::setBackground,
|
||||
MAIN_EXECUTOR.getHandler());
|
||||
labelView.setText(mAction.getTitle());
|
||||
}
|
||||
|
||||
final String actionIdentity = mAction.getTitle() + ", " +
|
||||
itemInfo.getTargetComponent().getPackageName();
|
||||
try {
|
||||
if (DEBUG) Log.d(TAG, "Sending action: " + actionIdentity);
|
||||
mAction.getActionIntent().send(
|
||||
activity,
|
||||
0,
|
||||
new Intent().putExtra(
|
||||
Intent.EXTRA_PACKAGE_NAME,
|
||||
itemInfo.getTargetComponent().getPackageName()),
|
||||
(pendingIntent, intent, resultCode, resultData, resultExtras) -> {
|
||||
if (DEBUG) Log.d(TAG, "Action is complete: " + actionIdentity);
|
||||
if (resultData != null && !resultData.isEmpty()) {
|
||||
Log.e(TAG, "Remote action returned result: " + actionIdentity
|
||||
+ " : " + resultData);
|
||||
Toast.makeText(activity, resultData, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
},
|
||||
new Handler(Looper.getMainLooper()));
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "Remote action canceled: " + actionIdentity, e);
|
||||
Toast.makeText(activity, activity.getString(
|
||||
R.string.remote_action_failed,
|
||||
mAction.getTitle()),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
@Override
|
||||
public void setIconAndContentDescriptionFor(ImageView view) {
|
||||
mAction.getIcon().loadDrawableAsync(view.getContext(),
|
||||
view::setImageDrawable,
|
||||
MAIN_EXECUTOR.getHandler());
|
||||
view.setContentDescription(mAction.getContentDescription());
|
||||
}
|
||||
|
||||
activity.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
|
||||
LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
|
||||
};
|
||||
@Override
|
||||
public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) {
|
||||
return new AccessibilityNodeInfo.AccessibilityAction(
|
||||
R.id.action_remote_action_shortcut, mAction.getContentDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
AbstractFloatingView.closeAllOpenViews(mTarget);
|
||||
|
||||
final String actionIdentity = mAction.getTitle() + ", "
|
||||
+ mItemInfo.getTargetComponent().getPackageName();
|
||||
try {
|
||||
if (DEBUG) Log.d(TAG, "Sending action: " + actionIdentity);
|
||||
mAction.getActionIntent().send(
|
||||
mTarget,
|
||||
0,
|
||||
new Intent().putExtra(
|
||||
Intent.EXTRA_PACKAGE_NAME,
|
||||
mItemInfo.getTargetComponent().getPackageName()),
|
||||
(pendingIntent, intent, resultCode, resultData, resultExtras) -> {
|
||||
if (DEBUG) Log.d(TAG, "Action is complete: " + actionIdentity);
|
||||
if (resultData != null && !resultData.isEmpty()) {
|
||||
Log.e(TAG, "Remote action returned result: " + actionIdentity
|
||||
+ " : " + resultData);
|
||||
Toast.makeText(mTarget, resultData, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
},
|
||||
MAIN_EXECUTOR.getHandler());
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "Remote action canceled: " + actionIdentity, e);
|
||||
Toast.makeText(mTarget, mTarget.getString(
|
||||
R.string.remote_action_failed,
|
||||
mAction.getTitle()),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
|
||||
mTarget.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
|
||||
LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,14 +5,13 @@ import android.app.ActivityOptions;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
@@ -39,41 +38,30 @@ import java.util.List;
|
||||
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class SystemShortcut<T extends BaseDraggingActivity>
|
||||
extends ItemInfo {
|
||||
public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo
|
||||
implements View.OnClickListener {
|
||||
|
||||
private final int mIconResId;
|
||||
private final int mLabelResId;
|
||||
private final Icon mIcon;
|
||||
private final CharSequence mLabel;
|
||||
private final CharSequence mContentDescription;
|
||||
private final int mAccessibilityActionId;
|
||||
|
||||
public SystemShortcut(int iconResId, int labelResId) {
|
||||
protected final T mTarget;
|
||||
protected final ItemInfo mItemInfo;
|
||||
|
||||
public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
|
||||
mIconResId = iconResId;
|
||||
mLabelResId = labelResId;
|
||||
mAccessibilityActionId = labelResId;
|
||||
mIcon = null;
|
||||
mLabel = null;
|
||||
mContentDescription = null;
|
||||
mTarget = target;
|
||||
mItemInfo = itemInfo;
|
||||
}
|
||||
|
||||
public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription,
|
||||
int accessibilityActionId) {
|
||||
mIcon = icon;
|
||||
mLabel = label;
|
||||
mContentDescription = contentDescription;
|
||||
mAccessibilityActionId = accessibilityActionId;
|
||||
mIconResId = 0;
|
||||
mLabelResId = 0;
|
||||
}
|
||||
|
||||
public SystemShortcut(SystemShortcut other) {
|
||||
public SystemShortcut(SystemShortcut<T> other) {
|
||||
mIconResId = other.mIconResId;
|
||||
mLabelResId = other.mLabelResId;
|
||||
mIcon = other.mIcon;
|
||||
mLabel = other.mLabel;
|
||||
mContentDescription = other.mContentDescription;
|
||||
mAccessibilityActionId = other.mAccessibilityActionId;
|
||||
mTarget = other.mTarget;
|
||||
mItemInfo = other.mItemInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,150 +72,135 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity>
|
||||
}
|
||||
|
||||
public void setIconAndLabelFor(View iconView, TextView labelView) {
|
||||
if (mIcon != null) {
|
||||
mIcon.loadDrawableAsync(iconView.getContext(),
|
||||
iconView::setBackground,
|
||||
new Handler(Looper.getMainLooper()));
|
||||
} else {
|
||||
iconView.setBackgroundResource(mIconResId);
|
||||
}
|
||||
|
||||
if (mLabel != null) {
|
||||
labelView.setText(mLabel);
|
||||
} else {
|
||||
labelView.setText(mLabelResId);
|
||||
}
|
||||
iconView.setBackgroundResource(mIconResId);
|
||||
labelView.setText(mLabelResId);
|
||||
}
|
||||
|
||||
public void setIconAndContentDescriptionFor(ImageView view) {
|
||||
if (mIcon != null) {
|
||||
mIcon.loadDrawableAsync(view.getContext(),
|
||||
view::setImageDrawable,
|
||||
new Handler(Looper.getMainLooper()));
|
||||
} else {
|
||||
view.setImageResource(mIconResId);
|
||||
}
|
||||
|
||||
view.setContentDescription(getContentDescription(view.getContext()));
|
||||
}
|
||||
|
||||
private CharSequence getContentDescription(Context context) {
|
||||
return mContentDescription != null ? mContentDescription : context.getText(mLabelResId);
|
||||
view.setImageResource(mIconResId);
|
||||
view.setContentDescription(view.getContext().getText(mLabelResId));
|
||||
}
|
||||
|
||||
public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) {
|
||||
return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId,
|
||||
getContentDescription(context));
|
||||
return new AccessibilityNodeInfo.AccessibilityAction(
|
||||
mAccessibilityActionId, context.getText(mLabelResId));
|
||||
}
|
||||
|
||||
public boolean hasHandlerForAction(int action) {
|
||||
return mAccessibilityActionId == action;
|
||||
}
|
||||
|
||||
public abstract View.OnClickListener getOnClickListener(T activity, ItemInfo itemInfo);
|
||||
public interface Factory<T extends BaseDraggingActivity> {
|
||||
|
||||
@Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo);
|
||||
}
|
||||
|
||||
public static final Factory<Launcher> WIDGETS = (launcher, itemInfo) -> {
|
||||
if (itemInfo.getTargetComponent() == null) return null;
|
||||
final List<WidgetItem> widgets =
|
||||
launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
|
||||
itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
|
||||
if (widgets == null) {
|
||||
return null;
|
||||
}
|
||||
return new Widgets(launcher, itemInfo);
|
||||
};
|
||||
|
||||
public static class Widgets extends SystemShortcut<Launcher> {
|
||||
|
||||
public Widgets() {
|
||||
super(R.drawable.ic_widget, R.string.widget_button_text);
|
||||
public Widgets(Launcher target, ItemInfo itemInfo) {
|
||||
super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getOnClickListener(final Launcher launcher,
|
||||
final ItemInfo itemInfo) {
|
||||
if (itemInfo.getTargetComponent() == null) return null;
|
||||
final List<WidgetItem> widgets =
|
||||
launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
|
||||
itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
|
||||
if (widgets == null) {
|
||||
return null;
|
||||
}
|
||||
return (view) -> {
|
||||
AbstractFloatingView.closeAllOpenViews(launcher);
|
||||
WidgetsBottomSheet widgetsBottomSheet =
|
||||
(WidgetsBottomSheet) launcher.getLayoutInflater().inflate(
|
||||
R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false);
|
||||
widgetsBottomSheet.populateAndShow(itemInfo);
|
||||
launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
|
||||
ControlType.WIDGETS_BUTTON, view);
|
||||
};
|
||||
public void onClick(View view) {
|
||||
AbstractFloatingView.closeAllOpenViews(mTarget);
|
||||
WidgetsBottomSheet widgetsBottomSheet =
|
||||
(WidgetsBottomSheet) mTarget.getLayoutInflater().inflate(
|
||||
R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false);
|
||||
widgetsBottomSheet.populateAndShow(mItemInfo);
|
||||
mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
|
||||
ControlType.WIDGETS_BUTTON, view);
|
||||
}
|
||||
}
|
||||
|
||||
public static final Factory<BaseDraggingActivity> APP_INFO = AppInfo::new;
|
||||
|
||||
public static class AppInfo extends SystemShortcut {
|
||||
public AppInfo() {
|
||||
super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label);
|
||||
|
||||
public AppInfo(BaseDraggingActivity target, ItemInfo itemInfo) {
|
||||
super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
|
||||
itemInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getOnClickListener(
|
||||
BaseDraggingActivity activity, ItemInfo itemInfo) {
|
||||
return (view) -> {
|
||||
dismissTaskMenuView(activity);
|
||||
Rect sourceBounds = activity.getViewBounds(view);
|
||||
new PackageManagerHelper(activity).startDetailsActivityForInfo(
|
||||
itemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
|
||||
activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
|
||||
ControlType.APPINFO_TARGET, view);
|
||||
};
|
||||
public void onClick(View view) {
|
||||
dismissTaskMenuView(mTarget);
|
||||
Rect sourceBounds = mTarget.getViewBounds(view);
|
||||
new PackageManagerHelper(mTarget).startDetailsActivityForInfo(
|
||||
mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
|
||||
mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
|
||||
ControlType.APPINFO_TARGET, view);
|
||||
}
|
||||
}
|
||||
|
||||
public static Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> {
|
||||
boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo)
|
||||
&& ((WorkspaceItemInfo) itemInfo).hasStatusFlag(
|
||||
WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI);
|
||||
boolean isInstantApp = false;
|
||||
if (itemInfo instanceof com.android.launcher3.AppInfo) {
|
||||
com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo;
|
||||
isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
|
||||
}
|
||||
boolean enabled = supportsWebUI || isInstantApp;
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
return new Install(activity, itemInfo);
|
||||
};
|
||||
|
||||
public static class Install extends SystemShortcut {
|
||||
public Install() {
|
||||
super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label);
|
||||
|
||||
public Install(BaseDraggingActivity target, ItemInfo itemInfo) {
|
||||
super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label,
|
||||
target, itemInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getOnClickListener(
|
||||
BaseDraggingActivity activity, ItemInfo itemInfo) {
|
||||
boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo) &&
|
||||
((WorkspaceItemInfo) itemInfo).hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI);
|
||||
boolean isInstantApp = false;
|
||||
if (itemInfo instanceof com.android.launcher3.AppInfo) {
|
||||
com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo;
|
||||
isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
|
||||
}
|
||||
boolean enabled = supportsWebUI || isInstantApp;
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
return createOnClickListener(activity, itemInfo);
|
||||
}
|
||||
|
||||
public View.OnClickListener createOnClickListener(
|
||||
BaseDraggingActivity activity, ItemInfo itemInfo) {
|
||||
return view -> {
|
||||
Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
|
||||
itemInfo.getTargetComponent().getPackageName());
|
||||
activity.startActivitySafely(view, intent, itemInfo, null);
|
||||
AbstractFloatingView.closeAllOpenViews(activity);
|
||||
};
|
||||
public void onClick(View view) {
|
||||
Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
|
||||
mItemInfo.getTargetComponent().getPackageName());
|
||||
mTarget.startActivitySafely(view, intent, mItemInfo, null);
|
||||
AbstractFloatingView.closeAllOpenViews(mTarget);
|
||||
}
|
||||
}
|
||||
|
||||
public static Factory<Launcher> DISMISS_PREDICTION = (launcher, itemInfo) -> {
|
||||
if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
|
||||
if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null;
|
||||
return new DismissPrediction(launcher, itemInfo);
|
||||
};
|
||||
|
||||
public static class DismissPrediction extends SystemShortcut<Launcher> {
|
||||
public DismissPrediction() {
|
||||
super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label);
|
||||
public DismissPrediction(Launcher launcher, ItemInfo itemInfo) {
|
||||
super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label, launcher,
|
||||
itemInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getOnClickListener(Launcher activity, ItemInfo itemInfo) {
|
||||
if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
|
||||
if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null;
|
||||
return (view) -> {
|
||||
PopupContainerWithArrow.closeAllOpenViews(activity);
|
||||
activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
|
||||
ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS);
|
||||
AppLaunchTracker.INSTANCE.get(view.getContext())
|
||||
.onDismissApp(itemInfo.getTargetComponent(),
|
||||
itemInfo.user,
|
||||
AppLaunchTracker.CONTAINER_PREDICTIONS);
|
||||
};
|
||||
public void onClick(View view) {
|
||||
PopupContainerWithArrow.closeAllOpenViews(mTarget);
|
||||
mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
|
||||
ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS);
|
||||
AppLaunchTracker.INSTANCE.get(view.getContext()).onDismissApp(
|
||||
mItemInfo.getTargetComponent(),
|
||||
mItemInfo.user,
|
||||
AppLaunchTracker.CONTAINER_PREDICTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void dismissTaskMenuView(BaseDraggingActivity activity) {
|
||||
public static void dismissTaskMenuView(BaseDraggingActivity activity) {
|
||||
AbstractFloatingView.closeOpenViews(activity, true,
|
||||
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
|
||||
}
|
||||
|
||||
@@ -34,25 +34,24 @@ public class SystemShortcutFactory implements ResourceBasedOverride {
|
||||
forOverride(SystemShortcutFactory.class, R.string.system_shortcut_factory_class);
|
||||
|
||||
/** Note that these are in order of priority. */
|
||||
private final SystemShortcut[] mAllShortcuts;
|
||||
private final SystemShortcut.Factory[] mAllFactories;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public SystemShortcutFactory() {
|
||||
this(new SystemShortcut.AppInfo(),
|
||||
new SystemShortcut.Widgets(),
|
||||
new SystemShortcut.Install(),
|
||||
new SystemShortcut.DismissPrediction());
|
||||
this(SystemShortcut.APP_INFO, SystemShortcut.WIDGETS, SystemShortcut.INSTALL,
|
||||
SystemShortcut.DISMISS_PREDICTION);
|
||||
}
|
||||
|
||||
protected SystemShortcutFactory(SystemShortcut... shortcuts) {
|
||||
mAllShortcuts = shortcuts;
|
||||
protected SystemShortcutFactory(SystemShortcut.Factory... factories) {
|
||||
mAllFactories = factories;
|
||||
}
|
||||
|
||||
public @NonNull List<SystemShortcut> getEnabledShortcuts(Launcher launcher, ItemInfo info) {
|
||||
List<SystemShortcut> systemShortcuts = new ArrayList<>();
|
||||
for (SystemShortcut systemShortcut : mAllShortcuts) {
|
||||
if (systemShortcut.getOnClickListener(launcher, info) != null) {
|
||||
systemShortcuts.add(systemShortcut);
|
||||
for (SystemShortcut.Factory factory : mAllFactories) {
|
||||
SystemShortcut shortcut = factory.getShortcut(launcher, info);
|
||||
if (shortcut != null) {
|
||||
systemShortcuts.add(shortcut);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user