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 @Appearance int mAppearanceFromResource; 41 private @Appearance int mAppearanceFromResourceMask; 42 private @Behavior int mBehavior = KEEP_BEHAVIOR; 43 private boolean mAnimationsDisabled; 44 private final InsetsState mDummyState = new InsetsState(); 45 private InsetsController mReplayedInsetsController; 46 private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners 47 = new ArrayList<>(); 48 private int mImeCaptionBarInsetsHeight = 0; 49 private WindowInsetsAnimationControlListener mLoggingListener; 50 private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); 51 52 @Override show(int types)53 public void show(int types) { 54 if (mReplayedInsetsController != null) { 55 mReplayedInsetsController.show(types); 56 } else { 57 mRequests.add(new ShowRequest(types)); 58 mRequestedVisibleTypes |= types; 59 } 60 } 61 62 @Override hide(int types)63 public void hide(int types) { 64 if (mReplayedInsetsController != null) { 65 mReplayedInsetsController.hide(types); 66 } else { 67 mRequests.add(new HideRequest(types)); 68 mRequestedVisibleTypes &= ~types; 69 } 70 } 71 72 @Override setSystemBarsAppearance(int appearance, int mask)73 public void setSystemBarsAppearance(int appearance, int mask) { 74 if (mReplayedInsetsController != null) { 75 mReplayedInsetsController.setSystemBarsAppearance(appearance, mask); 76 } else { 77 mAppearance = (mAppearance & ~mask) | (appearance & mask); 78 mAppearanceMask |= mask; 79 } 80 } 81 82 @Override setSystemBarsAppearanceFromResource(int appearance, int mask)83 public void setSystemBarsAppearanceFromResource(int appearance, int mask) { 84 if (mReplayedInsetsController != null) { 85 mReplayedInsetsController.setSystemBarsAppearanceFromResource(appearance, mask); 86 } else { 87 mAppearanceFromResource = (mAppearanceFromResource & ~mask) | (appearance & mask); 88 mAppearanceFromResourceMask |= mask; 89 } 90 } 91 92 @Override getSystemBarsAppearance()93 public int getSystemBarsAppearance() { 94 if (mReplayedInsetsController != null) { 95 return mReplayedInsetsController.getSystemBarsAppearance(); 96 } 97 return mAppearance | (mAppearanceFromResource & ~mAppearanceMask); 98 } 99 100 @Override setImeCaptionBarInsetsHeight(int height)101 public void setImeCaptionBarInsetsHeight(int height) { 102 mImeCaptionBarInsetsHeight = height; 103 } 104 105 @Override setSystemBarsBehavior(int behavior)106 public void setSystemBarsBehavior(int behavior) { 107 if (mReplayedInsetsController != null) { 108 mReplayedInsetsController.setSystemBarsBehavior(behavior); 109 } else { 110 mBehavior = behavior; 111 } 112 } 113 114 @Override getSystemBarsBehavior()115 public int getSystemBarsBehavior() { 116 if (mReplayedInsetsController != null) { 117 return mReplayedInsetsController.getSystemBarsBehavior(); 118 } 119 if (mBehavior == KEEP_BEHAVIOR) { 120 return BEHAVIOR_DEFAULT; 121 } 122 return mBehavior; 123 } 124 125 @Override setAnimationsDisabled(boolean disable)126 public void setAnimationsDisabled(boolean disable) { 127 if (mReplayedInsetsController != null) { 128 mReplayedInsetsController.setAnimationsDisabled(disable); 129 } else { 130 mAnimationsDisabled = disable; 131 } 132 } 133 134 @Override getState()135 public InsetsState getState() { 136 return mDummyState; 137 } 138 139 @Override getRequestedVisibleTypes()140 public @InsetsType int getRequestedVisibleTypes() { 141 if (mReplayedInsetsController != null) { 142 return mReplayedInsetsController.getRequestedVisibleTypes(); 143 } 144 return mRequestedVisibleTypes; 145 } 146 147 @Override addOnControllableInsetsChangedListener( OnControllableInsetsChangedListener listener)148 public void addOnControllableInsetsChangedListener( 149 OnControllableInsetsChangedListener listener) { 150 if (mReplayedInsetsController != null) { 151 mReplayedInsetsController.addOnControllableInsetsChangedListener(listener); 152 } else { 153 mControllableInsetsChangedListeners.add(listener); 154 listener.onControllableInsetsChanged(this, 0); 155 } 156 } 157 158 @Override removeOnControllableInsetsChangedListener( OnControllableInsetsChangedListener listener)159 public void removeOnControllableInsetsChangedListener( 160 OnControllableInsetsChangedListener listener) { 161 if (mReplayedInsetsController != null) { 162 mReplayedInsetsController.removeOnControllableInsetsChangedListener(listener); 163 } else { 164 mControllableInsetsChangedListeners.remove(listener); 165 } 166 } 167 168 /** 169 * Replays the commands on {@code controller} and attaches it to this instance such that any 170 * calls will be forwarded to the real instance in the future. 171 */ 172 @VisibleForTesting replayAndAttach(InsetsController controller)173 public void replayAndAttach(InsetsController controller) { 174 if (mBehavior != KEEP_BEHAVIOR) { 175 controller.setSystemBarsBehavior(mBehavior); 176 } 177 if (mAppearanceMask != 0) { 178 controller.setSystemBarsAppearance(mAppearance, mAppearanceMask); 179 } 180 if (mAppearanceFromResourceMask != 0) { 181 controller.setSystemBarsAppearanceFromResource( 182 mAppearanceFromResource, mAppearanceFromResourceMask); 183 } 184 if (mImeCaptionBarInsetsHeight != 0) { 185 controller.setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight); 186 } 187 if (mAnimationsDisabled) { 188 controller.setAnimationsDisabled(true); 189 } 190 int size = mRequests.size(); 191 for (int i = 0; i < size; i++) { 192 mRequests.get(i).replay(controller); 193 } 194 size = mControllableInsetsChangedListeners.size(); 195 for (int i = 0; i < size; i++) { 196 controller.addOnControllableInsetsChangedListener( 197 mControllableInsetsChangedListeners.get(i)); 198 } 199 if (mLoggingListener != null) { 200 controller.setSystemDrivenInsetsAnimationLoggingListener(mLoggingListener); 201 } 202 203 // Reset all state so it doesn't get applied twice just in case 204 mRequests.clear(); 205 mControllableInsetsChangedListeners.clear(); 206 mBehavior = KEEP_BEHAVIOR; 207 mAppearance = 0; 208 mAppearanceMask = 0; 209 mAppearanceFromResource = 0; 210 mAppearanceFromResourceMask = 0; 211 mAnimationsDisabled = false; 212 mLoggingListener = null; 213 mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); 214 // After replaying, we forward everything directly to the replayed instance. 215 mReplayedInsetsController = controller; 216 } 217 218 /** 219 * Detaches the controller to no longer forward calls to the real instance. 220 */ 221 @VisibleForTesting detach()222 public void detach() { 223 mReplayedInsetsController = null; 224 } 225 226 @Override setSystemDrivenInsetsAnimationLoggingListener( @ullable WindowInsetsAnimationControlListener listener)227 public void setSystemDrivenInsetsAnimationLoggingListener( 228 @Nullable WindowInsetsAnimationControlListener listener) { 229 if (mReplayedInsetsController != null) { 230 mReplayedInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener); 231 } else { 232 mLoggingListener = listener; 233 } 234 } 235 236 @Override controlWindowInsetsAnimation(@nsetsType int types, long durationMillis, @Nullable Interpolator interpolator, CancellationSignal cancellationSignal, @NonNull WindowInsetsAnimationControlListener listener)237 public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis, 238 @Nullable Interpolator interpolator, 239 CancellationSignal cancellationSignal, 240 @NonNull WindowInsetsAnimationControlListener listener) { 241 if (mReplayedInsetsController != null) { 242 mReplayedInsetsController.controlWindowInsetsAnimation(types, durationMillis, 243 interpolator, cancellationSignal, listener); 244 } else { 245 listener.onCancelled(null); 246 } 247 } 248 249 private interface PendingRequest { replay(InsetsController controller)250 void replay(InsetsController controller); 251 } 252 253 private static class ShowRequest implements PendingRequest { 254 255 private final @InsetsType int mTypes; 256 ShowRequest(int types)257 public ShowRequest(int types) { 258 mTypes = types; 259 } 260 261 @Override replay(InsetsController controller)262 public void replay(InsetsController controller) { 263 controller.show(mTypes); 264 } 265 } 266 267 private static class HideRequest implements PendingRequest { 268 269 private final @InsetsType int mTypes; 270 HideRequest(int types)271 public HideRequest(int types) { 272 mTypes = types; 273 } 274 275 @Override replay(InsetsController controller)276 public void replay(InsetsController controller) { 277 controller.hide(mTypes); 278 } 279 } 280 } 281