1 /*
2  * Copyright (C) 2024 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.pip2.phone;
18 
19 import android.annotation.IntDef;
20 import android.graphics.Rect;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.view.SurfaceControl;
24 import android.window.WindowContainerToken;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 
29 import com.android.internal.util.Preconditions;
30 import com.android.wm.shell.shared.annotations.ShellMainThread;
31 
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.util.ArrayList;
35 import java.util.List;
36 
37 /**
38  * Contains the state relevant to carry out or probe the status of PiP transitions.
39  *
40  * <p>Existing and new PiP components can subscribe to PiP transition related state changes
41  * via <code>PipTransitionStateChangedListener</code>.</p>
42  *
43  * <p><code>PipTransitionState</code> users shouldn't rely on listener execution ordering.
44  * For example, if a class <code>Foo</code> wants to change some arbitrary state A that belongs
45  * to some other class <code>Bar</code>, a special care must be given when manipulating state A in
46  * <code>Foo#onPipTransitionStateChanged()</code>, since that's the responsibility of
47  * the class <code>Bar</code>.</p>
48  *
49  * <p>Hence, the recommended usage for classes who want to subscribe to
50  * <code>PipTransitionState</code> changes is to manipulate only their own internal state or
51  * <code>PipTransitionState</code> state.</p>
52  *
53  * <p>If there is some state that must be manipulated in another class <code>Bar</code>, it should
54  * just be moved to <code>PipTransitionState</code> and become a shared state
55  * between Foo and Bar.</p>
56  *
57  * <p>Moreover, <code>onPipTransitionStateChanged(oldState, newState, extra)</code>
58  * receives a <code>Bundle</code> extra object that can be optionally set via
59  * <code>setState(state, extra)</code>. This can be used to resolve extra information to update
60  * relevant internal or <code>PipTransitionState</code> state. However, each listener
61  * needs to check for whether the extra passed is correct for a particular state,
62  * and throw an <code>IllegalStateException</code> otherwise.</p>
63  */
64 public class PipTransitionState {
65     public static final int UNDEFINED = 0;
66 
67     // State for Launcher animating the swipe PiP to home animation.
68     public static final int SWIPING_TO_PIP = 1;
69 
70     // State for Shell animating enter PiP or jump-cutting to PiP mode after Launcher animation.
71     public static final int ENTERING_PIP = 2;
72 
73     // State for app finishing drawing in PiP mode as a final step in enter PiP flow.
74     public static final int ENTERED_PIP = 3;
75 
76     // State to indicate we have scheduled a PiP bounds change transition.
77     public static final int SCHEDULED_BOUNDS_CHANGE = 4;
78 
79     // State for the start of playing a transition to change PiP bounds. At this point, WM Core
80     // is aware of the new PiP bounds, but Shell might still be continuing animating.
81     public static final int CHANGING_PIP_BOUNDS = 5;
82 
83     // State for finishing animating into new PiP bounds after resize is complete.
84     public static final int CHANGED_PIP_BOUNDS = 6;
85 
86     // State for starting exiting PiP.
87     public static final int EXITING_PIP = 7;
88 
89     // State for finishing exit PiP flow.
90     public static final int EXITED_PIP = 8;
91 
92     private static final int FIRST_CUSTOM_STATE = 1000;
93 
94     private int mPrevCustomState = FIRST_CUSTOM_STATE;
95 
96     @IntDef(prefix = { "TRANSITION_STATE_" }, value =  {
97             UNDEFINED,
98             SWIPING_TO_PIP,
99             ENTERING_PIP,
100             ENTERED_PIP,
101             SCHEDULED_BOUNDS_CHANGE,
102             CHANGING_PIP_BOUNDS,
103             CHANGED_PIP_BOUNDS,
104             EXITING_PIP,
105             EXITED_PIP,
106     })
107     @Retention(RetentionPolicy.SOURCE)
108     public @interface TransitionState {}
109 
110     @TransitionState
111     private int mState;
112 
113     //
114     // Dependencies
115     //
116 
117     @ShellMainThread
118     private final Handler mMainHandler;
119 
120     //
121     // Swipe up to enter PiP related state
122     //
123 
124     // true if Launcher has started swipe PiP to home animation
125     private boolean mInSwipePipToHomeTransition;
126 
127     // App bounds used when as a starting point to swipe PiP to home animation in Launcher;
128     // these are also used to calculate the app icon overlay buffer size.
129     @NonNull
130     private final Rect mSwipePipToHomeAppBounds = new Rect();
131 
132     //
133     // Tokens and leashes
134     //
135 
136     // pinned PiP task's WC token
137     @Nullable
138     WindowContainerToken mPipTaskToken;
139 
140     // pinned PiP task's leash
141     @Nullable
142     SurfaceControl mPinnedTaskLeash;
143 
144     // Overlay leash potentially used during swipe PiP to home transition;
145     // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
146     @Nullable
147     private SurfaceControl mSwipePipToHomeOverlay;
148 
149     /**
150      * An interface to track state updates as we progress through PiP transitions.
151      */
152     public interface PipTransitionStateChangedListener {
153 
154         /** Reports changes in PiP transition state. */
onPipTransitionStateChanged(@ransitionState int oldState, @TransitionState int newState, @Nullable Bundle extra)155         void onPipTransitionStateChanged(@TransitionState int oldState,
156                 @TransitionState int newState, @Nullable Bundle extra);
157     }
158 
159     private final List<PipTransitionStateChangedListener> mCallbacks = new ArrayList<>();
160 
PipTransitionState(@hellMainThread Handler handler)161     public PipTransitionState(@ShellMainThread Handler handler) {
162         mMainHandler = handler;
163     }
164 
165     /**
166      * @return the state of PiP in the context of transitions.
167      */
168     @TransitionState
getState()169     public int getState() {
170         return mState;
171     }
172 
173     /**
174      * Sets the state of PiP in the context of transitions.
175      */
setState(@ransitionState int state)176     public void setState(@TransitionState int state) {
177         setState(state, null /* extra */);
178     }
179 
180     /**
181      * Sets the state of PiP in the context of transitions
182      *
183      * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info.
184      */
setState(@ransitionState int state, @Nullable Bundle extra)185     public void setState(@TransitionState int state, @Nullable Bundle extra) {
186         if (state == ENTERING_PIP || state == SWIPING_TO_PIP
187                 || state == SCHEDULED_BOUNDS_CHANGE || state == CHANGING_PIP_BOUNDS) {
188             // States listed above require extra bundles to be provided.
189             Preconditions.checkArgument(extra != null && !extra.isEmpty(),
190                     "No extra bundle for " + stateToString(state) + " state.");
191         }
192         if (mState != state) {
193             dispatchPipTransitionStateChanged(mState, state, extra);
194             mState = state;
195         }
196     }
197 
198     /**
199      * Posts the state update for PiP in the context of transitions onto the main handler.
200      *
201      * <p>This is done to guarantee that any callback dispatches for the present state are
202      * complete. This is relevant for states that have multiple listeners, such as
203      * <code>SCHEDULED_BOUNDS_CHANGE</code> that helps turn off touch interactions along with
204      * the actual transition scheduling.</p>
205      */
postState(@ransitionState int state)206     public void postState(@TransitionState int state) {
207         postState(state, null /* extra */);
208     }
209 
210     /**
211      * Posts the state update for PiP in the context of transitions onto the main handler.
212      *
213      * <p>This is done to guarantee that any callback dispatches for the present state are
214      * complete. This is relevant for states that have multiple listeners, such as
215      * <code>SCHEDULED_BOUNDS_CHANGE</code> that helps turn off touch interactions along with
216      * the actual transition scheduling.</p>
217      *
218      * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info.
219      */
postState(@ransitionState int state, @Nullable Bundle extra)220     public void postState(@TransitionState int state, @Nullable Bundle extra) {
221         mMainHandler.post(() -> setState(state, extra));
222     }
223 
dispatchPipTransitionStateChanged(@ransitionState int oldState, @TransitionState int newState, @Nullable Bundle extra)224     private void dispatchPipTransitionStateChanged(@TransitionState int oldState,
225             @TransitionState int newState, @Nullable Bundle extra) {
226         mCallbacks.forEach(l -> l.onPipTransitionStateChanged(oldState, newState, extra));
227     }
228 
229     /**
230      * Adds a {@link PipTransitionStateChangedListener} for future PiP transition state updates.
231      */
addPipTransitionStateChangedListener(PipTransitionStateChangedListener listener)232     public void addPipTransitionStateChangedListener(PipTransitionStateChangedListener listener) {
233         if (mCallbacks.contains(listener)) {
234             return;
235         }
236         mCallbacks.add(listener);
237     }
238 
239     /**
240      * @return true if provided {@link PipTransitionStateChangedListener}
241      * is registered before removing it.
242      */
removePipTransitionStateChangedListener( PipTransitionStateChangedListener listener)243     public boolean removePipTransitionStateChangedListener(
244             PipTransitionStateChangedListener listener) {
245         return mCallbacks.remove(listener);
246     }
247 
248     /**
249      * @return true if we have fully entered PiP.
250      */
isInPip()251     public boolean isInPip() {
252         return mState > ENTERING_PIP && mState < EXITING_PIP;
253     }
254 
setSwipePipToHomeState(@ullable SurfaceControl overlayLeash, @NonNull Rect appBounds)255     void setSwipePipToHomeState(@Nullable SurfaceControl overlayLeash,
256             @NonNull Rect appBounds) {
257         mInSwipePipToHomeTransition = true;
258         if (overlayLeash != null && !appBounds.isEmpty()) {
259             mSwipePipToHomeOverlay = overlayLeash;
260             mSwipePipToHomeAppBounds.set(appBounds);
261         }
262     }
263 
resetSwipePipToHomeState()264     void resetSwipePipToHomeState() {
265         mInSwipePipToHomeTransition = false;
266         mSwipePipToHomeOverlay = null;
267         mSwipePipToHomeAppBounds.setEmpty();
268     }
269 
270     /**
271      * @return true if in swipe PiP to home. Note that this is true until overlay fades if used too.
272      */
isInSwipePipToHomeTransition()273     public boolean isInSwipePipToHomeTransition() {
274         return mInSwipePipToHomeTransition;
275     }
276 
277     /**
278      * @return the overlay used during swipe PiP to home for invalid srcRectHints in auto-enter PiP;
279      * null if srcRectHint provided is valid.
280      */
281     @Nullable
getSwipePipToHomeOverlay()282     public SurfaceControl getSwipePipToHomeOverlay() {
283         return mSwipePipToHomeOverlay;
284     }
285 
286     /**
287      * @return app bounds used to calculate
288      */
289     @NonNull
getSwipePipToHomeAppBounds()290     public Rect getSwipePipToHomeAppBounds() {
291         return mSwipePipToHomeAppBounds;
292     }
293 
294     /**
295      * @return a custom state solely for internal use by the caller.
296      */
297     @TransitionState
getCustomState()298     public int getCustomState() {
299         return ++mPrevCustomState;
300     }
301 
stateToString(int state)302     private static String stateToString(int state) {
303         switch (state) {
304             case UNDEFINED: return "undefined";
305             case SWIPING_TO_PIP: return "swiping_to_pip";
306             case ENTERING_PIP: return "entering-pip";
307             case ENTERED_PIP: return "entered-pip";
308             case SCHEDULED_BOUNDS_CHANGE: return "scheduled_bounds_change";
309             case CHANGING_PIP_BOUNDS: return "changing-bounds";
310             case CHANGED_PIP_BOUNDS: return "changed-bounds";
311             case EXITING_PIP: return "exiting-pip";
312             case EXITED_PIP: return "exited-pip";
313         }
314         throw new IllegalStateException("Unknown state: " + state);
315     }
316 
317     @Override
toString()318     public String toString() {
319         return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
320                 stateToString(mState), mInSwipePipToHomeTransition);
321     }
322 }
323