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