• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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