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