• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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.wm.shell.pip;
18 
19 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
21 import static android.view.WindowManager.TRANSIT_PIP;
22 
23 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
24 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
25 
26 import android.annotation.IntDef;
27 import android.annotation.Nullable;
28 import android.app.ActivityTaskManager;
29 import android.app.Flags;
30 import android.app.PictureInPictureParams;
31 import android.app.PictureInPictureUiState;
32 import android.app.TaskInfo;
33 import android.content.ComponentName;
34 import android.content.pm.ActivityInfo;
35 import android.graphics.Point;
36 import android.graphics.Rect;
37 import android.os.IBinder;
38 import android.os.RemoteException;
39 import android.view.SurfaceControl;
40 import android.view.WindowManager;
41 import android.window.TransitionInfo;
42 import android.window.TransitionRequestInfo;
43 import android.window.WindowContainerTransaction;
44 
45 import androidx.annotation.NonNull;
46 
47 import com.android.internal.protolog.ProtoLog;
48 import com.android.wm.shell.ShellTaskOrganizer;
49 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
50 import com.android.wm.shell.common.pip.PipBoundsState;
51 import com.android.wm.shell.common.pip.PipMenuController;
52 import com.android.wm.shell.protolog.ShellProtoLogGroup;
53 import com.android.wm.shell.sysui.ShellInit;
54 import com.android.wm.shell.transition.DefaultMixedHandler;
55 import com.android.wm.shell.transition.Transitions;
56 
57 import java.io.PrintWriter;
58 import java.lang.annotation.Retention;
59 import java.lang.annotation.RetentionPolicy;
60 import java.util.HashMap;
61 import java.util.Map;
62 import java.util.concurrent.Executor;
63 
64 /**
65  * Responsible supplying PiP Transitions.
66  */
67 public abstract class PipTransitionController implements Transitions.TransitionHandler {
68 
69     protected final PipBoundsAlgorithm mPipBoundsAlgorithm;
70     protected final PipBoundsState mPipBoundsState;
71     protected final ShellTaskOrganizer mShellTaskOrganizer;
72     protected final PipMenuController mPipMenuController;
73     protected final Transitions mTransitions;
74     private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
75     protected PipTaskOrganizer mPipOrganizer;
76     protected DefaultMixedHandler mMixedHandler;
77 
78     public static final int ANIM_TYPE_BOUNDS = 0;
79     public static final int ANIM_TYPE_ALPHA = 1;
80 
81     @IntDef(prefix = { "ANIM_TYPE_" }, value = {
82             ANIM_TYPE_BOUNDS,
83             ANIM_TYPE_ALPHA
84     })
85     @Retention(RetentionPolicy.SOURCE)
86     public @interface AnimationType {}
87 
88 
89     protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
90             new PipAnimationController.PipAnimationCallback() {
91                 @Override
92                 public void onPipAnimationStart(TaskInfo taskInfo,
93                         PipAnimationController.PipTransitionAnimator animator) {
94                     final int direction = animator.getTransitionDirection();
95                     sendOnPipTransitionStarted(direction);
96                 }
97 
98                 @Override
99                 public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
100                         PipAnimationController.PipTransitionAnimator animator) {
101                     final int direction = animator.getTransitionDirection();
102                     mPipBoundsState.setBounds(animator.getDestinationBounds());
103                     if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
104                         return;
105                     }
106                     if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
107                         mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
108                                 null /* callback */, true /* withStartDelay*/);
109                     }
110                     onFinishResize(taskInfo, animator.getDestinationBounds(),
111                             animator.getLeashOffset(), direction, tx);
112                     sendOnPipTransitionFinished(direction);
113                 }
114 
115                 @Override
116                 public void onPipAnimationCancel(TaskInfo taskInfo,
117                         PipAnimationController.PipTransitionAnimator animator) {
118                     final int direction = animator.getTransitionDirection();
119                     if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
120                         mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
121                                 null /* callback */, true /* withStartDelay */);
122                     }
123                     sendOnPipTransitionCancelled(animator.getTransitionDirection());
124                 }
125             };
126 
127     /**
128      * Called when transition is about to finish. This is usually for performing tasks such as
129      * applying WindowContainerTransaction to finalize the PiP bounds and send to the framework.
130      */
onFinishResize(@onNull TaskInfo taskInfo, @NonNull Rect destinationBounds, @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction, @NonNull SurfaceControl.Transaction tx)131     public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds,
132             @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction,
133             @NonNull SurfaceControl.Transaction tx) {
134     }
135 
136     /**
137      * Called when the Shell wants to start an exit Pip transition/animation.
138      */
startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds)139     public void startExitTransition(int type, WindowContainerTransaction out,
140             @Nullable Rect destinationBounds) {
141         // Default implementation does nothing.
142     }
143 
144     /**
145      * Called when the Shell wants to start an exit-via-expand from Pip transition/animation.
146      */
startExpandTransition(WindowContainerTransaction out, boolean toSplit)147     public void startExpandTransition(WindowContainerTransaction out, boolean toSplit) {
148         // Default implementation does nothing.
149     }
150 
151     /**
152      * Called when the Shell wants to start a remove Pip transition/animation.
153      */
startRemoveTransition(boolean withFadeout)154     public void startRemoveTransition(boolean withFadeout) {
155         // Default implementation does nothing.
156     }
157 
158     /**
159      * Called when the Shell wants to start resizing Pip transition/animation.
160      *
161      * @param duration the suggested duration for resize animation.
162      */
startResizeTransition(WindowContainerTransaction wct, int duration)163     public void startResizeTransition(WindowContainerTransaction wct, int duration) {
164         // Default implementation does nothing.
165     }
166 
167     /**
168      * Called when the transition animation can't continue (eg. task is removed during
169      * animation)
170      */
forceFinishTransition()171     public void forceFinishTransition() {
172     }
173 
174     /** Called when the fixed rotation started. */
onFixedRotationStarted()175     public void onFixedRotationStarted() {
176     }
177 
178     /** Called when the fixed rotation finished. */
onFixedRotationFinished()179     public void onFixedRotationFinished() {
180     }
181 
PipTransitionController( @onNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm)182     public PipTransitionController(
183             @NonNull ShellInit shellInit,
184             @NonNull ShellTaskOrganizer shellTaskOrganizer,
185             @NonNull Transitions transitions,
186             PipBoundsState pipBoundsState,
187             PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm) {
188         mPipBoundsState = pipBoundsState;
189         mPipMenuController = pipMenuController;
190         mShellTaskOrganizer = shellTaskOrganizer;
191         mPipBoundsAlgorithm = pipBoundsAlgorithm;
192         mTransitions = transitions;
193         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
194             shellInit.addInitCallback(this::onInit, this);
195         }
196     }
197 
onInit()198     protected void onInit() {
199         mTransitions.addHandler(this);
200     }
201 
setPipOrganizer(PipTaskOrganizer pto)202     void setPipOrganizer(PipTaskOrganizer pto) {
203         mPipOrganizer = pto;
204     }
205 
setMixedHandler(DefaultMixedHandler mixedHandler)206     public void setMixedHandler(DefaultMixedHandler mixedHandler) {
207         mMixedHandler = mixedHandler;
208     }
209 
applyTransaction(WindowContainerTransaction wct)210     public void applyTransaction(WindowContainerTransaction wct) {
211         mShellTaskOrganizer.applyTransaction(wct);
212     }
213 
214     /**
215      * Registers {@link PipTransitionCallback} to receive transition callbacks.
216      */
registerPipTransitionCallback( @onNull PipTransitionCallback callback, @NonNull Executor executor)217     public void registerPipTransitionCallback(
218             @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
219         mPipTransitionCallbacks.put(callback, executor);
220     }
221 
onStartSwipePipToHome()222     protected void onStartSwipePipToHome() {
223         if (Flags.enablePipUiStateCallbackOnEntering()) {
224             try {
225                 ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
226                         new PictureInPictureUiState.Builder()
227                                 .setTransitioningToPip(true)
228                                 .build());
229             } catch (RemoteException | IllegalStateException e) {
230                 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
231                         "Failed to set alert PiP state change.");
232             }
233         }
234     }
235 
236     /**
237      * Used in {@link #sendOnPipTransitionStarted(int)} to decide whether we should send the
238      * PictureInPictureUiState change callback on transition start.
239      * For instance, in auto-enter-pip case, {@link #onStartSwipePipToHome()} should have signaled
240      * the app, and we can return {@code true} here to avoid double callback.
241      *
242      * @return {@code true} if there is a ongoing swipe pip to home transition.
243      */
isInSwipePipToHomeTransition()244     protected boolean isInSwipePipToHomeTransition() {
245         return false;
246     }
247 
sendOnPipTransitionStarted( @ipAnimationController.TransitionDirection int direction)248     protected void sendOnPipTransitionStarted(
249             @PipAnimationController.TransitionDirection int direction) {
250         final Rect pipBounds = mPipBoundsState.getBounds();
251         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
252                 "sendOnPipTransitionStarted direction=%d, bounds=%s", direction, pipBounds);
253         for (Map.Entry<PipTransitionCallback, Executor> entry
254                 : mPipTransitionCallbacks.entrySet()) {
255             entry.getValue().execute(
256                     () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
257         }
258         if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()
259                 && !isInSwipePipToHomeTransition()) {
260             try {
261                 ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
262                         new PictureInPictureUiState.Builder()
263                                 .setTransitioningToPip(true)
264                                 .build());
265             } catch (RemoteException | IllegalStateException e) {
266                 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
267                         "Failed to set alert PiP state change.");
268             }
269         }
270     }
271 
sendOnPipTransitionFinished( @ipAnimationController.TransitionDirection int direction)272     protected void sendOnPipTransitionFinished(
273             @PipAnimationController.TransitionDirection int direction) {
274         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
275                 "sendOnPipTransitionFinished direction=%d", direction);
276         for (Map.Entry<PipTransitionCallback, Executor> entry
277                 : mPipTransitionCallbacks.entrySet()) {
278             entry.getValue().execute(
279                     () -> entry.getKey().onPipTransitionFinished(direction));
280         }
281         if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
282             try {
283                 ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
284                         new PictureInPictureUiState.Builder()
285                                 .setTransitioningToPip(false)
286                                 .build());
287             } catch (RemoteException | IllegalStateException e) {
288                 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
289                         "Failed to set alert PiP state change.");
290             }
291         }
292     }
293 
sendOnPipTransitionCancelled( @ipAnimationController.TransitionDirection int direction)294     protected void sendOnPipTransitionCancelled(
295             @PipAnimationController.TransitionDirection int direction) {
296         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
297                 "sendOnPipTransitionCancelled direction=%d", direction);
298         for (Map.Entry<PipTransitionCallback, Executor> entry
299                 : mPipTransitionCallbacks.entrySet()) {
300             entry.getValue().execute(
301                     () -> entry.getKey().onPipTransitionCanceled(direction));
302         }
303     }
304 
305     /**
306      * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
307      * and can be overridden to restore to an alternate windowing mode.
308      */
getOutPipWindowingMode()309     public int getOutPipWindowingMode() {
310         // By default, simply reset the windowing mode to undefined.
311         return WINDOWING_MODE_UNDEFINED;
312     }
313 
setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params, ActivityInfo activityInfo)314     protected void setBoundsStateForEntry(ComponentName componentName,
315             PictureInPictureParams params,
316             ActivityInfo activityInfo) {
317         mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, params,
318                 mPipBoundsAlgorithm);
319     }
320 
321     /**
322      * Called when the display is going to rotate.
323      *
324      * @return {@code true} if it was handled, otherwise the existing pip logic
325      *                      will deal with rotation.
326      */
handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct)327     public boolean handleRotateDisplay(int startRotation, int endRotation,
328             WindowContainerTransaction wct) {
329         return false;
330     }
331 
332     /** @return whether the transition-request represents a pip-entry. */
requestHasPipEnter(@onNull TransitionRequestInfo request)333     public boolean requestHasPipEnter(@NonNull TransitionRequestInfo request) {
334         return request.getType() == TRANSIT_PIP;
335     }
336 
337     /** Whether a particular change is a window that is entering pip. */
isEnteringPip(@onNull TransitionInfo.Change change, @WindowManager.TransitionType int transitType)338     public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
339             @WindowManager.TransitionType int transitType) {
340         return false;
341     }
342 
343     /** Whether a particular package is same as current pip package. */
isPackageActiveInPip(@ullable String packageName)344     public boolean isPackageActiveInPip(@Nullable String packageName) {
345         // No-op, to be handled differently in PIP1 and PIP2
346         return false;
347     }
348 
349     /** Add PiP-related changes to `outWCT` for the given request. */
augmentRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT)350     public void augmentRequest(@NonNull IBinder transition,
351             @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) {
352         throw new IllegalStateException("Request isn't entering PiP");
353     }
354 
355     /** Sets the type of animation when a PiP task appears. */
setEnterAnimationType(@nimationType int type)356     public void setEnterAnimationType(@AnimationType int type) {
357     }
358 
359     /** Play a transition animation for entering PiP on a specific PiP change. */
startEnterAnimation(@onNull final TransitionInfo.Change pipChange, @NonNull final SurfaceControl.Transaction startTransaction, @NonNull final SurfaceControl.Transaction finishTransaction, @NonNull final Transitions.TransitionFinishCallback finishCallback)360     public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange,
361             @NonNull final SurfaceControl.Transaction startTransaction,
362             @NonNull final SurfaceControl.Transaction finishTransaction,
363             @NonNull final Transitions.TransitionFinishCallback finishCallback) {
364     }
365 
366     /**
367      * Applies the proper surface states (rounded corners/shadows) to pip surfaces in `info`.
368      * This is intended to be used when PiP is part of another animation but isn't, itself,
369      * animating (eg. unlocking).
370      * @return `true` if there was a pip in `info`.
371      */
syncPipSurfaceState(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)372     public boolean syncPipSurfaceState(@NonNull TransitionInfo info,
373             @NonNull SurfaceControl.Transaction startTransaction,
374             @NonNull SurfaceControl.Transaction finishTransaction) {
375         return false;
376     }
377 
378     /**
379      * Gets a change amongst the transition targets that is in a different final orientation than
380      * the display, signalling a potential fixed rotation transition.
381      */
382     @Nullable
findFixedRotationChange(@onNull TransitionInfo info)383     public TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) {
384         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
385             final TransitionInfo.Change change = info.getChanges().get(i);
386             if (change.getEndFixedRotation() != ROTATION_UNDEFINED) {
387                 return change;
388             }
389         }
390         return null;
391     }
392 
393     /** End the currently-playing PiP animation. */
end()394     public void end() {
395     }
396 
397     /**
398      * Finish the current transition if possible.
399      */
finishTransition()400     public void finishTransition() {
401     }
402 
403     /**
404      * End the currently-playing PiP animation.
405      *
406      * @param onTransitionEnd callback to run upon finishing the playing transition.
407      */
end(@ullable Runnable onTransitionEnd)408     public void end(@Nullable Runnable onTransitionEnd) {
409     }
410 
411     /** Starts the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */
startHighPerfSession()412     public void startHighPerfSession() {}
413 
414     /** Closes the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */
closeHighPerfSession()415     public void closeHighPerfSession() {}
416 
417     /**
418      * Callback interface for PiP transitions (both from and to PiP mode)
419      */
420     public interface PipTransitionCallback {
421         /**
422          * Callback when the pip transition is started.
423          */
onPipTransitionStarted(int direction, Rect pipBounds)424         void onPipTransitionStarted(int direction, Rect pipBounds);
425 
426         /**
427          * Callback when the pip transition is finished.
428          */
onPipTransitionFinished(int direction)429         void onPipTransitionFinished(int direction);
430 
431         /**
432          * Callback when the pip transition is cancelled.
433          */
onPipTransitionCanceled(int direction)434         void onPipTransitionCanceled(int direction);
435     }
436 
437     /**
438      * Dumps internal states.
439      */
dump(PrintWriter pw, String prefix)440     public void dump(PrintWriter pw, String prefix) {}
441 }
442