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 android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.app.PictureInPictureParams; 22 import android.content.ComponentName; 23 import android.content.pm.ActivityInfo; 24 25 import java.lang.annotation.Retention; 26 import java.lang.annotation.RetentionPolicy; 27 import java.util.ArrayList; 28 import java.util.List; 29 30 /** 31 * Used to keep track of PiP leash state as it appears and animates by {@link PipTaskOrganizer} and 32 * {@link PipTransition}. 33 */ 34 public class PipTransitionState { 35 36 public static final int UNDEFINED = 0; 37 public static final int TASK_APPEARED = 1; 38 public static final int ENTRY_SCHEDULED = 2; 39 public static final int ENTERING_PIP = 3; 40 public static final int ENTERED_PIP = 4; 41 public static final int EXITING_PIP = 5; 42 43 private final List<OnPipTransitionStateChangedListener> mOnPipTransitionStateChangedListeners = 44 new ArrayList<>(); 45 46 /** 47 * If set to {@code true}, no entering PiP transition would be kicked off and most likely 48 * it's due to the fact that Launcher is handling the transition directly when swiping 49 * auto PiP-able Activity to home. 50 * See also {@link PipTaskOrganizer#startSwipePipToHome(ComponentName, ActivityInfo, 51 * PictureInPictureParams)}. 52 */ 53 private boolean mInSwipePipToHomeTransition; 54 55 // Not a complete set of states but serves what we want right now. 56 @IntDef(prefix = { "TRANSITION_STATE_" }, value = { 57 UNDEFINED, 58 TASK_APPEARED, 59 ENTRY_SCHEDULED, 60 ENTERING_PIP, 61 ENTERED_PIP, 62 EXITING_PIP 63 }) 64 @Retention(RetentionPolicy.SOURCE) 65 public @interface TransitionState {} 66 67 private @TransitionState int mState; 68 PipTransitionState()69 public PipTransitionState() { 70 mState = UNDEFINED; 71 } 72 setTransitionState(@ransitionState int state)73 public void setTransitionState(@TransitionState int state) { 74 if (mState != state) { 75 for (int i = 0; i < mOnPipTransitionStateChangedListeners.size(); i++) { 76 mOnPipTransitionStateChangedListeners.get(i).onPipTransitionStateChanged( 77 mState, state); 78 } 79 mState = state; 80 } 81 } 82 getTransitionState()83 public @TransitionState int getTransitionState() { 84 return mState; 85 } 86 isInPip()87 public boolean isInPip() { 88 return isInPip(mState); 89 } 90 91 /** Returns true if activity has fully entered PiP mode. */ hasEnteredPip()92 public boolean hasEnteredPip() { 93 return hasEnteredPip(mState); 94 } 95 96 /** Returns true if activity is currently entering PiP mode. */ isEnteringPip()97 public boolean isEnteringPip() { 98 return isEnteringPip(mState); 99 } 100 setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition)101 public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) { 102 mInSwipePipToHomeTransition = inSwipePipToHomeTransition; 103 } 104 getInSwipePipToHomeTransition()105 public boolean getInSwipePipToHomeTransition() { 106 return mInSwipePipToHomeTransition; 107 } 108 /** 109 * Resize request can be initiated in other component, ignore if we are no longer in PIP, 110 * still waiting for animation or we're exiting from it. 111 * 112 * @return {@code true} if the resize request should be blocked/ignored. 113 */ shouldBlockResizeRequest()114 public boolean shouldBlockResizeRequest() { 115 return mState < ENTERING_PIP 116 || mState == EXITING_PIP; 117 } 118 addOnPipTransitionStateChangedListener( @onNull OnPipTransitionStateChangedListener listener)119 public void addOnPipTransitionStateChangedListener( 120 @NonNull OnPipTransitionStateChangedListener listener) { 121 mOnPipTransitionStateChangedListeners.add(listener); 122 } 123 removeOnPipTransitionStateChangedListener( @onNull OnPipTransitionStateChangedListener listener)124 public void removeOnPipTransitionStateChangedListener( 125 @NonNull OnPipTransitionStateChangedListener listener) { 126 mOnPipTransitionStateChangedListeners.remove(listener); 127 } 128 isInPip(@ransitionState int state)129 public static boolean isInPip(@TransitionState int state) { 130 return state >= TASK_APPEARED && state != EXITING_PIP; 131 } 132 133 /** Returns true if activity has fully entered PiP mode. */ hasEnteredPip(@ransitionState int state)134 public static boolean hasEnteredPip(@TransitionState int state) { 135 return state == ENTERED_PIP; 136 } 137 138 /** Returns true if activity is currently entering PiP mode. */ isEnteringPip(@ransitionState int state)139 public static boolean isEnteringPip(@TransitionState int state) { 140 return state == ENTERING_PIP; 141 } 142 stateToString()143 private String stateToString() { 144 switch (mState) { 145 case UNDEFINED: return "undefined"; 146 case TASK_APPEARED: return "task-appeared"; 147 case ENTRY_SCHEDULED: return "entry-scheduled"; 148 case ENTERING_PIP: return "entering-pip"; 149 case ENTERED_PIP: return "entered-pip"; 150 case EXITING_PIP: return "exiting-pip"; 151 } 152 throw new IllegalStateException("Unknown state: " + mState); 153 } 154 155 @Override toString()156 public String toString() { 157 return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)", 158 stateToString(), mInSwipePipToHomeTransition); 159 } 160 161 public interface OnPipTransitionStateChangedListener { onPipTransitionStateChanged(@ransitionState int oldState, @TransitionState int newState)162 void onPipTransitionStateChanged(@TransitionState int oldState, 163 @TransitionState int newState); 164 } 165 } 166