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