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.InsetsState.TYPE_IME;
20 import static android.view.InsetsState.toPublicType;
21 import static android.view.WindowInsets.Type.all;
22 
23 import android.animation.Animator;
24 import android.animation.AnimatorListenerAdapter;
25 import android.animation.ObjectAnimator;
26 import android.animation.TypeEvaluator;
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.graphics.Insets;
30 import android.graphics.Rect;
31 import android.os.RemoteException;
32 import android.util.ArraySet;
33 import android.util.Log;
34 import android.util.Pair;
35 import android.util.Property;
36 import android.util.SparseArray;
37 import android.view.InsetsSourceConsumer.ShowResult;
38 import android.view.InsetsState.InternalInsetType;
39 import android.view.SurfaceControl.Transaction;
40 import android.view.WindowInsets.Type;
41 import android.view.WindowInsets.Type.InsetType;
42 import android.view.animation.Interpolator;
43 import android.view.animation.PathInterpolator;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 
50 /**
51  * Implements {@link WindowInsetsController} on the client.
52  * @hide
53  */
54 public class InsetsController implements WindowInsetsController {
55 
56     private static final int ANIMATION_DURATION_SHOW_MS = 275;
57     private static final int ANIMATION_DURATION_HIDE_MS = 340;
58     private static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
59     private static final int DIRECTION_NONE = 0;
60     private static final int DIRECTION_SHOW = 1;
61     private static final int DIRECTION_HIDE = 2;
62 
63     @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
64     private @interface AnimationDirection{}
65 
66     /**
67      * Translation animation evaluator.
68      */
69     private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of(
70             0,
71             (int) (startValue.top + fraction * (endValue.top - startValue.top)),
72             0,
73             (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
74 
75     /**
76      * Linear animation property
77      */
78     private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> {
InsetsProperty()79         InsetsProperty() {
80             super(Insets.class, "Insets");
81         }
82 
83         @Override
get(WindowInsetsAnimationController object)84         public Insets get(WindowInsetsAnimationController object) {
85             return object.getCurrentInsets();
86         }
87         @Override
set(WindowInsetsAnimationController object, Insets value)88         public void set(WindowInsetsAnimationController object, Insets value) {
89             object.changeInsets(value);
90         }
91     }
92 
93     private final String TAG = "InsetsControllerImpl";
94 
95     private final InsetsState mState = new InsetsState();
96     private final InsetsState mTmpState = new InsetsState();
97 
98     private final Rect mFrame = new Rect();
99     private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>();
100     private final ViewRootImpl mViewRoot;
101 
102     private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
103     private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>();
104     private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>();
105     private WindowInsets mLastInsets;
106 
107     private boolean mAnimCallbackScheduled;
108 
109     private final Runnable mAnimCallback;
110 
111     private final Rect mLastLegacyContentInsets = new Rect();
112     private final Rect mLastLegacyStableInsets = new Rect();
113     private @AnimationDirection int mAnimationDirection;
114 
115     private int mPendingTypesToShow;
116 
117     private int mLastLegacySoftInputMode;
118 
InsetsController(ViewRootImpl viewRoot)119     public InsetsController(ViewRootImpl viewRoot) {
120         mViewRoot = viewRoot;
121         mAnimCallback = () -> {
122             mAnimCallbackScheduled = false;
123             if (mAnimationControls.isEmpty()) {
124                 return;
125             }
126 
127             mTmpFinishedControls.clear();
128             InsetsState state = new InsetsState(mState, true /* copySources */);
129             for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
130                 InsetsAnimationControlImpl control = mAnimationControls.get(i);
131                 if (mAnimationControls.get(i).applyChangeInsets(state)) {
132                     mTmpFinishedControls.add(control);
133                 }
134             }
135 
136             WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(),
137                     mLastInsets.shouldAlwaysConsumeSystemBars(), mLastInsets.getDisplayCutout(),
138                     mLastLegacyContentInsets, mLastLegacyStableInsets, mLastLegacySoftInputMode,
139                     null /* typeSideMap */);
140             mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets);
141 
142             for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) {
143                 dispatchAnimationFinished(mTmpFinishedControls.get(i).getAnimation());
144             }
145         };
146     }
147 
148     @VisibleForTesting
onFrameChanged(Rect frame)149     public void onFrameChanged(Rect frame) {
150         if (mFrame.equals(frame)) {
151             return;
152         }
153         mViewRoot.notifyInsetsChanged();
154         mFrame.set(frame);
155     }
156 
getState()157     public InsetsState getState() {
158         return mState;
159     }
160 
onStateChanged(InsetsState state)161     boolean onStateChanged(InsetsState state) {
162         if (mState.equals(state)) {
163             return false;
164         }
165         mState.set(state);
166         mTmpState.set(state, true /* copySources */);
167         applyLocalVisibilityOverride();
168         mViewRoot.notifyInsetsChanged();
169         if (!mState.equals(mTmpState)) {
170             sendStateToWindowManager();
171         }
172         return true;
173     }
174 
175     /**
176      * @see InsetsState#calculateInsets
177      */
178     @VisibleForTesting
calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars, DisplayCutout cutout, Rect legacyContentInsets, Rect legacyStableInsets, int legacySoftInputMode)179     public WindowInsets calculateInsets(boolean isScreenRound,
180             boolean alwaysConsumeSystemBars, DisplayCutout cutout, Rect legacyContentInsets,
181             Rect legacyStableInsets, int legacySoftInputMode) {
182         mLastLegacyContentInsets.set(legacyContentInsets);
183         mLastLegacyStableInsets.set(legacyStableInsets);
184         mLastLegacySoftInputMode = legacySoftInputMode;
185         mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeSystemBars, cutout,
186                 legacyContentInsets, legacyStableInsets, legacySoftInputMode,
187                 null /* typeSideMap */);
188         return mLastInsets;
189     }
190 
191     /**
192      * Called when the server has dispatched us a new set of inset controls.
193      */
onControlsChanged(InsetsSourceControl[] activeControls)194     public void onControlsChanged(InsetsSourceControl[] activeControls) {
195         if (activeControls != null) {
196             for (InsetsSourceControl activeControl : activeControls) {
197                 if (activeControl != null) {
198                     // TODO(b/122982984): Figure out why it can be null.
199                     mTmpControlArray.put(activeControl.getType(), activeControl);
200                 }
201             }
202         }
203 
204         // Ensure to update all existing source consumers
205         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
206             final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
207             final InsetsSourceControl control = mTmpControlArray.get(consumer.getType());
208 
209             // control may be null, but we still need to update the control to null if it got
210             // revoked.
211             consumer.setControl(control);
212         }
213 
214         // Ensure to create source consumers if not available yet.
215         for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
216             final InsetsSourceControl control = mTmpControlArray.valueAt(i);
217             getSourceConsumer(control.getType()).setControl(control);
218         }
219         mTmpControlArray.clear();
220     }
221 
222     @Override
show(@nsetType int types)223     public void show(@InsetType int types) {
224         show(types, false /* fromIme */);
225     }
226 
show(@nsetType int types, boolean fromIme)227     private void show(@InsetType int types, boolean fromIme) {
228         // TODO: Support a ResultReceiver for IME.
229         // TODO(b/123718661): Make show() work for multi-session IME.
230         int typesReady = 0;
231         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
232         for (int i = internalTypes.size() - 1; i >= 0; i--) {
233             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
234             if (mAnimationDirection == DIRECTION_HIDE) {
235                 // Only one animator (with multiple InsetType) can run at a time.
236                 // previous one should be cancelled for simplicity.
237                 cancelExistingAnimation();
238             } else if (consumer.isVisible()
239                     && (mAnimationDirection == DIRECTION_NONE
240                     || mAnimationDirection == DIRECTION_HIDE)) {
241                 // no-op: already shown or animating in (because window visibility is
242                 // applied before starting animation).
243                 // TODO: When we have more than one types: handle specific case when
244                 // show animation is going on, but the current type is not becoming visible.
245                 continue;
246             }
247             typesReady |= InsetsState.toPublicType(consumer.getType());
248         }
249         applyAnimation(typesReady, true /* show */, fromIme);
250     }
251 
252     @Override
hide(@nsetType int types)253     public void hide(@InsetType int types) {
254         int typesReady = 0;
255         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
256         for (int i = internalTypes.size() - 1; i >= 0; i--) {
257             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
258             if (mAnimationDirection == DIRECTION_SHOW) {
259                 cancelExistingAnimation();
260             } else if (!consumer.isVisible()
261                     && (mAnimationDirection == DIRECTION_NONE
262                     || mAnimationDirection == DIRECTION_HIDE)) {
263                 // no-op: already hidden or animating out.
264                 continue;
265             }
266             typesReady |= InsetsState.toPublicType(consumer.getType());
267         }
268         applyAnimation(typesReady, false /* show */, false /* fromIme */);
269     }
270 
271     @Override
controlWindowInsetsAnimation(@nsetType int types, WindowInsetsAnimationControlListener listener)272     public void controlWindowInsetsAnimation(@InsetType int types,
273             WindowInsetsAnimationControlListener listener) {
274         controlWindowInsetsAnimation(types, listener, false /* fromIme */);
275     }
276 
controlWindowInsetsAnimation(@nsetType int types, WindowInsetsAnimationControlListener listener, boolean fromIme)277     private void controlWindowInsetsAnimation(@InsetType int types,
278             WindowInsetsAnimationControlListener listener, boolean fromIme) {
279         // If the frame of our window doesn't span the entire display, the control API makes very
280         // little sense, as we don't deal with negative insets. So just cancel immediately.
281         if (!mState.getDisplayFrame().equals(mFrame)) {
282             listener.onCancelled();
283             return;
284         }
285         controlAnimationUnchecked(types, listener, mFrame, fromIme);
286     }
287 
controlAnimationUnchecked(@nsetType int types, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme)288     private void controlAnimationUnchecked(@InsetType int types,
289             WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme) {
290         if (types == 0) {
291             // nothing to animate.
292             return;
293         }
294         cancelExistingControllers(types);
295 
296         final ArraySet<Integer> internalTypes = mState.toInternalType(types);
297         final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();
298 
299         Pair<Integer, Boolean> typesReadyPair = collectConsumers(fromIme, internalTypes, consumers);
300         int typesReady = typesReadyPair.first;
301         boolean isReady = typesReadyPair.second;
302         if (!isReady) {
303             // IME isn't ready, all requested types would be shown once IME is ready.
304             mPendingTypesToShow = typesReady;
305             // TODO: listener for pending types.
306             return;
307         }
308 
309         // pending types from previous request.
310         typesReady = collectPendingConsumers(typesReady, consumers);
311 
312         if (typesReady == 0) {
313             listener.onCancelled();
314             return;
315         }
316 
317         final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
318                 frame, mState, listener, typesReady,
319                 () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
320         mAnimationControls.add(controller);
321     }
322 
323     /**
324      * @return Pair of (types ready to animate, is ready to animate).
325      */
collectConsumers(boolean fromIme, ArraySet<Integer> internalTypes, SparseArray<InsetsSourceConsumer> consumers)326     private Pair<Integer, Boolean> collectConsumers(boolean fromIme,
327             ArraySet<Integer> internalTypes, SparseArray<InsetsSourceConsumer> consumers) {
328         int typesReady = 0;
329         boolean isReady = true;
330         for (int i = internalTypes.size() - 1; i >= 0; i--) {
331             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
332             if (consumer.getControl() != null) {
333                 if (!consumer.isVisible()) {
334                     // Show request
335                     switch(consumer.requestShow(fromIme)) {
336                         case ShowResult.SHOW_IMMEDIATELY:
337                             typesReady |= InsetsState.toPublicType(consumer.getType());
338                             break;
339                         case ShowResult.SHOW_DELAYED:
340                             isReady = false;
341                             break;
342                         case ShowResult.SHOW_FAILED:
343                             // IME cannot be shown (since it didn't have focus), proceed
344                             // with animation of other types.
345                             if (mPendingTypesToShow != 0) {
346                                 // remove IME from pending because view no longer has focus.
347                                 mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME);
348                             }
349                             break;
350                     }
351                 } else {
352                     // Hide request
353                     // TODO: Move notifyHidden() to beginning of the hide animation
354                     // (when visibility actually changes using hideDirectly()).
355                     consumer.notifyHidden();
356                     typesReady |= InsetsState.toPublicType(consumer.getType());
357                 }
358                 consumers.put(consumer.getType(), consumer);
359             } else {
360                 // TODO: Let calling app know it's not possible, or wait
361                 // TODO: Remove it from types
362             }
363         }
364         return new Pair<>(typesReady, isReady);
365     }
366 
collectPendingConsumers(@nsetType int typesReady, SparseArray<InsetsSourceConsumer> consumers)367     private int collectPendingConsumers(@InsetType int typesReady,
368             SparseArray<InsetsSourceConsumer> consumers) {
369         if (mPendingTypesToShow != 0) {
370             typesReady |= mPendingTypesToShow;
371             final ArraySet<Integer> internalTypes = mState.toInternalType(mPendingTypesToShow);
372             for (int i = internalTypes.size() - 1; i >= 0; i--) {
373                 InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
374                 consumers.put(consumer.getType(), consumer);
375             }
376             mPendingTypesToShow = 0;
377         }
378         return typesReady;
379     }
380 
cancelExistingControllers(@nsetType int types)381     private void cancelExistingControllers(@InsetType int types) {
382         for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
383             InsetsAnimationControlImpl control = mAnimationControls.get(i);
384             if ((control.getTypes() & types) != 0) {
385                 cancelAnimation(control);
386             }
387         }
388     }
389 
390     @VisibleForTesting
notifyFinished(InsetsAnimationControlImpl controller, int shownTypes)391     public void notifyFinished(InsetsAnimationControlImpl controller, int shownTypes) {
392         mAnimationControls.remove(controller);
393         hideDirectly(controller.getTypes() & ~shownTypes);
394         showDirectly(controller.getTypes() & shownTypes);
395     }
396 
notifyControlRevoked(InsetsSourceConsumer consumer)397     void notifyControlRevoked(InsetsSourceConsumer consumer) {
398         for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
399             InsetsAnimationControlImpl control = mAnimationControls.get(i);
400             if ((control.getTypes() & toPublicType(consumer.getType())) != 0) {
401                 cancelAnimation(control);
402             }
403         }
404     }
405 
cancelAnimation(InsetsAnimationControlImpl control)406     private void cancelAnimation(InsetsAnimationControlImpl control) {
407         control.onCancelled();
408         mAnimationControls.remove(control);
409     }
410 
applyLocalVisibilityOverride()411     private void applyLocalVisibilityOverride() {
412         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
413             final InsetsSourceConsumer controller = mSourceConsumers.valueAt(i);
414             controller.applyLocalVisibilityOverride();
415         }
416     }
417 
418     @VisibleForTesting
getSourceConsumer(@nternalInsetType int type)419     public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetType int type) {
420         InsetsSourceConsumer controller = mSourceConsumers.get(type);
421         if (controller != null) {
422             return controller;
423         }
424         controller = createConsumerOfType(type);
425         mSourceConsumers.put(type, controller);
426         return controller;
427     }
428 
429     @VisibleForTesting
notifyVisibilityChanged()430     public void notifyVisibilityChanged() {
431         mViewRoot.notifyInsetsChanged();
432         sendStateToWindowManager();
433     }
434 
435     /**
436      * Called when current window gains focus.
437      */
onWindowFocusGained()438     public void onWindowFocusGained() {
439         getSourceConsumer(TYPE_IME).onWindowFocusGained();
440     }
441 
442     /**
443      * Called when current window loses focus.
444      */
onWindowFocusLost()445     public void onWindowFocusLost() {
446         getSourceConsumer(TYPE_IME).onWindowFocusLost();
447     }
448 
getViewRoot()449     ViewRootImpl getViewRoot() {
450         return mViewRoot;
451     }
452 
453     /**
454      * Used by {@link ImeInsetsSourceConsumer} when IME decides to be shown/hidden.
455      * @hide
456      */
457     @VisibleForTesting
applyImeVisibility(boolean setVisible)458     public void applyImeVisibility(boolean setVisible) {
459         if (setVisible) {
460             show(Type.IME, true /* fromIme */);
461         } else {
462             hide(Type.IME);
463         }
464     }
465 
createConsumerOfType(int type)466     private InsetsSourceConsumer createConsumerOfType(int type) {
467         if (type == TYPE_IME) {
468             return new ImeInsetsSourceConsumer(mState, Transaction::new, this);
469         } else {
470             return new InsetsSourceConsumer(type, mState, Transaction::new, this);
471         }
472     }
473 
474     /**
475      * Sends the local visibility state back to window manager.
476      */
sendStateToWindowManager()477     private void sendStateToWindowManager() {
478         InsetsState tmpState = new InsetsState();
479         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
480             final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
481             if (consumer.getControl() != null) {
482                 tmpState.addSource(mState.getSource(consumer.getType()));
483             }
484         }
485 
486         // TODO: Put this on a dispatcher thread.
487         try {
488             mViewRoot.mWindowSession.insetsModified(mViewRoot.mWindow, tmpState);
489         } catch (RemoteException e) {
490             Log.e(TAG, "Failed to call insetsModified", e);
491         }
492     }
493 
applyAnimation(@nsetType final int types, boolean show, boolean fromIme)494     private void applyAnimation(@InsetType final int types, boolean show, boolean fromIme) {
495         if (types == 0) {
496             // nothing to animate.
497             return;
498         }
499 
500         WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
501 
502             private WindowInsetsAnimationController mController;
503             private ObjectAnimator mAnimator;
504 
505             @Override
506             public void onReady(WindowInsetsAnimationController controller, int types) {
507                 mController = controller;
508                 if (show) {
509                     showDirectly(types);
510                 } else {
511                     hideDirectly(types);
512                 }
513                 mAnimator = ObjectAnimator.ofObject(
514                         controller,
515                         new InsetsProperty(),
516                         sEvaluator,
517                         show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
518                         show ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
519                 );
520                 mAnimator.setDuration(show
521                         ? ANIMATION_DURATION_SHOW_MS
522                         : ANIMATION_DURATION_HIDE_MS);
523                 mAnimator.setInterpolator(INTERPOLATOR);
524                 mAnimator.addListener(new AnimatorListenerAdapter() {
525 
526                     @Override
527                     public void onAnimationEnd(Animator animation) {
528                         onAnimationFinish();
529                     }
530                 });
531                 mAnimator.start();
532             }
533 
534             @Override
535             public void onCancelled() {
536                 mAnimator.cancel();
537             }
538 
539             private void onAnimationFinish() {
540                 mAnimationDirection = DIRECTION_NONE;
541                 mController.finish(show ? types : 0);
542             }
543         };
544 
545         // Show/hide animations always need to be relative to the display frame, in order that shown
546         // and hidden state insets are correct.
547         controlAnimationUnchecked(types, listener, mState.getDisplayFrame(), fromIme);
548     }
549 
hideDirectly(@nsetType int types)550     private void hideDirectly(@InsetType int types) {
551         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
552         for (int i = internalTypes.size() - 1; i >= 0; i--) {
553             getSourceConsumer(internalTypes.valueAt(i)).hide();
554         }
555     }
556 
showDirectly(@nsetType int types)557     private void showDirectly(@InsetType int types) {
558         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
559         for (int i = internalTypes.size() - 1; i >= 0; i--) {
560             getSourceConsumer(internalTypes.valueAt(i)).show();
561         }
562     }
563 
564     /**
565      * Cancel on-going animation to show/hide {@link InsetType}.
566      */
567     @VisibleForTesting
cancelExistingAnimation()568     public void cancelExistingAnimation() {
569         cancelExistingControllers(all());
570     }
571 
dump(String prefix, PrintWriter pw)572     void dump(String prefix, PrintWriter pw) {
573         pw.println(prefix); pw.println("InsetsController:");
574         mState.dump(prefix + "  ", pw);
575     }
576 
577     @VisibleForTesting
dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation)578     public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
579         mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
580     }
581 
582     @VisibleForTesting
dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation)583     public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
584         mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
585     }
586 
587     @VisibleForTesting
scheduleApplyChangeInsets()588     public void scheduleApplyChangeInsets() {
589         if (!mAnimCallbackScheduled) {
590             mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
591                     mAnimCallback, null /* token*/);
592             mAnimCallbackScheduled = true;
593         }
594     }
595 }
596