| /* |
| * 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.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) { |
| TaskView taskView = taskContainer.getTaskView(); |
| AppInfo.SplitAccessibilityInfo accessibilityInfo = |
| new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(), |
| TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()), |
| taskContainer.getA11yNodeId() |
| ); |
| return new AppInfo(activity, taskContainer.getItemInfo(), accessibilityInfo); |
| } |
| |
| @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 final SplitPositionOption mSplitPositionOption; |
| public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView, |
| SplitPositionOption option) { |
| super(option.iconResId, option.textResId, target, taskView.getItemInfo()); |
| mTaskView = taskView; |
| mSplitPositionOption = option; |
| } |
| |
| @Override |
| public void onClick(View view) { |
| mTaskView.initiateSplitSelect(mSplitPositionOption); |
| } |
| } |
| |
| class MultiWindowSystemShortcut extends SystemShortcut<BaseDraggingActivity> { |
| |
| 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); |
| } |
| } |
| } |
| |
| /** @Deprecated */ |
| 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 |
| 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<BaseDraggingActivity> { |
| |
| 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; |
| }; |
| } |