• 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.Rect;
37 import android.util.ArraySet;
38 import android.util.Log;
39 import android.util.proto.ProtoOutputStream;
40 import android.view.InsetsState.InternalInsetsType;
41 import android.view.SurfaceControl.Transaction;
42 import android.view.WindowInsets.Type.InsetsType;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.internal.inputmethod.ImeTracing;
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      * @param type The {@link InternalInsetsType} of the consumed insets.
96      * @param state The current {@link InsetsState} of the consumed insets.
97      * @param transactionSupplier The source of new {@link Transaction} instances. The supplier
98      *         must provide *new* instances, which will be explicitly closed by this class.
99      * @param controller The {@link InsetsController} to use for insets interaction.
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      * @return Whether the control has changed from the server
118      */
setControl(@ullable InsetsSourceControl control, @InsetsType int[] showTypes, @InsetsType int[] hideTypes)119     public boolean setControl(@Nullable InsetsSourceControl control,
120             @InsetsType int[] showTypes, @InsetsType int[] hideTypes) {
121         if (mType == ITYPE_IME) {
122             ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl",
123                     mController.getHost().getInputMethodManager(), null /* icProto */);
124         }
125         if (Objects.equals(mSourceControl, control)) {
126             if (mSourceControl != null && mSourceControl != control) {
127                 mSourceControl.release(SurfaceControl::release);
128                 mSourceControl = control;
129             }
130             return false;
131         }
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             final boolean requestedVisible = isRequestedVisibleAwaitingControl();
157             final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
158             final SurfaceControl newLeash = control.getLeash();
159             if (newLeash != null && (oldLeash == null || !newLeash.isSameSurface(oldLeash))
160                     && requestedVisible != control.isInitiallyVisible()) {
161                 // We are gaining leash, and need to run an animation since previous state
162                 // didn't match.
163                 if (DEBUG) Log.d(TAG, String.format("Gaining leash in %s, requestedVisible: %b",
164                         mController.getHost().getRootViewTitle(), requestedVisible));
165                 if (requestedVisible) {
166                     showTypes[0] |= toPublicType(getType());
167                 } else {
168                     hideTypes[0] |= toPublicType(getType());
169                 }
170             } else {
171                 // We are gaining control, but don't need to run an animation.
172                 // However make sure that the leash visibility is still up to date.
173                 if (applyLocalVisibilityOverride()) {
174                     mController.notifyVisibilityChanged();
175                 }
176 
177                 // If we have a new leash, make sure visibility is up-to-date, even though we
178                 // didn't want to run an animation above.
179                 if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) {
180                     applyRequestedVisibilityToControl();
181                 }
182 
183                 // Remove the surface that owned by last control when it lost.
184                 if (!requestedVisible && lastControl == null) {
185                     removeSurface();
186                 }
187             }
188         }
189         if (lastControl != null) {
190             lastControl.release(SurfaceControl::release);
191         }
192         return true;
193     }
194 
195     @VisibleForTesting
getControl()196     public InsetsSourceControl getControl() {
197         return mSourceControl;
198     }
199 
200     /**
201      * Determines if the consumer will be shown after control is available.
202      * Note: for system bars this method is same as {@link #isRequestedVisible()}.
203      *
204      * @return {@code true} if consumer has a pending show.
205      */
isRequestedVisibleAwaitingControl()206     protected boolean isRequestedVisibleAwaitingControl() {
207         return isRequestedVisible();
208     }
209 
getType()210     int getType() {
211         return mType;
212     }
213 
214     @VisibleForTesting
show(boolean fromIme)215     public void show(boolean fromIme) {
216         if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ",
217                 InsetsState.typeToString(mType), fromIme));
218         setRequestedVisible(true);
219     }
220 
221     @VisibleForTesting
hide()222     public void hide() {
223         if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s",
224                 InsetsState.typeToString(mType), mController.getHost().getRootViewTitle()));
225         setRequestedVisible(false);
226     }
227 
hide(boolean animationFinished, @AnimationType int animationType)228     void hide(boolean animationFinished, @AnimationType int animationType) {
229         hide();
230     }
231 
232     /**
233      * Called when current window gains focus
234      */
onWindowFocusGained(boolean hasViewFocus)235     public void onWindowFocusGained(boolean hasViewFocus) {
236         mHasWindowFocus = true;
237         mHasViewFocusWhenWindowFocusGain = hasViewFocus;
238     }
239 
240     /**
241      * Called when current window loses focus.
242      */
onWindowFocusLost()243     public void onWindowFocusLost() {
244         mHasWindowFocus = false;
245     }
246 
hasViewFocusWhenWindowFocusGain()247     boolean hasViewFocusWhenWindowFocusGain() {
248         return mHasViewFocusWhenWindowFocusGain;
249     }
250 
applyLocalVisibilityOverride()251     boolean applyLocalVisibilityOverride() {
252         final InsetsSource source = mState.peekSource(mType);
253         final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType);
254         final boolean hasControl = mSourceControl != null;
255 
256         if (mType == ITYPE_IME) {
257             ImeTracing.getInstance().triggerClientDump(
258                     "InsetsSourceConsumer#applyLocalVisibilityOverride",
259                     mController.getHost().getInputMethodManager(), null /* icProto */);
260         }
261 
262         updateCompatSysUiVisibility(hasControl, source, isVisible);
263 
264         // If we don't have control, we are not able to change the visibility.
265         if (!hasControl) {
266             if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
267                     + mController.getHost().getRootViewTitle()
268                     + " requestedVisible " + mRequestedVisible);
269             return false;
270         }
271         if (isVisible == mRequestedVisible) {
272             return false;
273         }
274         if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b",
275                 mController.getHost().getRootViewTitle(), mRequestedVisible));
276         mState.getSource(mType).setVisible(mRequestedVisible);
277         return true;
278     }
279 
updateCompatSysUiVisibility(boolean hasControl, InsetsSource source, boolean visible)280     private void updateCompatSysUiVisibility(boolean hasControl, InsetsSource source,
281             boolean visible) {
282         final @InsetsType int publicType = InsetsState.toPublicType(mType);
283         if (publicType != WindowInsets.Type.statusBars()
284                 && publicType != WindowInsets.Type.navigationBars()) {
285             // System UI visibility only controls status bars and navigation bars.
286             return;
287         }
288         final boolean compatVisible;
289         if (hasControl) {
290             compatVisible = mRequestedVisible;
291         } else if (source != null && !source.getFrame().isEmpty()) {
292             compatVisible = visible;
293         } else {
294             final ArraySet<Integer> types = InsetsState.toInternalType(publicType);
295             for (int i = types.size() - 1; i >= 0; i--) {
296                 final InsetsSource s = mState.peekSource(types.valueAt(i));
297                 if (s != null && !s.getFrame().isEmpty()) {
298                     // The compat system UI visibility would be updated by another consumer which
299                     // handles the same public insets type.
300                     return;
301                 }
302             }
303             // No one provides the public type. Use the requested visibility for making the callback
304             // behavior compatible.
305             compatVisible = mRequestedVisible;
306         }
307         mController.updateCompatSysUiVisibility(mType, compatVisible, hasControl);
308     }
309 
310     @VisibleForTesting
isRequestedVisible()311     public boolean isRequestedVisible() {
312         return mRequestedVisible;
313     }
314 
315     /**
316      * Request to show current window type.
317      *
318      * @param fromController {@code true} if request is coming from controller.
319      *                       (e.g. in IME case, controller is
320      *                       {@link android.inputmethodservice.InputMethodService}).
321      * @return @see {@link ShowResult}.
322      */
323     @VisibleForTesting
requestShow(boolean fromController)324     public @ShowResult int requestShow(boolean fromController) {
325         return ShowResult.SHOW_IMMEDIATELY;
326     }
327 
328     /**
329      * Reports that this source's perceptibility has changed
330      *
331      * @param perceptible true if the source is perceptible, false otherwise.
332      * @see InsetsAnimationControlCallbacks#reportPerceptible
333      */
onPerceptible(boolean perceptible)334     public void onPerceptible(boolean perceptible) {
335     }
336 
337     /**
338      * Notify listeners that window is now hidden.
339      */
notifyHidden()340     void notifyHidden() {
341         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
342     }
343 
344     /**
345      * Remove surface on which this consumer type is drawn.
346      */
removeSurface()347     public void removeSurface() {
348         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
349     }
350 
351     @VisibleForTesting(visibility = PACKAGE)
updateSource(InsetsSource newSource, @AnimationType int animationType)352     public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
353         InsetsSource source = mState.peekSource(mType);
354         if (source == null || animationType == ANIMATION_TYPE_NONE
355                 || source.getFrame().equals(newSource.getFrame())) {
356             mPendingFrame = null;
357             mPendingVisibleFrame = null;
358             mState.addSource(newSource);
359             return;
360         }
361 
362         // Frame is changing while animating. Keep note of the new frame but keep existing frame
363         // until animation is finished.
364         newSource = new InsetsSource(newSource);
365         mPendingFrame = new Rect(newSource.getFrame());
366         mPendingVisibleFrame = newSource.getVisibleFrame() != null
367                 ? new Rect(newSource.getVisibleFrame())
368                 : null;
369         newSource.setFrame(source.getFrame());
370         newSource.setVisibleFrame(source.getVisibleFrame());
371         mState.addSource(newSource);
372         if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
373     }
374 
375     @VisibleForTesting(visibility = PACKAGE)
notifyAnimationFinished()376     public boolean notifyAnimationFinished() {
377         if (mPendingFrame != null) {
378             InsetsSource source = mState.getSource(mType);
379             source.setFrame(mPendingFrame);
380             source.setVisibleFrame(mPendingVisibleFrame);
381             mPendingFrame = null;
382             mPendingVisibleFrame = null;
383             return true;
384         }
385         return false;
386     }
387 
388     /**
389      * Sets requested visibility from the client, regardless of whether we are able to control it at
390      * the moment.
391      */
setRequestedVisible(boolean requestedVisible)392     protected void setRequestedVisible(boolean requestedVisible) {
393         if (mRequestedVisible != requestedVisible) {
394             mRequestedVisible = requestedVisible;
395             mController.onRequestedVisibilityChanged(this);
396             if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
397         }
398         if (applyLocalVisibilityOverride()) {
399             mController.notifyVisibilityChanged();
400         }
401     }
402 
applyRequestedVisibilityToControl()403     private void applyRequestedVisibilityToControl() {
404         if (mSourceControl == null || mSourceControl.getLeash() == null) {
405             return;
406         }
407 
408         try (Transaction t = mTransactionSupplier.get()) {
409             if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
410             if (mRequestedVisible) {
411                 t.show(mSourceControl.getLeash());
412             } else {
413                 t.hide(mSourceControl.getLeash());
414             }
415             // Ensure the alpha value is aligned with the actual requested visibility.
416             t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
417             t.apply();
418         }
419         onPerceptible(mRequestedVisible);
420     }
421 
dumpDebug(ProtoOutputStream proto, long fieldId)422     void dumpDebug(ProtoOutputStream proto, long fieldId) {
423         final long token = proto.start(fieldId);
424         proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType));
425         proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
426         proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible);
427         if (mSourceControl != null) {
428             mSourceControl.dumpDebug(proto, SOURCE_CONTROL);
429         }
430         if (mPendingFrame != null) {
431             mPendingFrame.dumpDebug(proto, PENDING_FRAME);
432         }
433         if (mPendingVisibleFrame != null) {
434             mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME);
435         }
436         proto.end(token);
437     }
438 }
439