1 /* 2 * Copyright 2021 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 android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20 21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 22 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 23 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; 24 25 import android.animation.AnimatorSet; 26 import android.app.ActivityOptions; 27 import android.content.res.Resources; 28 import android.graphics.Rect; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.Looper; 32 import android.util.Pair; 33 import android.view.Gravity; 34 import android.view.SurfaceControl; 35 import android.window.TransitionInfo; 36 37 import androidx.annotation.Nullable; 38 39 import com.android.launcher3.BaseActivity; 40 import com.android.launcher3.BaseQuickstepLauncher; 41 import com.android.launcher3.DeviceProfile; 42 import com.android.launcher3.InsettableFrameLayout; 43 import com.android.launcher3.LauncherAnimationRunner; 44 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; 45 import com.android.launcher3.R; 46 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; 47 import com.android.quickstep.SystemUiProxy; 48 import com.android.quickstep.TaskAnimationManager; 49 import com.android.quickstep.TaskViewUtils; 50 import com.android.quickstep.views.TaskView; 51 import com.android.systemui.shared.system.ActivityOptionsCompat; 52 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; 53 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; 54 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 55 import com.android.systemui.shared.system.RemoteTransitionCompat; 56 import com.android.systemui.shared.system.RemoteTransitionRunner; 57 58 /** 59 * Represent data needed for the transient state when user has selected one app for split screen 60 * and is in the process of either a) selecting a second app or b) exiting intention to invoke split 61 */ 62 public class SplitSelectStateController { 63 64 private final SystemUiProxy mSystemUiProxy; 65 private TaskView mInitialTaskView; 66 private SplitPositionOption mInitialPosition; 67 private Rect mInitialBounds; 68 private final Handler mHandler; 69 SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy)70 public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy) { 71 mSystemUiProxy = systemUiProxy; 72 mHandler = handler; 73 } 74 75 /** 76 * To be called after first task selected 77 */ setInitialTaskSelect(TaskView taskView, SplitPositionOption positionOption, Rect initialBounds)78 public void setInitialTaskSelect(TaskView taskView, SplitPositionOption positionOption, 79 Rect initialBounds) { 80 mInitialTaskView = taskView; 81 mInitialPosition = positionOption; 82 mInitialBounds = initialBounds; 83 } 84 85 /** 86 * To be called after second task selected 87 */ setSecondTaskId(TaskView taskView)88 public void setSecondTaskId(TaskView taskView) { 89 if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 90 // Assume initial task is for top/left part of screen 91 final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT 92 ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id} 93 : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id}; 94 95 RemoteSplitLaunchAnimationRunner animationRunner = 96 new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView); 97 mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1], 98 null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, 99 new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR)); 100 return; 101 } 102 // Assume initial mInitialTaskId is for top/left part of screen 103 RemoteAnimationFactory initialSplitRunnerWrapped = new SplitLaunchAnimationRunner( 104 mInitialTaskView, 0); 105 RemoteAnimationFactory secondarySplitRunnerWrapped = new SplitLaunchAnimationRunner( 106 taskView, 1); 107 RemoteAnimationRunnerCompat initialSplitRunner = new LauncherAnimationRunner( 108 new Handler(Looper.getMainLooper()), initialSplitRunnerWrapped, 109 true /* startAtFrontOfQueue */); 110 RemoteAnimationRunnerCompat secondarySplitRunner = new LauncherAnimationRunner( 111 new Handler(Looper.getMainLooper()), secondarySplitRunnerWrapped, 112 true /* startAtFrontOfQueue */); 113 ActivityOptions initialOptions = ActivityOptionsCompat.makeRemoteAnimation( 114 new RemoteAnimationAdapterCompat(initialSplitRunner, 300, 150)); 115 ActivityOptions secondaryOptions = ActivityOptionsCompat.makeRemoteAnimation( 116 new RemoteAnimationAdapterCompat(secondarySplitRunner, 300, 150)); 117 mSystemUiProxy.startTask(mInitialTaskView.getTask().key.id, mInitialPosition.mStageType, 118 mInitialPosition.mStagePosition, 119 /*null*/ initialOptions.toBundle()); 120 Pair<Integer, Integer> compliment = getComplimentaryStageAndPosition(mInitialPosition); 121 mSystemUiProxy.startTask(taskView.getTask().key.id, compliment.first, 122 compliment.second, 123 /*null*/ secondaryOptions.toBundle()); 124 // After successful launch, call resetState 125 resetState(); 126 } 127 128 /** 129 * @return {@link InsettableFrameLayout.LayoutParams} to correctly position the 130 * split placeholder view 131 */ getLayoutParamsForActivePosition(Resources resources, DeviceProfile deviceProfile)132 public InsettableFrameLayout.LayoutParams getLayoutParamsForActivePosition(Resources resources, 133 DeviceProfile deviceProfile) { 134 InsettableFrameLayout.LayoutParams params = 135 new InsettableFrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); 136 boolean topLeftPosition = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT; 137 if (deviceProfile.isLandscape) { 138 params.width = (int) resources.getDimension(R.dimen.split_placeholder_size); 139 params.gravity = topLeftPosition ? Gravity.START : Gravity.END; 140 } else { 141 params.height = (int) resources.getDimension(R.dimen.split_placeholder_size); 142 params.gravity = Gravity.TOP; 143 } 144 145 return params; 146 } 147 148 @Nullable getActiveSplitPositionOption()149 public SplitPositionOption getActiveSplitPositionOption() { 150 return mInitialPosition; 151 } 152 153 /** 154 * Requires Shell Transitions 155 */ 156 private class RemoteSplitLaunchAnimationRunner implements RemoteTransitionRunner { 157 158 private final TaskView mInitialTaskView; 159 private final TaskView mTaskView; 160 RemoteSplitLaunchAnimationRunner(TaskView initialTaskView, TaskView taskView)161 RemoteSplitLaunchAnimationRunner(TaskView initialTaskView, TaskView taskView) { 162 mInitialTaskView = initialTaskView; 163 mTaskView = taskView; 164 } 165 166 @Override startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, Runnable finishCallback)167 public void startAnimation(IBinder transition, TransitionInfo info, 168 SurfaceControl.Transaction t, Runnable finishCallback) { 169 TaskViewUtils.composeRecentsSplitLaunchAnimator(mInitialTaskView, mTaskView, 170 info, t, finishCallback); 171 // After successful launch, call resetState 172 resetState(); 173 } 174 } 175 176 /** 177 * LEGACY 178 * @return the opposite stage and position from the {@param position} provided as first and 179 * second object, respectively 180 * Ex. If position is has stage = Main and position = Top/Left, this will return 181 * Pair(stage=Side, position=Bottom/Left) 182 */ getComplimentaryStageAndPosition(SplitPositionOption position)183 private Pair<Integer, Integer> getComplimentaryStageAndPosition(SplitPositionOption position) { 184 // Right now this is as simple as flipping between 0 and 1 185 int complimentStageType = position.mStageType ^ 1; 186 int complimentStagePosition = position.mStagePosition ^ 1; 187 return new Pair<>(complimentStageType, complimentStagePosition); 188 } 189 190 /** 191 * LEGACY 192 * Remote animation runner for animation to launch an app. 193 */ 194 private class SplitLaunchAnimationRunner implements RemoteAnimationFactory { 195 196 private final TaskView mV; 197 private final int mTargetState; 198 SplitLaunchAnimationRunner(TaskView v, int targetState)199 SplitLaunchAnimationRunner(TaskView v, int targetState) { 200 mV = v; 201 mTargetState = targetState; 202 } 203 204 @Override onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, LauncherAnimationRunner.AnimationResult result)205 public void onCreateAnimation(int transit, 206 RemoteAnimationTargetCompat[] appTargets, 207 RemoteAnimationTargetCompat[] wallpaperTargets, 208 RemoteAnimationTargetCompat[] nonAppTargets, 209 LauncherAnimationRunner.AnimationResult result) { 210 AnimatorSet anim = new AnimatorSet(); 211 BaseQuickstepLauncher activity = BaseActivity.fromContext(mV.getContext()); 212 TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(anim, mV, 213 appTargets, wallpaperTargets, nonAppTargets, true, activity.getStateManager(), 214 activity.getDepthController(), mTargetState); 215 result.setAnimation(anim, activity); 216 } 217 } 218 219 220 /** 221 * To be called if split select was cancelled 222 */ resetState()223 public void resetState() { 224 mInitialTaskView = null; 225 mInitialPosition = null; 226 mInitialBounds = null; 227 } 228 isSplitSelectActive()229 public boolean isSplitSelectActive() { 230 return mInitialTaskView != null; 231 } 232 getInitialBounds()233 public Rect getInitialBounds() { 234 return mInitialBounds; 235 } 236 } 237