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