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         // We are loosing control
117         if (mSourceControl == null) {
118             mController.notifyControlRevoked(this);
119 
120             // Restore server visibility.
121             mState.getSource(getType()).setVisible(
122                     mController.getLastDispatchedState().getSource(getType()).isVisible());
123             applyLocalVisibilityOverride();
124         } else {
125             // We are gaining control, and need to run an animation since previous state
126             // didn't match
127             final boolean requestedVisible = isRequestedVisibleAwaitingControl();
128             final boolean needAnimation = requestedVisible != mState.getSource(mType).isVisible();
129             if (control.getLeash() != null && (needAnimation || mIsAnimationPending)) {
130                 if (DEBUG) Log.d(TAG, String.format("Gaining control in %s, requestedVisible: %b",
131                         mController.getHost().getRootViewTitle(), requestedVisible));
132                 if (requestedVisible) {
133                     showTypes[0] |= toPublicType(getType());
134                 } else {
135                     hideTypes[0] |= toPublicType(getType());
136                 }
137                 mIsAnimationPending = false;
138             } else {
139                 if (needAnimation) {
140                     // We need animation but we haven't had a leash yet. Set this flag that when we
141                     // get the leash we can play the deferred animation.
142                     mIsAnimationPending = true;
143                 }
144                 // We are gaining control, but don't need to run an animation.
145                 // However make sure that the leash visibility is still up to date.
146                 if (applyLocalVisibilityOverride()) {
147                     mController.notifyVisibilityChanged();
148                 }
149 
150                 // If we have a new leash, make sure visibility is up-to-date, even though we
151                 // didn't want to run an animation above.
152                 SurfaceControl newLeash = mSourceControl.getLeash();
153                 if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) {
154                     applyHiddenToControl();
155                 }
156                 if (!requestedVisible && !mIsAnimationPending) {
157                     removeSurface();
158                 }
159             }
160         }
161         if (lastControl != null) {
162             lastControl.release(SurfaceControl::release);
163         }
164     }
165 
166     @VisibleForTesting
getControl()167     public InsetsSourceControl getControl() {
168         return mSourceControl;
169     }
170 
171     /**
172      * Determines if the consumer will be shown after control is available.
173      * Note: for system bars this method is same as {@link #isRequestedVisible()}.
174      *
175      * @return {@code true} if consumer has a pending show.
176      */
isRequestedVisibleAwaitingControl()177     protected boolean isRequestedVisibleAwaitingControl() {
178         return isRequestedVisible();
179     }
180 
getType()181     int getType() {
182         return mType;
183     }
184 
185     @VisibleForTesting
show(boolean fromIme)186     public void show(boolean fromIme) {
187         if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ",
188                 InsetsState.typeToString(mType), fromIme));
189         setRequestedVisible(true);
190     }
191 
192     @VisibleForTesting
hide()193     public void hide() {
194         if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s",
195                 InsetsState.typeToString(mType), mController.getHost().getRootViewTitle()));
196         setRequestedVisible(false);
197     }
198 
hide(boolean animationFinished, @AnimationType int animationType)199     void hide(boolean animationFinished, @AnimationType int animationType) {
200         hide();
201     }
202 
203     /**
204      * Called when current window gains focus
205      */
onWindowFocusGained()206     public void onWindowFocusGained() {
207         mHasWindowFocus = true;
208     }
209 
210     /**
211      * Called when current window loses focus.
212      */
onWindowFocusLost()213     public void onWindowFocusLost() {
214         mHasWindowFocus = false;
215     }
216 
hasWindowFocus()217     boolean hasWindowFocus() {
218         return mHasWindowFocus;
219     }
220 
applyLocalVisibilityOverride()221     boolean applyLocalVisibilityOverride() {
222         final InsetsSource source = mState.peekSource(mType);
223         final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType);
224         final boolean hasControl = mSourceControl != null;
225 
226         // We still need to let the legacy app know the visibility change even if we don't have the
227         // control. If we don't have the source, we don't change the requested visibility for making
228         // the callback behavior compatible.
229         mController.updateCompatSysUiVisibility(
230                 mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl);
231 
232         // If we don't have control, we are not able to change the visibility.
233         if (!hasControl) {
234             if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
235                     + mController.getHost().getRootViewTitle()
236                     + " requestedVisible " + mRequestedVisible);
237             return false;
238         }
239         if (isVisible == mRequestedVisible) {
240             return false;
241         }
242         if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b",
243                 mController.getHost().getRootViewTitle(), mRequestedVisible));
244         mState.getSource(mType).setVisible(mRequestedVisible);
245         return true;
246     }
247 
248     @VisibleForTesting
isRequestedVisible()249     public boolean isRequestedVisible() {
250         return mRequestedVisible;
251     }
252 
253     /**
254      * Request to show current window type.
255      *
256      * @param fromController {@code true} if request is coming from controller.
257      *                       (e.g. in IME case, controller is
258      *                       {@link android.inputmethodservice.InputMethodService}).
259      * @return @see {@link ShowResult}.
260      */
261     @VisibleForTesting
requestShow(boolean fromController)262     public @ShowResult int requestShow(boolean fromController) {
263         return ShowResult.SHOW_IMMEDIATELY;
264     }
265 
266     /**
267      * Reports that this source's perceptibility has changed
268      *
269      * @param perceptible true if the source is perceptible, false otherwise.
270      * @see InsetsAnimationControlCallbacks#reportPerceptible
271      */
onPerceptible(boolean perceptible)272     public void onPerceptible(boolean perceptible) {
273     }
274 
275     /**
276      * Notify listeners that window is now hidden.
277      */
notifyHidden()278     void notifyHidden() {
279         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
280     }
281 
282     /**
283      * Remove surface on which this consumer type is drawn.
284      */
removeSurface()285     public void removeSurface() {
286         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
287     }
288 
289     @VisibleForTesting(visibility = PACKAGE)
updateSource(InsetsSource newSource, @AnimationType int animationType)290     public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
291         InsetsSource source = mState.peekSource(mType);
292         if (source == null || animationType == ANIMATION_TYPE_NONE
293                 || source.getFrame().equals(newSource.getFrame())) {
294             mPendingFrame = null;
295             mPendingVisibleFrame = null;
296             mState.addSource(newSource);
297             return;
298         }
299 
300         // Frame is changing while animating. Keep note of the new frame but keep existing frame
301         // until animation is finished.
302         newSource = new InsetsSource(newSource);
303         mPendingFrame = new Rect(newSource.getFrame());
304         mPendingVisibleFrame = newSource.getVisibleFrame() != null
305                 ? new Rect(newSource.getVisibleFrame())
306                 : null;
307         newSource.setFrame(source.getFrame());
308         newSource.setVisibleFrame(source.getVisibleFrame());
309         mState.addSource(newSource);
310         if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
311     }
312 
313     @VisibleForTesting(visibility = PACKAGE)
notifyAnimationFinished()314     public boolean notifyAnimationFinished() {
315         if (mPendingFrame != null) {
316             InsetsSource source = mState.getSource(mType);
317             source.setFrame(mPendingFrame);
318             source.setVisibleFrame(mPendingVisibleFrame);
319             mPendingFrame = null;
320             mPendingVisibleFrame = null;
321             return true;
322         }
323         return false;
324     }
325 
326     /**
327      * Sets requested visibility from the client, regardless of whether we are able to control it at
328      * the moment.
329      */
setRequestedVisible(boolean requestedVisible)330     protected void setRequestedVisible(boolean requestedVisible) {
331         if (mRequestedVisible != requestedVisible) {
332             mRequestedVisible = requestedVisible;
333             mIsAnimationPending = false;
334             if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
335         }
336         if (applyLocalVisibilityOverride()) {
337             mController.notifyVisibilityChanged();
338         }
339     }
340 
applyHiddenToControl()341     private void applyHiddenToControl() {
342         if (mSourceControl == null || mSourceControl.getLeash() == null) {
343             return;
344         }
345 
346         final Transaction t = mTransactionSupplier.get();
347         if (DEBUG) Log.d(TAG, "applyHiddenToControl: " + mRequestedVisible);
348         if (mRequestedVisible) {
349             t.show(mSourceControl.getLeash());
350         } else {
351             t.hide(mSourceControl.getLeash());
352         }
353         t.apply();
354         onPerceptible(mRequestedVisible);
355     }
356 }
357