• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.wm.shell.common;
18 
19 import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL;
20 import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END;
21 import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START;
22 import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorListenerAdapter;
26 import android.animation.ValueAnimator;
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.content.ComponentName;
31 import android.content.res.Configuration;
32 import android.graphics.Point;
33 import android.graphics.Rect;
34 import android.os.RemoteException;
35 import android.util.EventLog;
36 import android.util.Slog;
37 import android.util.SparseArray;
38 import android.view.IDisplayWindowInsetsController;
39 import android.view.IWindowManager;
40 import android.view.InsetsSource;
41 import android.view.InsetsSourceControl;
42 import android.view.InsetsState;
43 import android.view.Surface;
44 import android.view.SurfaceControl;
45 import android.view.WindowInsets;
46 import android.view.WindowInsets.Type.InsetsType;
47 import android.view.animation.Interpolator;
48 import android.view.animation.PathInterpolator;
49 import android.view.inputmethod.ImeTracker;
50 import android.view.inputmethod.InputMethodManagerGlobal;
51 
52 import androidx.annotation.VisibleForTesting;
53 
54 import com.android.internal.inputmethod.SoftInputShowHideReason;
55 import com.android.wm.shell.shared.TransactionPool;
56 import com.android.wm.shell.sysui.ShellInit;
57 
58 import java.util.ArrayList;
59 import java.util.Objects;
60 import java.util.concurrent.Executor;
61 
62 /**
63  * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode.
64  */
65 public class DisplayImeController implements DisplayController.OnDisplaysChangedListener {
66     private static final String TAG = "DisplayImeController";
67 
68     private static final boolean DEBUG = false;
69 
70     // NOTE: All these constants came from InsetsController.
71     public static final int ANIMATION_DURATION_SHOW_MS = 275;
72     public static final int ANIMATION_DURATION_HIDE_MS = 340;
73     public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
74     private static final int DIRECTION_NONE = 0;
75     private static final int DIRECTION_SHOW = 1;
76     private static final int DIRECTION_HIDE = 2;
77     private static final int FLOATING_IME_BOTTOM_INSET = -80;
78 
79     protected final IWindowManager mWmService;
80     protected final Executor mMainExecutor;
81     private final TransactionPool mTransactionPool;
82     private final DisplayController mDisplayController;
83     private final DisplayInsetsController mDisplayInsetsController;
84     private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
85     private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
86 
87 
DisplayImeController(IWindowManager wmService, ShellInit shellInit, DisplayController displayController, DisplayInsetsController displayInsetsController, TransactionPool transactionPool, Executor mainExecutor)88     public DisplayImeController(IWindowManager wmService,
89             ShellInit shellInit,
90             DisplayController displayController,
91             DisplayInsetsController displayInsetsController,
92             TransactionPool transactionPool,
93             Executor mainExecutor) {
94         mWmService = wmService;
95         mDisplayController = displayController;
96         mDisplayInsetsController = displayInsetsController;
97         mMainExecutor = mainExecutor;
98         mTransactionPool = transactionPool;
99         shellInit.addInitCallback(this::onInit, this);
100     }
101 
102     /**
103      * Starts monitor displays changes and set insets controller for each displays.
104      */
onInit()105     public void onInit() {
106         mDisplayController.addDisplayWindowListener(this);
107     }
108 
109     @Override
onDisplayAdded(int displayId)110     public void onDisplayAdded(int displayId) {
111         // Add's a system-ui window-manager specifically for ime. This type is special because
112         // WM will defer IME inset handling to it in multi-window scenarious.
113         PerDisplay pd = new PerDisplay(displayId,
114                 mDisplayController.getDisplayLayout(displayId).rotation());
115         pd.register();
116         mImePerDisplay.put(displayId, pd);
117     }
118 
119     @Override
onDisplayConfigurationChanged(int displayId, Configuration newConfig)120     public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
121         PerDisplay pd = mImePerDisplay.get(displayId);
122         if (pd == null) {
123             return;
124         }
125         if (mDisplayController.getDisplayLayout(displayId).rotation()
126                 != pd.mRotation && isImeShowing(displayId)) {
127             pd.startAnimation(true, false /* forceRestart */,
128                     SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED);
129         }
130     }
131 
132     @Override
onDisplayRemoved(int displayId)133     public void onDisplayRemoved(int displayId) {
134         PerDisplay pd = mImePerDisplay.get(displayId);
135         if (pd == null) {
136             return;
137         }
138         pd.unregister();
139         mImePerDisplay.remove(displayId);
140     }
141 
isImeShowing(int displayId)142     private boolean isImeShowing(int displayId) {
143         PerDisplay pd = mImePerDisplay.get(displayId);
144         if (pd == null) {
145             return false;
146         }
147         final InsetsSource imeSource = pd.mInsetsState.peekSource(InsetsSource.ID_IME);
148         return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible();
149     }
150 
dispatchPositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)151     private void dispatchPositionChanged(int displayId, int imeTop,
152             SurfaceControl.Transaction t) {
153         synchronized (mPositionProcessors) {
154             for (ImePositionProcessor pp : mPositionProcessors) {
155                 pp.onImePositionChanged(displayId, imeTop, t);
156             }
157         }
158     }
159 
dispatchImeRequested(int displayId, boolean isRequested)160     private void dispatchImeRequested(int displayId, boolean isRequested) {
161         synchronized (mPositionProcessors) {
162             for (ImePositionProcessor pp : mPositionProcessors) {
163                 pp.onImeRequested(displayId, isRequested);
164             }
165         }
166     }
167 
168     @ImePositionProcessor.ImeAnimationFlags
dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, boolean show, boolean isFloating, SurfaceControl.Transaction t)169     private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop,
170             boolean show, boolean isFloating, SurfaceControl.Transaction t) {
171         synchronized (mPositionProcessors) {
172             int flags = 0;
173             for (ImePositionProcessor pp : mPositionProcessors) {
174                 flags |= pp.onImeStartPositioning(
175                         displayId, hiddenTop, shownTop, show, isFloating, t);
176             }
177             return flags;
178         }
179     }
180 
dispatchEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t)181     private void dispatchEndPositioning(int displayId, boolean cancel,
182             SurfaceControl.Transaction t) {
183         synchronized (mPositionProcessors) {
184             for (ImePositionProcessor pp : mPositionProcessors) {
185                 pp.onImeEndPositioning(displayId, cancel, t);
186             }
187         }
188     }
189 
dispatchImeControlTargetChanged(int displayId, boolean controlling)190     private void dispatchImeControlTargetChanged(int displayId, boolean controlling) {
191         synchronized (mPositionProcessors) {
192             for (ImePositionProcessor pp : mPositionProcessors) {
193                 pp.onImeControlTargetChanged(displayId, controlling);
194             }
195         }
196     }
197 
dispatchVisibilityChanged(int displayId, boolean isShowing)198     private void dispatchVisibilityChanged(int displayId, boolean isShowing) {
199         synchronized (mPositionProcessors) {
200             for (ImePositionProcessor pp : mPositionProcessors) {
201                 pp.onImeVisibilityChanged(displayId, isShowing);
202             }
203         }
204     }
205 
206     /**
207      * Adds an {@link ImePositionProcessor} to be called during ime position updates.
208      */
addPositionProcessor(ImePositionProcessor processor)209     public void addPositionProcessor(ImePositionProcessor processor) {
210         synchronized (mPositionProcessors) {
211             if (mPositionProcessors.contains(processor)) {
212                 return;
213             }
214             mPositionProcessors.add(processor);
215         }
216     }
217 
218     /**
219      * Removes an {@link ImePositionProcessor} to be called during ime position updates.
220      */
removePositionProcessor(ImePositionProcessor processor)221     public void removePositionProcessor(ImePositionProcessor processor) {
222         synchronized (mPositionProcessors) {
223             mPositionProcessors.remove(processor);
224         }
225     }
226 
227     /** Hides the IME for Bubbles when the device is locked. */
hideImeForBubblesWhenLocked(int displayId)228     public void hideImeForBubblesWhenLocked(int displayId) {
229         PerDisplay pd = mImePerDisplay.get(displayId);
230         InsetsSourceControl imeSourceControl = pd.getImeSourceControl();
231         if (imeSourceControl != null) {
232             ImeTracker.Token imeStatsToken = imeSourceControl.getImeStatsToken();
233             if (imeStatsToken != null) {
234                 pd.setImeInputTargetRequestedVisibility(false, imeStatsToken);
235             }
236         }
237     }
238 
239     /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */
240     public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener {
241         final int mDisplayId;
242         final InsetsState mInsetsState = new InsetsState();
243         boolean mImeRequestedVisible =
244                 (WindowInsets.Type.defaultVisible() & WindowInsets.Type.ime()) != 0;
245         InsetsSourceControl mImeSourceControl = null;
246         int mAnimationDirection = DIRECTION_NONE;
247         ValueAnimator mAnimation = null;
248         int mRotation = Surface.ROTATION_0;
249         boolean mImeShowing = false;
250         final Rect mImeFrame = new Rect();
251         boolean mAnimateAlpha = true;
252 
PerDisplay(int displayId, int initialRotation)253         public PerDisplay(int displayId, int initialRotation) {
254             mDisplayId = displayId;
255             mRotation = initialRotation;
256         }
257 
register()258         public void register() {
259             mDisplayInsetsController.addInsetsChangedListener(mDisplayId, this);
260         }
261 
unregister()262         public void unregister() {
263             mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, this);
264         }
265 
266         @Override
insetsChanged(InsetsState insetsState)267         public void insetsChanged(InsetsState insetsState) {
268             if (mInsetsState.equals(insetsState)) {
269                 return;
270             }
271 
272             if (!android.view.inputmethod.Flags.refactorInsetsController()) {
273                 updateImeVisibility(insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME,
274                         WindowInsets.Type.ime()));
275             }
276 
277             final InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
278             final Rect newFrame = newSource != null ? newSource.getFrame() : null;
279             final boolean newSourceVisible = newSource != null && newSource.isVisible();
280             final InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
281             final Rect oldFrame = oldSource != null ? oldSource.getFrame() : null;
282 
283             mInsetsState.set(insetsState, true /* copySources */);
284             if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) {
285                 if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
286                 startAnimation(mImeShowing, true /* forceRestart */,
287                         SoftInputShowHideReason.DISPLAY_INSETS_CHANGED);
288             }
289         }
290 
291         @Override
292         @VisibleForTesting
insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)293         public void insetsControlChanged(InsetsState insetsState,
294                 InsetsSourceControl[] activeControls) {
295             insetsChanged(insetsState);
296             InsetsSourceControl imeSourceControl = null;
297             if (activeControls != null) {
298                 for (InsetsSourceControl activeControl : activeControls) {
299                     if (activeControl == null) {
300                         continue;
301                     }
302                     if (activeControl.getType() == WindowInsets.Type.ime()) {
303                         imeSourceControl = activeControl;
304                     }
305                 }
306             }
307 
308             final boolean hadImeSourceControl = mImeSourceControl != null;
309             final boolean hasImeSourceControl = imeSourceControl != null;
310             if (hadImeSourceControl != hasImeSourceControl) {
311                 dispatchImeControlTargetChanged(mDisplayId, hasImeSourceControl);
312             }
313             final boolean hasImeLeash = hasImeSourceControl && imeSourceControl.getLeash() != null;
314 
315             boolean pendingImeStartAnimation = false;
316             boolean positionChanged = false;
317             if (hasImeLeash) {
318                 if (mAnimation != null) {
319                     final Point lastSurfacePosition = hadImeSourceControl
320                             ? mImeSourceControl.getSurfacePosition() : null;
321                     positionChanged = !imeSourceControl.getSurfacePosition().equals(
322                             lastSurfacePosition);
323                 } else {
324                     if (!haveSameLeash(mImeSourceControl, imeSourceControl)) {
325                         if (android.view.inputmethod.Flags.refactorInsetsController()) {
326                             pendingImeStartAnimation = true;
327                             // The starting point for the IME should be it's previous state
328                             // (whether it is initiallyVisible or not)
329                             updateImeVisibility(imeSourceControl.isInitiallyVisible());
330                         }
331                         applyVisibilityToLeash(imeSourceControl);
332                     }
333                     if (!android.view.inputmethod.Flags.refactorInsetsController()) {
334                         if (!mImeShowing) {
335                             removeImeSurface(mDisplayId);
336                         }
337                     }
338                 }
339             } else {
340                 if (!android.view.inputmethod.Flags.refactorInsetsController()
341                         && mAnimation != null) {
342                     // we don't want to cancel the hide animation, when the control is lost, but
343                     // continue the bar to slide to the end (even without visible IME)
344                     mAnimation.cancel();
345                 } else if (android.view.inputmethod.Flags.refactorInsetsController() && mImeShowing
346                         && mAnimation == null) {
347                     // There is no leash, so the IME cannot be in a showing state
348                     updateImeVisibility(false);
349                 }
350             }
351 
352             // Make mImeSourceControl point to the new control before starting the animation.
353             if (hadImeSourceControl && mImeSourceControl != imeSourceControl) {
354                 mImeSourceControl.release(SurfaceControl::release);
355                 if (android.view.inputmethod.Flags.refactorInsetsController()
356                         && !hasImeLeash && mAnimation != null) {
357                     // In case of losing the leash, the animation should be cancelled.
358                     mAnimation.cancel();
359                 }
360             }
361             mImeSourceControl = imeSourceControl;
362 
363             if (positionChanged) {
364                 if (android.view.inputmethod.Flags.refactorInsetsController()) {
365                     // For showing the IME, the leash has to be available first. Hiding
366                     // the IME happens directly via {@link #hideInsets} (triggered by
367                     // setImeInputTargetRequestedVisibility) while the leash is not gone
368                     // yet.
369                     pendingImeStartAnimation = true;
370                 } else {
371                     startAnimation(mImeShowing, true /* forceRestart */,
372                             SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED);
373                 }
374             }
375 
376             if (android.view.inputmethod.Flags.refactorInsetsController()) {
377                 if (pendingImeStartAnimation) {
378                     startAnimation(mImeRequestedVisible, true /* forceRestart */);
379                 }
380             }
381         }
382 
applyVisibilityToLeash(InsetsSourceControl imeSourceControl)383         private void applyVisibilityToLeash(InsetsSourceControl imeSourceControl) {
384             SurfaceControl leash = imeSourceControl.getLeash();
385             if (leash != null) {
386                 SurfaceControl.Transaction t = mTransactionPool.acquire();
387                 if (mImeShowing) {
388                     t.show(leash);
389                 } else {
390                     t.hide(leash);
391                 }
392                 t.apply();
393                 mTransactionPool.release(t);
394             }
395         }
396 
397         @Override
showInsets(@nsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)398         public void showInsets(@InsetsType int types, boolean fromIme,
399                 @Nullable ImeTracker.Token statsToken) {
400             if ((types & WindowInsets.Type.ime()) == 0) {
401                 return;
402             }
403             if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
404             startAnimation(true /* show */, false /* forceRestart */, statsToken);
405         }
406 
407         @Override
hideInsets(@nsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)408         public void hideInsets(@InsetsType int types, boolean fromIme,
409                 @Nullable ImeTracker.Token statsToken) {
410             if ((types & WindowInsets.Type.ime()) == 0) {
411                 return;
412             }
413             if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
414             startAnimation(false /* show */, false /* forceRestart */, statsToken);
415         }
416 
417         @Override
topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes)418         public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) {
419             // Do nothing
420         }
421 
422         @Override
423         // TODO(b/335404678): pass control target
setImeInputTargetRequestedVisibility(boolean visible, @NonNull ImeTracker.Token statsToken)424         public void setImeInputTargetRequestedVisibility(boolean visible,
425                 @NonNull ImeTracker.Token statsToken) {
426             if (android.view.inputmethod.Flags.refactorInsetsController()) {
427                 ImeTracker.forLogging().onProgress(statsToken,
428                         ImeTracker.PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE);
429                 mImeRequestedVisible = visible;
430                 dispatchImeRequested(mDisplayId, mImeRequestedVisible);
431 
432                 // In the case that the IME becomes visible, but we have the control with leash
433                 // already (e.g., when focussing an editText in activity B, while and editText in
434                 // activity A is focussed), we will not get a call of #insetsControlChanged, and
435                 // therefore have to start the show animation from here
436                 if (visible || mImeShowing) {
437                     // only start the animation if we're either already showing or becoming visible.
438                     // otherwise starting another hide animation causes flickers.
439                     startAnimation(mImeRequestedVisible /* show */, false /* forceRestart */,
440                             statsToken);
441                 }
442 
443                 boolean hideAnimOngoing;
444                 boolean reportVisible;
445                 if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
446                     hideAnimOngoing = false;
447                     reportVisible = mImeRequestedVisible;
448                 } else {
449                     // In case of a hide, the statsToken should not been send yet (as the animation
450                     // is still ongoing). It will be sent at the end of the animation.
451                     hideAnimOngoing = !mImeRequestedVisible && mAnimation != null;
452                     reportVisible = mImeRequestedVisible || mAnimation != null;
453                 }
454                 setVisibleDirectly(reportVisible, hideAnimOngoing ? null : statsToken);
455             }
456         }
457 
458         /**
459          * Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
460          */
setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken)461         private void setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken) {
462             mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible);
463             int visibleTypes = visible ? WindowInsets.Type.ime() : 0;
464             try {
465                 mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId,
466                         visibleTypes, WindowInsets.Type.ime(), statsToken);
467             } catch (RemoteException e) {
468             }
469         }
470 
setAnimating(boolean imeAnimationOngoing, @Nullable ImeTracker.Token statsToken)471         private void setAnimating(boolean imeAnimationOngoing,
472                 @Nullable ImeTracker.Token statsToken) {
473             int animatingTypes = imeAnimationOngoing ? WindowInsets.Type.ime() : 0;
474             try {
475                 mWmService.updateDisplayWindowAnimatingTypes(mDisplayId, animatingTypes,
476                         statsToken);
477             } catch (RemoteException e) {
478             }
479         }
480 
imeTop(float surfaceOffset, float surfacePositionY)481         private int imeTop(float surfaceOffset, float surfacePositionY) {
482             // surfaceOffset is already offset by the surface's top inset, so we need to subtract
483             // the top inset so that the return value is in screen coordinates.
484             return mImeFrame.top + (int) (surfaceOffset - surfacePositionY);
485         }
486 
calcIsFloating(InsetsSource imeSource)487         private boolean calcIsFloating(InsetsSource imeSource) {
488             final Rect frame = imeSource.getFrame();
489             if (frame.height() == 0) {
490                 return true;
491             }
492             // Some Floating Input Methods will still report a frame, but the frame is actually
493             // a nav-bar inset created by WM and not part of the IME (despite being reported as
494             // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar
495             // frame height so any reported frame that is <= nav-bar frame height is assumed to
496             // be floating.
497             return frame.height() <= mDisplayController.getDisplayLayout(mDisplayId)
498                     .navBarFrameHeight();
499         }
500 
startAnimation(final boolean show, final boolean forceRestart)501         private void startAnimation(final boolean show, final boolean forceRestart) {
502             final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
503             if (imeSource == null || mImeSourceControl == null) {
504                 return;
505             }
506             // TODO(b/353463205): For hide: this still has the statsToken from the previous show
507             //  request
508             final var statsToken = mImeSourceControl.getImeStatsToken();
509 
510             startAnimation(show, forceRestart, statsToken);
511         }
512 
startAnimation(final boolean show, final boolean forceRestart, @SoftInputShowHideReason int reason)513         private void startAnimation(final boolean show, final boolean forceRestart,
514                 @SoftInputShowHideReason int reason) {
515             final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
516             if (imeSource == null || mImeSourceControl == null) {
517                 return;
518             }
519             final ImeTracker.Token statsToken;
520             if (android.view.inputmethod.Flags.refactorInsetsController()
521                     && mImeSourceControl.getImeStatsToken() != null) {
522                 statsToken = mImeSourceControl.getImeStatsToken();
523             } else {
524                 statsToken = ImeTracker.forLogging().onStart(
525                         show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE,
526                         ImeTracker.ORIGIN_WM_SHELL, reason, false /* fromUser */);
527             }
528             startAnimation(show, forceRestart, statsToken);
529         }
530 
startAnimation(final boolean show, final boolean forceRestart, @NonNull final ImeTracker.Token statsToken)531         private void startAnimation(final boolean show, final boolean forceRestart,
532                 @NonNull final ImeTracker.Token statsToken) {
533             if (mImeSourceControl == null || mImeSourceControl.getLeash() == null) {
534                 if (DEBUG) Slog.d(TAG, "No leash available, not starting the animation.");
535                 return;
536             }
537             if (android.view.inputmethod.Flags.refactorInsetsController()) {
538                 if (!mImeRequestedVisible && show) {
539                     // we have a control with leash, but the IME was not requested visible before,
540                     // therefore aborting the show animation.
541                     Slog.e(TAG, "IME was not requested visible, not starting the show animation.");
542                     // TODO(b/353463205) fail statsToken here
543                     return;
544                 }
545             }
546             final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
547             if (imeSource == null) {
548                 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
549                 return;
550             }
551             final Rect newFrame = imeSource.getFrame();
552             final boolean isFloating = calcIsFloating(imeSource) && show;
553             if (isFloating) {
554                 // This is a "floating" or "expanded" IME, so to get animations, just
555                 // pretend the ime has some size just below the screen.
556                 mImeFrame.set(newFrame);
557                 final int floatingInset = (int) (mDisplayController.getDisplayLayout(mDisplayId)
558                         .density() * FLOATING_IME_BOTTOM_INSET);
559                 mImeFrame.bottom -= floatingInset;
560             } else if (newFrame.height() != 0) {
561                 // Don't set a new frame if it's empty and hiding -- this maintains continuity
562                 mImeFrame.set(newFrame);
563             }
564             if (DEBUG) {
565                 Slog.d(TAG, "Run startAnim  show:" + show + "  was:"
566                         + (mAnimationDirection == DIRECTION_SHOW ? "SHOW"
567                         : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE")));
568             }
569             if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
570                     || (mAnimationDirection == DIRECTION_HIDE && !show)) {
571                 ImeTracker.forLogging().onCancelled(
572                         statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
573                 return;
574             }
575             boolean seek = false;
576             float seekValue = 0;
577             if (mAnimation != null) {
578                 if (mAnimation.isRunning()) {
579                     seekValue = (float) mAnimation.getAnimatedValue();
580                     seek = true;
581                 }
582                 mAnimation.cancel();
583             }
584             final InsetsSourceControl animatingControl = new InsetsSourceControl(mImeSourceControl);
585             final SurfaceControl animatingLeash = animatingControl.getLeash();
586             final float defaultY = animatingControl.getSurfacePosition().y;
587             final float x = animatingControl.getSurfacePosition().x;
588             final float hiddenY = defaultY + mImeFrame.height();
589             final float shownY = defaultY;
590             final float startY = show ? hiddenY : shownY;
591             final float endY = show ? shownY : hiddenY;
592             if (mAnimationDirection == DIRECTION_NONE && mImeShowing && show) {
593                 // IME is already showing, so set seek to end
594                 seekValue = shownY;
595                 seek = true;
596             }
597             mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
598             updateImeVisibility(show);
599             mAnimation = ValueAnimator.ofFloat(startY, endY);
600             mAnimation.setDuration(
601                     show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
602             if (seek) {
603                 mAnimation.setCurrentFraction((seekValue - startY) / (endY - startY));
604             } else {
605                 // In some cases the value in onAnimationStart is zero, therefore setting it
606                 // explicitly to startY
607                 mAnimation.setCurrentFraction(0);
608             }
609 
610             mAnimation.addUpdateListener(animation -> {
611                 SurfaceControl.Transaction t = mTransactionPool.acquire();
612                 float value = (float) animation.getAnimatedValue();
613                 t.setPosition(animatingLeash, x, value);
614                 final float alpha = (mAnimateAlpha || isFloating)
615                         ? (value - hiddenY) / (shownY - hiddenY) : 1f;
616                 t.setAlpha(animatingLeash, alpha);
617                 dispatchPositionChanged(mDisplayId, imeTop(value, defaultY), t);
618                 t.apply();
619                 mTransactionPool.release(t);
620             });
621             mAnimation.setInterpolator(INTERPOLATOR);
622             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
623             mAnimation.addListener(new AnimatorListenerAdapter() {
624                 private boolean mCancelled = false;
625                 @NonNull
626                 private final ImeTracker.Token mStatsToken = statsToken;
627 
628                 @Override
629                 public void onAnimationStart(Animator animation) {
630                     ValueAnimator valueAnimator = (ValueAnimator) animation;
631                     float value = (float) valueAnimator.getAnimatedValue();
632                     SurfaceControl.Transaction t = mTransactionPool.acquire();
633                     t.setPosition(animatingLeash, x, value);
634                     if (DEBUG) {
635                         Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
636                                 + imeTop(hiddenY, defaultY) + "->" + imeTop(shownY, defaultY)
637                                 + " showing:" + (mAnimationDirection == DIRECTION_SHOW));
638                     }
639                     if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
640                         // Updating the animatingTypes when starting the animation is not the
641                         // trigger to show the IME. Thus, not sending the statsToken here.
642                         setAnimating(true /* imeAnimationOngoing */, null /* statsToken */);
643                     }
644                     int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY, defaultY),
645                             imeTop(shownY, defaultY), mAnimationDirection == DIRECTION_SHOW,
646                             isFloating, t);
647                     mAnimateAlpha = (flags & ImePositionProcessor.IME_ANIMATION_NO_ALPHA) == 0;
648                     final float alpha = (mAnimateAlpha || isFloating)
649                             ? (value - hiddenY) / (shownY - hiddenY)
650                             : 1.f;
651                     t.setAlpha(animatingLeash, alpha);
652                     if (mAnimationDirection == DIRECTION_SHOW) {
653                         ImeTracker.forLogging().onProgress(mStatsToken,
654                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
655                         t.show(animatingLeash);
656                     }
657                     if (DEBUG_IME_VISIBILITY) {
658                         EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START,
659                                 mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
660                                 mDisplayId, mAnimationDirection, alpha, value, endY,
661                                 Objects.toString(animatingLeash),
662                                 Objects.toString(animatingControl.getInsetsHint()),
663                                 Objects.toString(animatingControl.getSurfacePosition()),
664                                 Objects.toString(mImeFrame));
665                     }
666                     t.apply();
667                     mTransactionPool.release(t);
668                 }
669 
670                 @Override
671                 public void onAnimationCancel(Animator animation) {
672                     mCancelled = true;
673                     if (DEBUG_IME_VISIBILITY) {
674                         EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL,
675                                 mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
676                                 mDisplayId,
677                                 Objects.toString(animatingControl.getInsetsHint()));
678                     }
679                 }
680 
681                 @Override
682                 public void onAnimationEnd(Animator animation) {
683                     if (DEBUG) Slog.d(TAG, "onAnimationEnd " + mCancelled);
684                     SurfaceControl.Transaction t = mTransactionPool.acquire();
685                     if (!mCancelled) {
686                         t.setPosition(animatingLeash, x, endY);
687                         t.setAlpha(animatingLeash, 1.f);
688                     }
689                     if (!android.view.inputmethod.Flags.refactorInsetsController()) {
690                         dispatchEndPositioning(mDisplayId, mCancelled, t);
691                     } else if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
692                         setAnimating(false /* imeAnimationOngoing */,
693                                 mAnimationDirection == DIRECTION_HIDE ? statsToken : null);
694                     }
695                     if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
696                         ImeTracker.forLogging().onProgress(mStatsToken,
697                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
698                         t.hide(animatingLeash);
699                         if (!android.view.inputmethod.Flags.refactorInsetsController()) {
700                             removeImeSurface(mDisplayId);
701                         }
702                         if (android.view.inputmethod.Flags.refactorInsetsController()) {
703                             // Updating the client visibility will not hide the IME, unless it is
704                             // not animating anymore. Thus, not sending a statsToken here, but
705                             // only later when we're updating the animatingTypes.
706                             setVisibleDirectly(false /* visible */,
707                                     !android.view.inputmethod.Flags.reportAnimatingInsetsTypes()
708                                             ? statsToken : null);
709                         }
710                         if (!android.view.inputmethod.Flags.refactorInsetsController()) {
711                             ImeTracker.forLogging().onHidden(mStatsToken);
712                         }
713                     } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
714                         ImeTracker.forLogging().onShown(mStatsToken);
715                     } else if (mCancelled) {
716                         ImeTracker.forLogging().onCancelled(mStatsToken,
717                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
718                     }
719                     if (android.view.inputmethod.Flags.refactorInsetsController()) {
720                         // In split screen, we also set {@link
721                         // WindowContainer#mExcludeInsetsTypes} but this should only happen after
722                         // the IME client visibility was set. Otherwise the insets will we
723                         // dispatched too early, and we get a flicker. Thus, only dispatching it
724                         // after reporting that the IME is hidden to system server.
725                         dispatchEndPositioning(mDisplayId, mCancelled, t);
726                     }
727                     if (DEBUG_IME_VISIBILITY) {
728                         EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END,
729                                 mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
730                                 mDisplayId, mAnimationDirection, endY,
731                                 Objects.toString(animatingLeash),
732                                 Objects.toString(animatingControl.getInsetsHint()),
733                                 Objects.toString(animatingControl.getSurfacePosition()),
734                                 Objects.toString(mImeFrame));
735                     }
736                     t.apply();
737                     mTransactionPool.release(t);
738 
739                     mAnimationDirection = DIRECTION_NONE;
740                     mAnimation = null;
741                     animatingControl.release(SurfaceControl::release);
742                 }
743             });
744             if (!android.view.inputmethod.Flags.refactorInsetsController() && !show) {
745                 // When going away, queue up insets change first, otherwise any bounds changes
746                 // can have a "flicker" of ime-provided insets.
747                 setVisibleDirectly(false /* visible */, null /* statsToken */);
748             }
749             mAnimation.start();
750             if (!android.view.inputmethod.Flags.refactorInsetsController() && show) {
751                 // When showing away, queue up insets change last, otherwise any bounds changes
752                 // can have a "flicker" of ime-provided insets.
753                 setVisibleDirectly(true /* visible */, null /* statsToken */);
754             }
755         }
756 
updateImeVisibility(boolean isShowing)757         private void updateImeVisibility(boolean isShowing) {
758             if (mImeShowing != isShowing) {
759                 mImeShowing = isShowing;
760                 dispatchVisibilityChanged(mDisplayId, isShowing);
761             }
762         }
763 
764         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
getImeSourceControl()765         public InsetsSourceControl getImeSourceControl() {
766             return mImeSourceControl;
767         }
768     }
769 
removeImeSurface(int displayId)770     void removeImeSurface(int displayId) {
771         // Remove the IME surface to make the insets invisible for
772         // non-client controlled insets.
773         InputMethodManagerGlobal.removeImeSurface(displayId,
774                 e -> Slog.e(TAG, "Failed to remove IME surface.", e));
775     }
776 
777     /**
778      * Allows other things to synchronize with the ime position
779      */
780     public interface ImePositionProcessor {
781 
782         /** Default animation flags. */
783         int IME_ANIMATION_DEFAULT = 0;
784 
785         /**
786          * Indicates that ime shouldn't animate alpha. It will always be opaque. Used when stuff
787          * behind the IME shouldn't be visible (for example during split-screen adjustment where
788          * there is nothing behind the ime).
789          */
790         int IME_ANIMATION_NO_ALPHA = 1;
791 
792         /** @hide */
793         @IntDef(prefix = {"IME_ANIMATION_"}, value = {
794                 IME_ANIMATION_DEFAULT,
795                 IME_ANIMATION_NO_ALPHA,
796         })
797         @interface ImeAnimationFlags {
798         }
799 
800         /**
801          * Called when the IME was requested by an app
802          *
803          * @param isRequested {@code true} if the IME was requested to be visible
804          */
onImeRequested(int displayId, boolean isRequested)805         default void onImeRequested(int displayId, boolean isRequested) {
806         }
807 
808         /**
809          * Called when the IME position is starting to animate.
810          *
811          * @param hiddenTop  The y position of the top of the IME surface when it is hidden.
812          * @param shownTop   The y position of the top of the IME surface when it is shown.
813          * @param showing    {@code true} when we are animating from hidden to shown, {@code false}
814          *                   when animating from shown to hidden.
815          * @param isFloating {@code true} when the ime is a floating ime (doesn't inset).
816          * @return flags that may alter how ime itself is animated (eg. no-alpha).
817          */
818         @ImeAnimationFlags
onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, boolean isFloating, SurfaceControl.Transaction t)819         default int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
820                 boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
821             return IME_ANIMATION_DEFAULT;
822         }
823 
824         /**
825          * Called when the ime position changed. This is expected to be a synchronous call on the
826          * animation thread. Operations can be added to the transaction to be applied in sync.
827          *
828          * @param imeTop The current y position of the top of the IME surface.
829          */
onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)830         default void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
831         }
832 
833         /**
834          * Called when the IME position is done animating.
835          *
836          * @param cancel {@code true} if this was cancelled. This implies another start is coming.
837          */
onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t)838         default void onImeEndPositioning(int displayId, boolean cancel,
839                 SurfaceControl.Transaction t) {
840         }
841 
842         /**
843          * Called when the IME control target changed. So that the processor can restore its
844          * adjusted layout when the IME insets is not controlling by the current controller anymore.
845          *
846          * @param controlling indicates whether the current controller is controlling IME insets.
847          */
onImeControlTargetChanged(int displayId, boolean controlling)848         default void onImeControlTargetChanged(int displayId, boolean controlling) {
849         }
850 
851         /**
852          * Called when the IME visibility changed.
853          *
854          * @param isShowing {@code true} if the IME is shown.
855          */
onImeVisibilityChanged(int displayId, boolean isShowing)856         default void onImeVisibilityChanged(int displayId, boolean isShowing) {
857 
858         }
859     }
860 
haveSameLeash(InsetsSourceControl a, InsetsSourceControl b)861     private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) {
862         if (a == b) {
863             return true;
864         }
865         if (a == null || b == null) {
866             return false;
867         }
868         if (a.getLeash() == b.getLeash()) {
869             return true;
870         }
871         if (a.getLeash() == null || b.getLeash() == null) {
872             return false;
873         }
874         return a.getLeash().isSameSurface(b.getLeash());
875     }
876 }
877