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