1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.quickstep.util; 18 19 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE; 20 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS; 21 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.app.PendingIntent; 28 import android.content.Intent; 29 import android.graphics.Bitmap; 30 import android.graphics.Canvas; 31 import android.graphics.Rect; 32 import android.graphics.RectF; 33 import android.graphics.drawable.Drawable; 34 import android.os.UserHandle; 35 import android.view.View; 36 37 import com.android.launcher3.DeviceProfile; 38 import com.android.launcher3.Launcher; 39 import com.android.launcher3.R; 40 import com.android.launcher3.anim.PendingAnimation; 41 import com.android.launcher3.icons.BitmapInfo; 42 import com.android.launcher3.model.data.WorkspaceItemInfo; 43 import com.android.quickstep.views.FloatingTaskView; 44 import com.android.quickstep.views.RecentsView; 45 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 46 47 /** Handles when the stage split lands on the home screen. */ 48 public class SplitToWorkspaceController { 49 50 private final Launcher mLauncher; 51 private final DeviceProfile mDP; 52 private final SplitSelectStateController mController; 53 54 private final int mHalfDividerSize; 55 SplitToWorkspaceController(Launcher launcher, SplitSelectStateController controller)56 public SplitToWorkspaceController(Launcher launcher, SplitSelectStateController controller) { 57 mLauncher = launcher; 58 mDP = mLauncher.getDeviceProfile(); 59 mController = controller; 60 61 mHalfDividerSize = mLauncher.getResources().getDimensionPixelSize( 62 R.dimen.multi_window_task_divider_size) / 2; 63 } 64 65 /** 66 * Handles widget selection from staged split. 67 * @param view Original widget view 68 * @param pendingIntent Provided by widget via InteractionHandler 69 * @return {@code true} if we can attempt launch the widget into split, {@code false} otherwise 70 * to allow launcher to handle the click 71 */ handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent)72 public boolean handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent) { 73 if (shouldIgnoreSecondSplitLaunch()) { 74 return false; 75 } 76 77 // Convert original widgetView into bitmap to use for animation 78 // TODO(b/276361926) get the icon for this widget via PackageManager? 79 int width = view.getWidth(); 80 int height = view.getHeight(); 81 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 82 Canvas canvas = new Canvas(bitmap); 83 view.draw(canvas); 84 85 mController.setSecondTask(pendingIntent); 86 87 startWorkspaceAnimation(view, bitmap, null /*icon*/); 88 return true; 89 } 90 91 /** 92 * Handles second app selection from stage split. If the item can't be opened in split or 93 * it's not in stage split state, we pass it onto Launcher's default item click handler. 94 */ handleSecondAppSelectionForSplit(View view)95 public boolean handleSecondAppSelectionForSplit(View view) { 96 if (shouldIgnoreSecondSplitLaunch()) { 97 return false; 98 } 99 Object tag = view.getTag(); 100 Intent intent; 101 UserHandle user; 102 BitmapInfo bitmapInfo; 103 if (tag instanceof WorkspaceItemInfo) { 104 final WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) tag; 105 intent = workspaceItemInfo.intent; 106 user = workspaceItemInfo.user; 107 bitmapInfo = workspaceItemInfo.bitmap; 108 } else if (tag instanceof com.android.launcher3.model.data.AppInfo) { 109 final com.android.launcher3.model.data.AppInfo appInfo = 110 (com.android.launcher3.model.data.AppInfo) tag; 111 intent = appInfo.intent; 112 user = appInfo.user; 113 bitmapInfo = appInfo.bitmap; 114 } else { 115 return false; 116 } 117 118 mController.setSecondTask(intent, user); 119 120 startWorkspaceAnimation(view, null /*bitmap*/, bitmapInfo.newIcon(mLauncher)); 121 return true; 122 } 123 startWorkspaceAnimation(@onNull View view, @Nullable Bitmap bitmap, @Nullable Drawable icon)124 private void startWorkspaceAnimation(@NonNull View view, @Nullable Bitmap bitmap, 125 @Nullable Drawable icon) { 126 boolean isTablet = mLauncher.getDeviceProfile().isTablet; 127 SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet); 128 PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration()); 129 130 Rect firstTaskStartingBounds = new Rect(); 131 Rect firstTaskEndingBounds = new Rect(); 132 RectF secondTaskStartingBounds = new RectF(); 133 Rect secondTaskEndingBounds = new Rect(); 134 135 RecentsView recentsView = mLauncher.getOverviewPanel(); 136 recentsView.getPagedOrientationHandler().getFinalSplitPlaceholderBounds(mHalfDividerSize, 137 mDP, mController.getActiveSplitStagePosition(), firstTaskEndingBounds, 138 secondTaskEndingBounds); 139 140 FloatingTaskView firstFloatingTaskView = mController.getFirstFloatingTaskView(); 141 firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds); 142 firstFloatingTaskView.addConfirmAnimation(pendingAnimation, 143 new RectF(firstTaskStartingBounds), firstTaskEndingBounds, 144 false /* fadeWithThumbnail */, true /* isStagedTask */); 145 146 FloatingTaskView secondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mLauncher, 147 view, bitmap, icon, secondTaskStartingBounds); 148 secondFloatingTaskView.setAlpha(1); 149 secondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds, 150 secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */); 151 152 pendingAnimation.addListener(new AnimatorListenerAdapter() { 153 private boolean mIsCancelled = false; 154 155 @Override 156 public void onAnimationCancel(Animator animation) { 157 mIsCancelled = true; 158 cleanUp(); 159 } 160 161 @Override 162 public void onAnimationEnd(Animator animation) { 163 if (!mIsCancelled) { 164 mController.launchSplitTasks(aBoolean -> cleanUp()); 165 InteractionJankMonitorWrapper.end( 166 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER); 167 } 168 } 169 170 private void cleanUp() { 171 mLauncher.getDragLayer().removeView(firstFloatingTaskView); 172 mLauncher.getDragLayer().removeView(secondFloatingTaskView); 173 mController.getSplitAnimationController().removeSplitInstructionsView(mLauncher); 174 mController.resetState(); 175 } 176 }); 177 pendingAnimation.buildAnim().start(); 178 } 179 shouldIgnoreSecondSplitLaunch()180 private boolean shouldIgnoreSecondSplitLaunch() { 181 return (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get() 182 && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() 183 && !ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE.get()) 184 || !mController.isSplitSelectActive(); 185 } 186 } 187