• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 static android.view.InsetsController.ANIMATION_TYPE_NONE;
20 import static android.view.InsetsController.AnimationType;
21 import static android.view.InsetsController.DEBUG;
22 import static android.view.InsetsState.getDefaultVisibility;
23 import static android.view.InsetsState.toPublicType;
24 
25 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
26 
27 import android.annotation.IntDef;
28 import android.annotation.Nullable;
29 import android.graphics.Rect;
30 import android.util.Log;
31 import android.view.InsetsState.InternalInsetsType;
32 import android.view.SurfaceControl.Transaction;
33 import android.view.WindowInsets.Type.InsetsType;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.util.function.Supplier;
40 
41 /**
42  * Controls the visibility and animations of a single window insets source.
43  * @hide
44  */
45 public class InsetsSourceConsumer {
46 
47     @Retention(RetentionPolicy.SOURCE)
48     @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED})
49     @interface ShowResult {
50         /**
51          * Window type is ready to be shown, will be shown immidiately.
52          */
53         int SHOW_IMMEDIATELY = 0;
54         /**
55          * Result will be delayed. Window needs to be prepared or request is not from controller.
56          * Request will be delegated to controller and may or may not be shown.
57          */
58         int IME_SHOW_DELAYED = 1;
59         /**
60          * Window will not be shown because one of the conditions couldn't be met.
61          * (e.g. in IME's case, when no editor is focused.)
62          */
63         int IME_SHOW_FAILED = 2;
64     }
65 
66     protected final InsetsController mController;
67     protected boolean mRequestedVisible;
68     protected final InsetsState mState;
69     protected final @InternalInsetsType int mType;
70 
71     private static final String TAG = "InsetsSourceConsumer";
72     private final Supplier<Transaction> mTransactionSupplier;
73     private @Nullable InsetsSourceControl mSourceControl;
74     private boolean mHasWindowFocus;
75     private Rect mPendingFrame;
76     private Rect mPendingVisibleFrame;
77 
78     /**
79      * Indicates if we have the pending animation. When we have the control, we need to play the
80      * animation if the requested visibility is different from the current state. But if we haven't
81      * had a leash yet, we will set this flag, and play the animation once we get the leash.
82      */
83     private boolean mIsAnimationPending;
84 
InsetsSourceConsumer(@nternalInsetsType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller)85     public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state,
86             Supplier<Transaction> transactionSupplier, InsetsController controller) {
87         mType = type;
88         mState = state;
89         mTransactionSupplier = transactionSupplier;
90         mController = controller;
91         mRequestedVisible = getDefaultVisibility(type);
92     }
93 
94     /**
95      * Updates the control delivered from the server.
96 
97      * @param showTypes An integer array with a single entry that determines which types a show
98      *                  animation should be run after setting the control.
99      * @param hideTypes An integer array with a single entry that determines which types a hide
100      *                  animation should be run after setting the control.
101      */
setControl(@ullable InsetsSourceControl control, @InsetsType int[] showTypes, @InsetsType int[] hideTypes)102     public void setControl(@Nullable InsetsSourceControl control,
103             @InsetsType int[] showTypes, @InsetsType int[] hideTypes) {
104         if (mSourceControl == control) {
105             return;
106         }
107         SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null;
108 
109         final InsetsSourceControl lastControl = mSourceControl;
110         mSourceControl = control;
111         if (control != null) {
112             if (DEBUG) Log.d(TAG, String.format("setControl -> %s on %s",
113                     InsetsState.typeToString(control.getType()),
114                     mController.getHost().getRootViewTitle()));
115         }
116         if (mSourceControl == null) {
117             // We are loosing control
118             mController.notifyControlRevoked(this);
119 
120             // Check if we need to restore server visibility.
121             final InsetsSource source = mState.getSource(mType);
122             final boolean serverVisibility =
123                     mController.getLastDispatchedState().getSourceOrDefaultVisibility(mType);
124             if (source.isVisible() != serverVisibility) {
125                 source.setVisible(serverVisibility);
126                 mController.notifyVisibilityChanged();
127             }
128 
129             // For updateCompatSysUiVisibility
130             applyLocalVisibilityOverride();
131         } else {
132             // We are gaining control, and need to run an animation since previous state
133             // didn't match
134             final boolean requestedVisible = isRequestedVisibleAwaitingControl();
135             final boolean needAnimation = requestedVisible != mState.getSource(mType).isVisible();
136             if (control.getLeash() != null && (needAnimation || mIsAnimationPending)) {
137                 if (DEBUG) Log.d(TAG, String.format("Gaining control in %s, requestedVisible: %b",
138                         mController.getHost().getRootViewTitle(), requestedVisible));
139                 if (requestedVisible) {
140                     showTypes[0] |= toPublicType(getType());
141                 } else {
142                     hideTypes[0] |= toPublicType(getType());
143                 }
144                 mIsAnimationPending = false;
145             } else {
146                 if (needAnimation) {
147                     // We need animation but we haven't had a leash yet. Set this flag that when we
148                     // get the leash we can play the deferred animation.
149                     mIsAnimationPending = true;
150                 }
151                 // We are gaining control, but don't need to run an animation.
152                 // However make sure that the leash visibility is still up to date.
153                 if (applyLocalVisibilityOverride()) {
154                     mController.notifyVisibilityChanged();
155                 }
156 
157                 // If we have a new leash, make sure visibility is up-to-date, even though we
158                 // didn't want to run an animation above.
159                 SurfaceControl newLeash = mSourceControl.getLeash();
160                 if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) {
161                     applyHiddenToControl();
162                 }
163                 if (!requestedVisible && !mIsAnimationPending) {
164                     removeSurface();
165                 }
166             }
167         }
168         if (lastControl != null) {
169             lastControl.release(SurfaceControl::release);
170         }
171     }
172 
173     @VisibleForTesting
getControl()174     public InsetsSourceControl getControl() {
175         return mSourceControl;
176     }
177 
178     /**
179      * Determines if the consumer will be shown after control is available.
180      * Note: for system bars this method is same as {@link #isRequestedVisible()}.
181      *
182      * @return {@code true} if consumer has a pending show.
183      */
isRequestedVisibleAwaitingControl()184     protected boolean isRequestedVisibleAwaitingControl() {
185         return isRequestedVisible();
186     }
187 
getType()188     int getType() {
189         return mType;
190     }
191 
192     @VisibleForTesting
show(boolean fromIme)193     public void show(boolean fromIme) {
194         if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ",
195                 InsetsState.typeToString(mType), fromIme));
196         setRequestedVisible(true);
197     }
198 
199     @VisibleForTesting
hide()200     public void hide() {
201         if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s",
202                 InsetsState.typeToString(mType), mController.getHost().getRootViewTitle()));
203         setRequestedVisible(false);
204     }
205 
hide(boolean animationFinished, @AnimationType int animationType)206     void hide(boolean animationFinished, @AnimationType int animationType) {
207         hide();
208     }
209 
210     /**
211      * Called when current window gains focus
212      */
onWindowFocusGained()213     public void onWindowFocusGained() {
214         mHasWindowFocus = true;
215     }
216 
217     /**
218      * Called when current window loses focus.
219      */
onWindowFocusLost()220     public void onWindowFocusLost() {
221         mHasWindowFocus = false;
222     }
223 
hasWindowFocus()224     boolean hasWindowFocus() {
225         return mHasWindowFocus;
226     }
227 
applyLocalVisibilityOverride()228     boolean applyLocalVisibilityOverride() {
229         final InsetsSource source = mState.peekSource(mType);
230         final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType);
231         final boolean hasControl = mSourceControl != null;
232 
233         // We still need to let the legacy app know the visibility change even if we don't have the
234         // control. If we don't have the source, we don't change the requested visibility for making
235         // the callback behavior compatible.
236         mController.updateCompatSysUiVisibility(
237                 mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl);
238 
239         // If we don't have control, we are not able to change the visibility.
240         if (!hasControl) {
241             if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
242                     + mController.getHost().getRootViewTitle()
243                     + " requestedVisible " + mRequestedVisible);
244             return false;
245         }
246         if (isVisible == mRequestedVisible) {
247             return false;
248         }
249         if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b",
250                 mController.getHost().getRootViewTitle(), mRequestedVisible));
251         mState.getSource(mType).setVisible(mRequestedVisible);
252         return true;
253     }
254 
255     @VisibleForTesting
isRequestedVisible()256     public boolean isRequestedVisible() {
257         return mRequestedVisible;
258     }
259 
260     /**
261      * Request to show current window type.
262      *
263      * @param fromController {@code true} if request is coming from controller.
264      *                       (e.g. in IME case, controller is
265      *                       {@link android.inputmethodservice.InputMethodService}).
266      * @return @see {@link ShowResult}.
267      */
268     @VisibleForTesting
requestShow(boolean fromController)269     public @ShowResult int requestShow(boolean fromController) {
270         return ShowResult.SHOW_IMMEDIATELY;
271     }
272 
273     /**
274      * Reports that this source's perceptibility has changed
275      *
276      * @param perceptible true if the source is perceptible, false otherwise.
277      * @see InsetsAnimationControlCallbacks#reportPerceptible
278      */
onPerceptible(boolean perceptible)279     public void onPerceptible(boolean perceptible) {
280     }
281 
282     /**
283      * Notify listeners that window is now hidden.
284      */
notifyHidden()285     void notifyHidden() {
286         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
287     }
288 
289     /**
290      * Remove surface on which this consumer type is drawn.
291      */
removeSurface()292     public void removeSurface() {
293         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
294     }
295 
296     @VisibleForTesting(visibility = PACKAGE)
updateSource(InsetsSource newSource, @AnimationType int animationType)297     public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
298         InsetsSource source = mState.peekSource(mType);
299         if (source == null || animationType == ANIMATION_TYPE_NONE
300                 || source.getFrame().equals(newSource.getFrame())) {
301             mPendingFrame = null;
302             mPendingVisibleFrame = null;
303             mState.addSource(newSource);
304             return;
305         }
306 
307         // Frame is changing while animating. Keep note of the new frame but keep existing frame
308         // until animation is finished.
309         newSource = new InsetsSource(newSource);
310         mPendingFrame = new Rect(newSource.getFrame());
311         mPendingVisibleFrame = newSource.getVisibleFrame() != null
312                 ? new Rect(newSource.getVisibleFrame())
313                 : null;
314         newSource.setFrame(source.getFrame());
315         newSource.setVisibleFrame(source.getVisibleFrame());
316         mState.addSource(newSource);
317         if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
318     }
319 
320     @VisibleForTesting(visibility = PACKAGE)
notifyAnimationFinished()321     public boolean notifyAnimationFinished() {
322         if (mPendingFrame != null) {
323             InsetsSource source = mState.getSource(mType);
324             source.setFrame(mPendingFrame);
325             source.setVisibleFrame(mPendingVisibleFrame);
326             mPendingFrame = null;
327             mPendingVisibleFrame = null;
328             return true;
329         }
330         return false;
331     }
332 
333     /**
334      * Sets requested visibility from the client, regardless of whether we are able to control it at
335      * the moment.
336      */
setRequestedVisible(boolean requestedVisible)337     protected void setRequestedVisible(boolean requestedVisible) {
338         if (mRequestedVisible != requestedVisible) {
339             mRequestedVisible = requestedVisible;
340             mIsAnimationPending = false;
341             if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
342         }
343         if (applyLocalVisibilityOverride()) {
344             mController.notifyVisibilityChanged();
345         }
346     }
347 
applyHiddenToControl()348     private void applyHiddenToControl() {
349         if (mSourceControl == null || mSourceControl.getLeash() == null) {
350             return;
351         }
352 
353         final Transaction t = mTransactionSupplier.get();
354         if (DEBUG) Log.d(TAG, "applyHiddenToControl: " + mRequestedVisible);
355         if (mRequestedVisible) {
356             t.show(mSourceControl.getLeash());
357         } else {
358             t.hide(mSourceControl.getLeash());
359         }
360         t.apply();
361         onPerceptible(mRequestedVisible);
362     }
363 }
364