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.util.Executors.MODEL_EXECUTOR; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.PendingIntent; 26 import android.content.Intent; 27 import android.graphics.Bitmap; 28 import android.graphics.Canvas; 29 import android.graphics.Rect; 30 import android.graphics.RectF; 31 import android.graphics.drawable.BitmapDrawable; 32 import android.graphics.drawable.Drawable; 33 import android.os.UserHandle; 34 import android.view.View; 35 36 import com.android.internal.jank.Cuj; 37 import com.android.launcher3.DeviceProfile; 38 import com.android.launcher3.LauncherAppState; 39 import com.android.launcher3.R; 40 import com.android.launcher3.anim.PendingAnimation; 41 import com.android.launcher3.config.FeatureFlags; 42 import com.android.launcher3.icons.BitmapInfo; 43 import com.android.launcher3.icons.IconCache; 44 import com.android.launcher3.model.data.AppPairInfo; 45 import com.android.launcher3.model.data.ItemInfo; 46 import com.android.launcher3.model.data.PackageItemInfo; 47 import com.android.launcher3.model.data.WorkspaceItemInfo; 48 import com.android.launcher3.uioverrides.QuickstepLauncher; 49 import com.android.quickstep.views.FloatingTaskView; 50 import com.android.quickstep.views.RecentsView; 51 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 52 53 /** Handles when the stage split lands on the home screen. */ 54 public class SplitToWorkspaceController { 55 56 private final QuickstepLauncher mLauncher; 57 private final SplitSelectStateController mController; 58 59 private final int mHalfDividerSize; 60 private final IconCache mIconCache; 61 SplitToWorkspaceController(QuickstepLauncher launcher, SplitSelectStateController controller)62 public SplitToWorkspaceController(QuickstepLauncher launcher, 63 SplitSelectStateController controller) { 64 mLauncher = launcher; 65 mController = controller; 66 mIconCache = LauncherAppState.getInstance(launcher).getIconCache(); 67 mHalfDividerSize = mLauncher.getResources().getDimensionPixelSize( 68 R.dimen.multi_window_task_divider_size) / 2; 69 } 70 71 /** 72 * Handles widget selection from staged split. 73 * @param view Original widget view 74 * @param pendingIntent Provided by widget via InteractionHandler 75 * @return {@code true} if we can attempt launch the widget into split, {@code false} otherwise 76 * to allow launcher to handle the click 77 */ handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent, Intent remoteResponseIntent)78 public boolean handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent, 79 Intent remoteResponseIntent) { 80 if (shouldIgnoreSecondSplitLaunch()) { 81 return false; 82 } 83 84 int width = view.getWidth(); 85 int height = view.getHeight(); 86 MODEL_EXECUTOR.execute(() -> { 87 PackageItemInfo infoInOut = new PackageItemInfo(pendingIntent.getCreatorPackage(), 88 pendingIntent.getCreatorUserHandle()); 89 mIconCache.getTitleAndIconForApp(infoInOut, false); 90 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 91 92 view.post(() -> { 93 mController.setSecondWidget(pendingIntent, remoteResponseIntent); 94 // Convert original widgetView into bitmap to use for animation 95 Canvas canvas = new Canvas(bitmap); 96 view.draw(canvas); 97 startWorkspaceAnimation(view, bitmap, 98 new BitmapDrawable(mLauncher.getResources(), infoInOut.bitmap.icon)); 99 }); 100 }); 101 102 return true; 103 } 104 105 /** 106 * Handles second app selection from stage split. If the item can't be opened in split or 107 * it's not in stage split state, we pass it onto Launcher's default item click handler. 108 */ handleSecondAppSelectionForSplit(View view)109 public boolean handleSecondAppSelectionForSplit(View view) { 110 if (shouldIgnoreSecondSplitLaunch()) { 111 return false; 112 } 113 Object tag = view.getTag(); 114 Intent intent; 115 UserHandle user; 116 BitmapInfo bitmapInfo; 117 if (tag instanceof WorkspaceItemInfo) { 118 final WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) tag; 119 intent = workspaceItemInfo.intent; 120 user = workspaceItemInfo.user; 121 bitmapInfo = workspaceItemInfo.bitmap; 122 } else if (tag instanceof com.android.launcher3.model.data.AppInfo) { 123 final com.android.launcher3.model.data.AppInfo appInfo = 124 (com.android.launcher3.model.data.AppInfo) tag; 125 intent = appInfo.intent; 126 user = appInfo.user; 127 bitmapInfo = appInfo.bitmap; 128 } else if (tag instanceof AppPairInfo) { 129 // Prompt the user to select something else by wiggling the instructions view 130 mController.getSplitInstructionsView().goBoing(); 131 return true; 132 } else { 133 // Use Launcher's default click handler 134 return false; 135 } 136 137 mController.setSecondTask(intent, user, (ItemInfo) tag); 138 139 startWorkspaceAnimation(view, null /*bitmap*/, bitmapInfo.newIcon(mLauncher)); 140 return true; 141 } 142 startWorkspaceAnimation(@onNull View view, @Nullable Bitmap bitmap, @Nullable Drawable icon)143 private void startWorkspaceAnimation(@NonNull View view, @Nullable Bitmap bitmap, 144 @Nullable Drawable icon) { 145 DeviceProfile dp = mLauncher.getDeviceProfile(); 146 boolean isTablet = dp.isTablet; 147 SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet); 148 PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration()); 149 150 Rect firstTaskStartingBounds = new Rect(); 151 Rect firstTaskEndingBounds = new Rect(); 152 RectF secondTaskStartingBounds = new RectF(); 153 Rect secondTaskEndingBounds = new Rect(); 154 155 RecentsView recentsView = mLauncher.getOverviewPanel(); 156 recentsView.getPagedOrientationHandler().getFinalSplitPlaceholderBounds(mHalfDividerSize, 157 dp, mController.getActiveSplitStagePosition(), firstTaskEndingBounds, 158 secondTaskEndingBounds); 159 160 FloatingTaskView firstFloatingTaskView = mController.getFirstFloatingTaskView(); 161 firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds); 162 firstFloatingTaskView.addConfirmAnimation(pendingAnimation, 163 new RectF(firstTaskStartingBounds), firstTaskEndingBounds, 164 false /* fadeWithThumbnail */, true /* isStagedTask */); 165 166 View backingScrim = recentsView.getSplitSelectController().getSplitAnimationController() 167 .addScrimBehindAnim(pendingAnimation, mLauncher, view.getContext()); 168 169 FloatingTaskView secondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mLauncher, 170 view, bitmap, icon, secondTaskStartingBounds); 171 secondFloatingTaskView.setAlpha(1); 172 secondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds, 173 secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */); 174 175 pendingAnimation.addListener(new AnimatorListenerAdapter() { 176 private boolean mIsCancelled = false; 177 178 @Override 179 public void onAnimationStart(Animator animation) { 180 mController.launchSplitTasks(aBoolean -> cleanUp()); 181 } 182 183 @Override 184 public void onAnimationCancel(Animator animation) { 185 mIsCancelled = true; 186 cleanUp(); 187 } 188 189 @Override 190 public void onAnimationEnd(Animator animation) { 191 if (!mIsCancelled) { 192 InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER); 193 } 194 } 195 196 private void cleanUp() { 197 mLauncher.getDragLayer().removeView(firstFloatingTaskView); 198 mLauncher.getDragLayer().removeView(secondFloatingTaskView); 199 mLauncher.getDragLayer().removeView(backingScrim); 200 mController.getSplitAnimationController().removeSplitInstructionsView(mLauncher); 201 mController.resetState(); 202 } 203 }); 204 pendingAnimation.buildAnim().start(); 205 } 206 shouldIgnoreSecondSplitLaunch()207 private boolean shouldIgnoreSecondSplitLaunch() { 208 return !FeatureFlags.enableSplitContextually() || !mController.isSplitSelectActive(); 209 } 210 } 211