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