1 /* 2 * Copyright (C) 2019 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 android.view; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.CancellationSignal; 22 import android.view.WindowInsets.Type.InsetsType; 23 import android.view.animation.Interpolator; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 27 import java.util.ArrayList; 28 29 /** 30 * An insets controller that keeps track of pending requests. This is such that an app can freely 31 * use {@link WindowInsetsController} before the view root is attached during activity startup. 32 * @hide 33 */ 34 public class PendingInsetsController implements WindowInsetsController { 35 36 private static final int KEEP_BEHAVIOR = -1; 37 private final ArrayList<PendingRequest> mRequests = new ArrayList<>(); 38 private @Appearance int mAppearance; 39 private @Appearance int mAppearanceMask; 40 private @Behavior int mBehavior = KEEP_BEHAVIOR; 41 private boolean mAnimationsDisabled; 42 private final InsetsState mDummyState = new InsetsState(); 43 private InsetsController mReplayedInsetsController; 44 private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners 45 = new ArrayList<>(); 46 private int mCaptionInsetsHeight = 0; 47 private WindowInsetsAnimationControlListener mLoggingListener; 48 49 @Override show(int types)50 public void show(int types) { 51 if (mReplayedInsetsController != null) { 52 mReplayedInsetsController.show(types); 53 } else { 54 mRequests.add(new ShowRequest(types)); 55 } 56 } 57 58 @Override hide(int types)59 public void hide(int types) { 60 if (mReplayedInsetsController != null) { 61 mReplayedInsetsController.hide(types); 62 } else { 63 mRequests.add(new HideRequest(types)); 64 } 65 } 66 67 @Override setSystemBarsAppearance(int appearance, int mask)68 public void setSystemBarsAppearance(int appearance, int mask) { 69 if (mReplayedInsetsController != null) { 70 mReplayedInsetsController.setSystemBarsAppearance(appearance, mask); 71 } else { 72 mAppearance = (mAppearance & ~mask) | (appearance & mask); 73 mAppearanceMask |= mask; 74 } 75 } 76 77 @Override getSystemBarsAppearance()78 public int getSystemBarsAppearance() { 79 if (mReplayedInsetsController != null) { 80 return mReplayedInsetsController.getSystemBarsAppearance(); 81 } 82 return mAppearance; 83 } 84 85 @Override setCaptionInsetsHeight(int height)86 public void setCaptionInsetsHeight(int height) { 87 mCaptionInsetsHeight = height; 88 } 89 90 @Override setSystemBarsBehavior(int behavior)91 public void setSystemBarsBehavior(int behavior) { 92 if (mReplayedInsetsController != null) { 93 mReplayedInsetsController.setSystemBarsBehavior(behavior); 94 } else { 95 mBehavior = behavior; 96 } 97 } 98 99 @Override getSystemBarsBehavior()100 public int getSystemBarsBehavior() { 101 if (mReplayedInsetsController != null) { 102 return mReplayedInsetsController.getSystemBarsBehavior(); 103 } 104 if (mBehavior == KEEP_BEHAVIOR) { 105 return BEHAVIOR_DEFAULT; 106 } 107 return mBehavior; 108 } 109 110 @Override setAnimationsDisabled(boolean disable)111 public void setAnimationsDisabled(boolean disable) { 112 if (mReplayedInsetsController != null) { 113 mReplayedInsetsController.setAnimationsDisabled(disable); 114 } else { 115 mAnimationsDisabled = disable; 116 } 117 } 118 119 @Override getState()120 public InsetsState getState() { 121 return mDummyState; 122 } 123 124 @Override isRequestedVisible(int type)125 public boolean isRequestedVisible(int type) { 126 127 // Method is only used once real insets controller is attached, so no need to traverse 128 // requests here. 129 return InsetsState.getDefaultVisibility(type); 130 } 131 132 @Override addOnControllableInsetsChangedListener( OnControllableInsetsChangedListener listener)133 public void addOnControllableInsetsChangedListener( 134 OnControllableInsetsChangedListener listener) { 135 if (mReplayedInsetsController != null) { 136 mReplayedInsetsController.addOnControllableInsetsChangedListener(listener); 137 } else { 138 mControllableInsetsChangedListeners.add(listener); 139 listener.onControllableInsetsChanged(this, 0); 140 } 141 } 142 143 @Override removeOnControllableInsetsChangedListener( OnControllableInsetsChangedListener listener)144 public void removeOnControllableInsetsChangedListener( 145 OnControllableInsetsChangedListener listener) { 146 if (mReplayedInsetsController != null) { 147 mReplayedInsetsController.removeOnControllableInsetsChangedListener(listener); 148 } else { 149 mControllableInsetsChangedListeners.remove(listener); 150 } 151 } 152 153 /** 154 * Replays the commands on {@code controller} and attaches it to this instance such that any 155 * calls will be forwarded to the real instance in the future. 156 */ 157 @VisibleForTesting replayAndAttach(InsetsController controller)158 public void replayAndAttach(InsetsController controller) { 159 if (mBehavior != KEEP_BEHAVIOR) { 160 controller.setSystemBarsBehavior(mBehavior); 161 } 162 if (mAppearanceMask != 0) { 163 controller.setSystemBarsAppearance(mAppearance, mAppearanceMask); 164 } 165 if (mCaptionInsetsHeight != 0) { 166 controller.setCaptionInsetsHeight(mCaptionInsetsHeight); 167 } 168 if (mAnimationsDisabled) { 169 controller.setAnimationsDisabled(true); 170 } 171 int size = mRequests.size(); 172 for (int i = 0; i < size; i++) { 173 mRequests.get(i).replay(controller); 174 } 175 size = mControllableInsetsChangedListeners.size(); 176 for (int i = 0; i < size; i++) { 177 controller.addOnControllableInsetsChangedListener( 178 mControllableInsetsChangedListeners.get(i)); 179 } 180 if (mLoggingListener != null) { 181 controller.setSystemDrivenInsetsAnimationLoggingListener(mLoggingListener); 182 } 183 184 // Reset all state so it doesn't get applied twice just in case 185 mRequests.clear(); 186 mControllableInsetsChangedListeners.clear(); 187 mBehavior = KEEP_BEHAVIOR; 188 mAppearance = 0; 189 mAppearanceMask = 0; 190 mAnimationsDisabled = false; 191 mLoggingListener = null; 192 // After replaying, we forward everything directly to the replayed instance. 193 mReplayedInsetsController = controller; 194 } 195 196 /** 197 * Detaches the controller to no longer forward calls to the real instance. 198 */ 199 @VisibleForTesting detach()200 public void detach() { 201 mReplayedInsetsController = null; 202 } 203 204 @Override setSystemDrivenInsetsAnimationLoggingListener( @ullable WindowInsetsAnimationControlListener listener)205 public void setSystemDrivenInsetsAnimationLoggingListener( 206 @Nullable WindowInsetsAnimationControlListener listener) { 207 if (mReplayedInsetsController != null) { 208 mReplayedInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener); 209 } else { 210 mLoggingListener = listener; 211 } 212 } 213 214 @Override controlWindowInsetsAnimation(@nsetsType int types, long durationMillis, @Nullable Interpolator interpolator, CancellationSignal cancellationSignal, @NonNull WindowInsetsAnimationControlListener listener)215 public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis, 216 @Nullable Interpolator interpolator, 217 CancellationSignal cancellationSignal, 218 @NonNull WindowInsetsAnimationControlListener listener) { 219 if (mReplayedInsetsController != null) { 220 mReplayedInsetsController.controlWindowInsetsAnimation(types, durationMillis, 221 interpolator, cancellationSignal, listener); 222 } else { 223 listener.onCancelled(null); 224 } 225 } 226 227 private interface PendingRequest { replay(InsetsController controller)228 void replay(InsetsController controller); 229 } 230 231 private static class ShowRequest implements PendingRequest { 232 233 private final @InsetsType int mTypes; 234 ShowRequest(int types)235 public ShowRequest(int types) { 236 mTypes = types; 237 } 238 239 @Override replay(InsetsController controller)240 public void replay(InsetsController controller) { 241 controller.show(mTypes); 242 } 243 } 244 245 private static class HideRequest implements PendingRequest { 246 247 private final @InsetsType int mTypes; 248 HideRequest(int types)249 public HideRequest(int types) { 250 mTypes = types; 251 } 252 253 @Override replay(InsetsController controller)254 public void replay(InsetsController controller) { 255 controller.hide(mTypes); 256 } 257 } 258 } 259