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