• 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.ANIMATION_STATE;
23 import static android.view.InsetsSourceConsumerProto.HAS_WINDOW_FOCUS;
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.InsetsSourceConsumerProto.TYPE_NUMBER;
29 
30 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
31 
32 import android.annotation.IntDef;
33 import android.annotation.Nullable;
34 import android.graphics.Insets;
35 import android.graphics.Matrix;
36 import android.graphics.Point;
37 import android.graphics.Rect;
38 import android.os.IBinder;
39 import android.text.TextUtils;
40 import android.util.Log;
41 import android.util.proto.ProtoOutputStream;
42 import android.view.WindowInsets.Type.InsetsType;
43 import android.view.inputmethod.Flags;
44 import android.view.inputmethod.ImeTracker;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.internal.inputmethod.ImeTracing;
48 
49 import java.lang.annotation.Retention;
50 import java.lang.annotation.RetentionPolicy;
51 import java.util.Objects;
52 
53 /**
54  * Controls the visibility and animations of a single window insets source.
55  * @hide
56  */
57 public class InsetsSourceConsumer {
58 
59     @Retention(RetentionPolicy.SOURCE)
60     @IntDef(value = {
61             ShowResult.SHOW_IMMEDIATELY,
62             ShowResult.IME_SHOW_DELAYED,
63             ShowResult.IME_SHOW_FAILED
64     })
65     @interface ShowResult {
66         /**
67          * Window type is ready to be shown, will be shown immediately.
68          */
69         int SHOW_IMMEDIATELY = 0;
70         /**
71          * Result will be delayed. Window needs to be prepared or request is not from controller.
72          * Request will be delegated to controller and may or may not be shown.
73          */
74         int IME_SHOW_DELAYED = 1;
75         /**
76          * Window will not be shown because one of the conditions couldn't be met.
77          * (e.g. in IME's case, when no editor is focused.)
78          */
79         int IME_SHOW_FAILED = 2;
80     }
81 
82     protected static final int ANIMATION_STATE_NONE = 0;
83     protected static final int ANIMATION_STATE_SHOW = 1;
84     protected static final int ANIMATION_STATE_HIDE = 2;
85 
86     protected int mAnimationState = ANIMATION_STATE_NONE;
87 
88     protected final InsetsController mController;
89     protected final InsetsState mState;
90     private int mId;
91     @InsetsType
92     private final int mType;
93 
94     private static final String TAG = "InsetsSourceConsumer";
95     @Nullable
96     private InsetsSourceControl mSourceControl;
97     private boolean mHasWindowFocus;
98     private InsetsAnimationControlRunner.SurfaceParamsApplier mSurfaceParamsApplier =
99             InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT;
100     private final Matrix mTmpMatrix = new Matrix();
101 
102     /**
103      * Whether the view has focus returned by {@link #onWindowFocusGained(boolean)}.
104      */
105     private boolean mHasViewFocusWhenWindowFocusGain;
106     private Rect mPendingFrame;
107     private Rect mPendingVisibleFrame;
108 
109     /**
110      * @param id The ID of the consumed insets.
111      * @param type The {@link InsetsType} of the consumed insets.
112      * @param state The current {@link InsetsState} of the consumed insets.
113      * @param controller The {@link InsetsController} to use for insets interaction.
114      */
InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state, InsetsController controller)115     public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state,
116             InsetsController controller) {
117         mId = id;
118         mType = type;
119         mState = state;
120         mController = controller;
121     }
122 
123     /**
124      * Updates the control delivered from the server.
125      *
126      * @param showTypes An integer array with a single entry that determines which types a show
127      *                  animation should be run after setting the control.
128      * @param hideTypes An integer array with a single entry that determines which types a hide
129      *                  animation should be run after setting the control.
130      * @return Whether the control has changed from the server
131      */
setControl(@ullable InsetsSourceControl control, @InsetsType int[] showTypes, @InsetsType int[] hideTypes, @InsetsType int[] cancelTypes, @InsetsType int[] transientTypes)132     public boolean setControl(@Nullable InsetsSourceControl control,
133             @InsetsType int[] showTypes,
134             @InsetsType int[] hideTypes,
135             @InsetsType int[] cancelTypes,
136             @InsetsType int[] transientTypes) {
137         if (Objects.equals(mSourceControl, control)) {
138             if (mSourceControl != null && mSourceControl != control) {
139                 mSourceControl.release(SurfaceControl::release);
140                 mSourceControl = control;
141             }
142             return false;
143         }
144 
145         final InsetsSourceControl lastControl = mSourceControl;
146         mSourceControl = control;
147         if (control != null) {
148             if (DEBUG) Log.d(TAG, String.format("setControl -> %s on %s",
149                     WindowInsets.Type.toString(control.getType()),
150                     mController.getHost().getRootViewTitle()));
151         }
152         if (mSourceControl == null) {
153             // We are loosing control
154             mController.notifyControlRevoked(this);
155 
156             // Check if we need to restore server visibility.
157             final InsetsSource localSource = mState.peekSource(mId);
158             final InsetsSource serverSource = mController.getLastDispatchedState().peekSource(mId);
159             final boolean localVisible = localSource != null && localSource.isVisible();
160             final boolean serverVisible = serverSource != null && serverSource.isVisible();
161             if (localSource != null) {
162                 localSource.setVisible(serverVisible);
163             }
164             if (localVisible != serverVisible) {
165                 mController.notifyVisibilityChanged();
166             }
167 
168             // Reset the applier to the default one which has the most lightweight implementation.
169             setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT);
170         } else {
171             if (lastControl != null && !Insets.NONE.equals(lastControl.getInsetsHint())
172                     && InsetsSource.getInsetSide(lastControl.getInsetsHint())
173                             != InsetsSource.getInsetSide(control.getInsetsHint())) {
174                 // The source has been moved to a different side. The coordinates are stale.
175                 // Canceling existing animation if there is any.
176                 cancelTypes[0] |= mType;
177             }
178             final boolean requestedVisible = isRequestedVisibleAwaitingControl();
179             final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
180             final SurfaceControl newLeash = control.getLeash();
181             if (newLeash != null && (oldLeash == null || !newLeash.isSameSurface(oldLeash))
182                     && requestedVisible != control.isInitiallyVisible()) {
183                 // We are gaining leash, and need to run an animation since previous state
184                 // didn't match.
185                 if (DEBUG) Log.d(TAG, String.format("Gaining leash in %s, requestedVisible: %b",
186                         mController.getHost().getRootViewTitle(), requestedVisible));
187                 if (requestedVisible) {
188                     showTypes[0] |= mType;
189                 } else {
190                     hideTypes[0] |= mType;
191                 }
192                 if (lastControl != null && lastControl.isFake()) {
193                     transientTypes[0] |= mType;
194                 }
195             } else {
196                 // We are gaining control, but don't need to run an animation.
197                 // However make sure that the leash visibility is still up to date.
198                 if (applyLocalVisibilityOverride()) {
199                     mController.notifyVisibilityChanged();
200                 }
201 
202                 // If there is no animation controlling the leash, make sure the visibility and the
203                 // position is up-to-date.
204                 if (!mController.hasSurfaceAnimation(mType)) {
205                     applyRequestedVisibilityAndPositionToControl();
206                 }
207 
208                 // Remove the surface that owned by last control when it lost.
209                 if (!requestedVisible && lastControl == null) {
210                     removeSurface();
211                 }
212             }
213         }
214         if (lastControl != null) {
215             lastControl.release(SurfaceControl::release);
216         }
217         return true;
218     }
219 
220     @VisibleForTesting(visibility = PACKAGE)
getControl()221     public InsetsSourceControl getControl() {
222         return mSourceControl;
223     }
224 
225     /**
226      * Determines if the consumer will be shown after control is available.
227      *
228      * @return {@code true} if consumer has a pending show.
229      */
isRequestedVisibleAwaitingControl()230     protected boolean isRequestedVisibleAwaitingControl() {
231         return (mController.getRequestedVisibleTypes() & mType) != 0;
232     }
233 
getId()234     int getId() {
235         return mId;
236     }
237 
setId(int id)238     void setId(int id) {
239         mId = id;
240     }
241 
getType()242     @InsetsType int getType() {
243         return mType;
244     }
245 
246     /**
247      * Sets the SurfaceParamsApplier that the latest animation runner is using. The leash owned by
248      * this class is always applied by the applier, so that the transaction order can always be
249      * aligned with the calling sequence.
250      */
setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier applier)251     void setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier applier) {
252         mSurfaceParamsApplier = applier;
253     }
254 
255     /**
256      * Called right after the animation is started or finished.
257      */
258     @VisibleForTesting(visibility = PACKAGE)
onAnimationStateChanged(boolean running)259     public boolean onAnimationStateChanged(boolean running) {
260         boolean insetsChanged = false;
261         if (!running && mPendingFrame != null) {
262             final InsetsSource source = mState.peekSource(mId);
263             if (source != null) {
264                 source.setFrame(mPendingFrame);
265                 source.setVisibleFrame(mPendingVisibleFrame);
266                 insetsChanged = true;
267             }
268             mPendingFrame = null;
269             mPendingVisibleFrame = null;
270         }
271 
272         final boolean showRequested = isShowRequested();
273         final boolean cancelledForNewAnimation;
274         if (Flags.refactorInsetsController()) {
275             cancelledForNewAnimation =
276                     (mController.getCancelledForNewAnimationTypes() & mType) != 0;
277         } else {
278             cancelledForNewAnimation = (!running && showRequested)
279                     ? mAnimationState == ANIMATION_STATE_HIDE
280                     : mAnimationState == ANIMATION_STATE_SHOW;
281         }
282 
283         mAnimationState = running
284                 ? (showRequested ? ANIMATION_STATE_SHOW : ANIMATION_STATE_HIDE)
285                 : ANIMATION_STATE_NONE;
286 
287         // We apply the visibility override after the animation is started. We don't do this before
288         // that because we need to know the initial insets state while creating the animation.
289         // We also need to apply the override after the animation is finished because the requested
290         // visibility can be set when finishing the user animation.
291         // If the animation is cancelled because we are going to play a new animation with an
292         // opposite direction, don't apply it now but after the new animation is started.
293         if (!cancelledForNewAnimation) {
294             insetsChanged |= applyLocalVisibilityOverride();
295         }
296         return insetsChanged;
297     }
298 
isShowRequested()299     protected boolean isShowRequested() {
300         return (mController.getRequestedVisibleTypes() & getType()) != 0;
301     }
302 
303     /**
304      * Called when current window gains focus
305      */
onWindowFocusGained(boolean hasViewFocus)306     public void onWindowFocusGained(boolean hasViewFocus) {
307         mHasWindowFocus = true;
308         mHasViewFocusWhenWindowFocusGain = hasViewFocus;
309     }
310 
311     /**
312      * Called when current window loses focus.
313      */
onWindowFocusLost()314     public void onWindowFocusLost() {
315         mHasWindowFocus = false;
316     }
317 
hasViewFocusWhenWindowFocusGain()318     boolean hasViewFocusWhenWindowFocusGain() {
319         return mHasViewFocusWhenWindowFocusGain;
320     }
321 
322     @VisibleForTesting(visibility = PACKAGE)
applyLocalVisibilityOverride()323     public boolean applyLocalVisibilityOverride() {
324         if (Flags.refactorInsetsController()) {
325             if (mType == WindowInsets.Type.ime()) {
326                 ImeTracing.getInstance().triggerClientDump(
327                         "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
328                         mController.getHost().getInputMethodManager(), null /* icProto */);
329             }
330         }
331         final InsetsSource source = mState.peekSource(mId);
332         if (source == null) {
333             return false;
334         }
335         final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
336 
337         // If we don't have control or the leash (in case of the IME), we enforce the
338         // visibility to be hidden, as otherwise we would let the app know too early.
339         if (mSourceControl == null) {
340             if (DEBUG) {
341                 Log.d(TAG, TextUtils.formatSimple(
342                         "applyLocalVisibilityOverride: No control in %s for type %s, "
343                                 + "requestedVisible=%s",
344                         mController.getHost().getRootViewTitle(),
345                         WindowInsets.Type.toString(mType), requestedVisible));
346             }
347             return false;
348         }
349         if (Flags.refactorInsetsController()) {
350             // TODO(b/323136120) add a flag to the control, to define whether a leash is
351             //  needed and make it generic for all types
352             if (mId == InsetsSource.ID_IME && mSourceControl.getLeash() == null) {
353                 if (DEBUG) {
354                     Log.d(TAG, TextUtils.formatSimple(
355                             "applyLocalVisibilityOverride: Set the source visibility to false, as"
356                                     + " there is no leash yet for type %s in %s",
357                             WindowInsets.Type.toString(mType),
358                             mController.getHost().getRootViewTitle()));
359                 }
360                 boolean wasVisible = source.isVisible();
361                 source.setVisible(false);
362                 // only if it was visible before and is now hidden, we want to notify about the
363                 // changed state
364                 return wasVisible;
365             }
366         }
367         if (source.isVisible() == requestedVisible) {
368             return false;
369         }
370         if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b",
371                 mController.getHost().getRootViewTitle(), requestedVisible));
372         source.setVisible(requestedVisible);
373         return true;
374     }
375 
376     /**
377      * Request to show current window type.
378      *
379      * @param fromController {@code true} if request is coming from controller.
380      *                       (e.g. in IME case, controller is
381      *                       {@link android.inputmethodservice.InputMethodService}).
382      * @param statsToken the token tracking the current IME request or {@code null} otherwise.
383      *
384      * @implNote The {@code statsToken} is ignored here, and only handled in
385      * {@link ImeInsetsSourceConsumer} for IME animations only.
386      *
387      * @return @see {@link ShowResult}.
388      */
389     @VisibleForTesting(visibility = PACKAGE)
390     @ShowResult
requestShow(boolean fromController, @Nullable ImeTracker.Token statsToken)391     public int requestShow(boolean fromController, @Nullable ImeTracker.Token statsToken) {
392         return ShowResult.SHOW_IMMEDIATELY;
393     }
394 
requestHide(boolean fromController, @Nullable ImeTracker.Token statsToken)395     void requestHide(boolean fromController, @Nullable ImeTracker.Token statsToken) {
396         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
397     }
398 
399     /**
400      * Reports that this source's perceptibility has changed
401      *
402      * @param perceptible true if the source is perceptible, false otherwise.
403      * @see InsetsAnimationControlCallbacks#reportPerceptible
404      */
onPerceptible(boolean perceptible)405     public void onPerceptible(boolean perceptible) {
406         if (Flags.refactorInsetsController()) {
407             if (mType == WindowInsets.Type.ime()) {
408                 final IBinder window = mController.getHost().getWindowToken();
409                 if (window != null) {
410                     mController.getHost().getInputMethodManager().reportPerceptible(window,
411                             perceptible);
412                 }
413             }
414         }
415     }
416 
417     /**
418      * Remove surface on which this consumer type is drawn.
419      */
removeSurface()420     public void removeSurface() {
421         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
422         if (Flags.refactorInsetsController()) {
423             if (mType == WindowInsets.Type.ime()) {
424                 final IBinder window = mController.getHost().getWindowToken();
425                 if (window != null) {
426                     mController.getHost().getInputMethodManager().removeImeSurface(window);
427                 }
428             }
429         }
430     }
431 
432     @VisibleForTesting(visibility = PACKAGE)
updateSource(InsetsSource newSource, @AnimationType int animationType)433     public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
434         InsetsSource source = mState.peekSource(mId);
435         if (source == null || animationType == ANIMATION_TYPE_NONE
436                 || source.getFrame().equals(newSource.getFrame())) {
437             mPendingFrame = null;
438             mPendingVisibleFrame = null;
439             mState.addSource(newSource);
440             return;
441         }
442 
443         // Frame is changing while animating. Keep note of the new frame but keep existing frame
444         // until animation is finished.
445         mPendingFrame = new Rect(newSource.getFrame());
446         mPendingVisibleFrame = newSource.getVisibleFrame() != null
447                 ? new Rect(newSource.getVisibleFrame())
448                 : null;
449         newSource.setFrame(source.getFrame());
450         newSource.setVisibleFrame(source.getVisibleFrame());
451         mState.addSource(newSource);
452         if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
453     }
454 
applyRequestedVisibilityAndPositionToControl()455     private void applyRequestedVisibilityAndPositionToControl() {
456         if (mSourceControl == null) {
457             return;
458         }
459         final SurfaceControl leash = mSourceControl.getLeash();
460         if (leash == null) {
461             return;
462         }
463 
464         final boolean visible = (mController.getRequestedVisibleTypes() & mType) != 0;
465         final Point surfacePosition = mSourceControl.getSurfacePosition();
466 
467         if (DEBUG) Log.d(TAG, "applyRequestedVisibilityAndPositionToControl: visible=" + visible
468                 + " position=" + surfacePosition);
469 
470         mTmpMatrix.setTranslate(surfacePosition.x, surfacePosition.y);
471         mSurfaceParamsApplier.applySurfaceParams(
472                 new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(leash)
473                         .withVisibility(visible)
474                         .withAlpha(visible ? 1 : 0)
475                         .withMatrix(mTmpMatrix)
476                         .build());
477 
478         onPerceptible(visible);
479     }
480 
dumpDebug(ProtoOutputStream proto, long fieldId)481     void dumpDebug(ProtoOutputStream proto, long fieldId) {
482         final long token = proto.start(fieldId);
483         proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
484         proto.write(IS_REQUESTED_VISIBLE, isShowRequested());
485         if (mSourceControl != null) {
486             mSourceControl.dumpDebug(proto, SOURCE_CONTROL);
487         }
488         if (mPendingFrame != null) {
489             mPendingFrame.dumpDebug(proto, PENDING_FRAME);
490         }
491         if (mPendingVisibleFrame != null) {
492             mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME);
493         }
494         proto.write(ANIMATION_STATE, mAnimationState);
495         proto.write(TYPE_NUMBER, mType);
496         proto.end(token);
497     }
498 }
499