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