• 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.systemui.wm;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.annotation.IntDef;
23 import android.content.Context;
24 import android.content.res.Configuration;
25 import android.graphics.Point;
26 import android.graphics.Rect;
27 import android.os.RemoteException;
28 import android.os.ServiceManager;
29 import android.util.Slog;
30 import android.util.SparseArray;
31 import android.view.IDisplayWindowInsetsController;
32 import android.view.IWindowManager;
33 import android.view.InsetsSource;
34 import android.view.InsetsSourceControl;
35 import android.view.InsetsState;
36 import android.view.Surface;
37 import android.view.SurfaceControl;
38 import android.view.WindowInsets;
39 import android.view.animation.Interpolator;
40 import android.view.animation.PathInterpolator;
41 
42 import androidx.annotation.BinderThread;
43 import androidx.annotation.VisibleForTesting;
44 
45 import com.android.internal.view.IInputMethodManager;
46 import com.android.systemui.TransactionPool;
47 import com.android.systemui.dagger.qualifiers.Main;
48 
49 import java.util.ArrayList;
50 import java.util.concurrent.Executor;
51 
52 import javax.inject.Inject;
53 import javax.inject.Singleton;
54 
55 /**
56  * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode.
57  */
58 @Singleton
59 public class DisplayImeController implements DisplayController.OnDisplaysChangedListener {
60     private static final String TAG = "DisplayImeController";
61 
62     private static final boolean DEBUG = false;
63 
64     // NOTE: All these constants came from InsetsController.
65     public static final int ANIMATION_DURATION_SHOW_MS = 275;
66     public static final int ANIMATION_DURATION_HIDE_MS = 340;
67     public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
68     private static final int DIRECTION_NONE = 0;
69     private static final int DIRECTION_SHOW = 1;
70     private static final int DIRECTION_HIDE = 2;
71     private static final int FLOATING_IME_BOTTOM_INSET = -80;
72 
73     protected final IWindowManager mWmService;
74     protected final Executor mMainExecutor;
75     final TransactionPool mTransactionPool;
76     final DisplayController mDisplayController;
77 
78     final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
79 
80     final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
81 
82     @Inject
DisplayImeController(IWindowManager wmService, DisplayController displayController, @Main Executor mainExecutor, TransactionPool transactionPool)83     public DisplayImeController(IWindowManager wmService, DisplayController displayController,
84             @Main Executor mainExecutor, TransactionPool transactionPool) {
85         mWmService = wmService;
86         mMainExecutor = mainExecutor;
87         mTransactionPool = transactionPool;
88         mDisplayController = displayController;
89         displayController.addDisplayWindowListener(this);
90     }
91 
92     @Override
onDisplayAdded(int displayId)93     public void onDisplayAdded(int displayId) {
94         // Add's a system-ui window-manager specifically for ime. This type is special because
95         // WM will defer IME inset handling to it in multi-window scenarious.
96         PerDisplay pd = new PerDisplay(displayId,
97                 mDisplayController.getDisplayLayout(displayId).rotation());
98         pd.register();
99         mImePerDisplay.put(displayId, pd);
100     }
101 
102     @Override
onDisplayConfigurationChanged(int displayId, Configuration newConfig)103     public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
104         PerDisplay pd = mImePerDisplay.get(displayId);
105         if (pd == null) {
106             return;
107         }
108         if (mDisplayController.getDisplayLayout(displayId).rotation()
109                 != pd.mRotation && isImeShowing(displayId)) {
110             pd.startAnimation(true, false /* forceRestart */);
111         }
112     }
113 
114     @Override
onDisplayRemoved(int displayId)115     public void onDisplayRemoved(int displayId) {
116         try {
117             mWmService.setDisplayWindowInsetsController(displayId, null);
118         } catch (RemoteException e) {
119             Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
120         }
121         mImePerDisplay.remove(displayId);
122     }
123 
isImeShowing(int displayId)124     private boolean isImeShowing(int displayId) {
125         PerDisplay pd = mImePerDisplay.get(displayId);
126         if (pd == null) {
127             return false;
128         }
129         final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME);
130         return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible();
131     }
132 
dispatchPositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)133     private void dispatchPositionChanged(int displayId, int imeTop,
134             SurfaceControl.Transaction t) {
135         synchronized (mPositionProcessors) {
136             for (ImePositionProcessor pp : mPositionProcessors) {
137                 pp.onImePositionChanged(displayId, imeTop, t);
138             }
139         }
140     }
141 
142     @ImePositionProcessor.ImeAnimationFlags
dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, boolean show, boolean isFloating, SurfaceControl.Transaction t)143     private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop,
144             boolean show, boolean isFloating, SurfaceControl.Transaction t) {
145         synchronized (mPositionProcessors) {
146             int flags = 0;
147             for (ImePositionProcessor pp : mPositionProcessors) {
148                 flags |= pp.onImeStartPositioning(
149                         displayId, hiddenTop, shownTop, show, isFloating, t);
150             }
151             return flags;
152         }
153     }
154 
dispatchEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t)155     private void dispatchEndPositioning(int displayId, boolean cancel,
156             SurfaceControl.Transaction t) {
157         synchronized (mPositionProcessors) {
158             for (ImePositionProcessor pp : mPositionProcessors) {
159                 pp.onImeEndPositioning(displayId, cancel, t);
160             }
161         }
162     }
163 
164     /**
165      * Adds an {@link ImePositionProcessor} to be called during ime position updates.
166      */
addPositionProcessor(ImePositionProcessor processor)167     public void addPositionProcessor(ImePositionProcessor processor) {
168         synchronized (mPositionProcessors) {
169             if (mPositionProcessors.contains(processor)) {
170                 return;
171             }
172             mPositionProcessors.add(processor);
173         }
174     }
175 
176     /**
177      * Removes an {@link ImePositionProcessor} to be called during ime position updates.
178      */
removePositionProcessor(ImePositionProcessor processor)179     public void removePositionProcessor(ImePositionProcessor processor) {
180         synchronized (mPositionProcessors) {
181             mPositionProcessors.remove(processor);
182         }
183     }
184 
185     /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */
186     public class PerDisplay {
187         final int mDisplayId;
188         final InsetsState mInsetsState = new InsetsState();
189         protected final DisplayWindowInsetsControllerImpl mInsetsControllerImpl =
190                 new DisplayWindowInsetsControllerImpl();
191         InsetsSourceControl mImeSourceControl = null;
192         int mAnimationDirection = DIRECTION_NONE;
193         ValueAnimator mAnimation = null;
194         int mRotation = Surface.ROTATION_0;
195         boolean mImeShowing = false;
196         final Rect mImeFrame = new Rect();
197         boolean mAnimateAlpha = true;
198 
PerDisplay(int displayId, int initialRotation)199         PerDisplay(int displayId, int initialRotation) {
200             mDisplayId = displayId;
201             mRotation = initialRotation;
202         }
203 
register()204         public void register() {
205             try {
206                 mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl);
207             } catch (RemoteException e) {
208                 Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId);
209             }
210         }
211 
insetsChanged(InsetsState insetsState)212         public void insetsChanged(InsetsState insetsState) {
213             if (mInsetsState.equals(insetsState)) {
214                 return;
215             }
216 
217             mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
218 
219             final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
220             final Rect newFrame = newSource.getFrame();
221             final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
222 
223             mInsetsState.set(insetsState, true /* copySources */);
224             if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
225                 if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
226                 startAnimation(mImeShowing, true /* forceRestart */);
227             }
228         }
229 
insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)230         public void insetsControlChanged(InsetsState insetsState,
231                 InsetsSourceControl[] activeControls) {
232             insetsChanged(insetsState);
233             if (activeControls != null) {
234                 for (InsetsSourceControl activeControl : activeControls) {
235                     if (activeControl == null) {
236                         continue;
237                     }
238                     if (activeControl.getType() == InsetsState.ITYPE_IME) {
239                         final Point lastSurfacePosition = mImeSourceControl != null
240                                 ? mImeSourceControl.getSurfacePosition() : null;
241                         final boolean positionChanged =
242                                 !activeControl.getSurfacePosition().equals(lastSurfacePosition);
243                         final boolean leashChanged =
244                                 !haveSameLeash(mImeSourceControl, activeControl);
245                         mImeSourceControl = activeControl;
246                         if (mAnimation != null) {
247                             if (positionChanged) {
248                                 startAnimation(mImeShowing, true /* forceRestart */);
249                             }
250                         } else {
251                             if (leashChanged) {
252                                 applyVisibilityToLeash();
253                             }
254                             if (!mImeShowing) {
255                                 removeImeSurface();
256                             }
257                         }
258                     }
259                 }
260             }
261         }
262 
applyVisibilityToLeash()263         private void applyVisibilityToLeash() {
264             SurfaceControl leash = mImeSourceControl.getLeash();
265             if (leash != null) {
266                 SurfaceControl.Transaction t = mTransactionPool.acquire();
267                 if (mImeShowing) {
268                     t.show(leash);
269                 } else {
270                     t.hide(leash);
271                 }
272                 t.apply();
273                 mTransactionPool.release(t);
274             }
275         }
276 
showInsets(int types, boolean fromIme)277         public void showInsets(int types, boolean fromIme) {
278             if ((types & WindowInsets.Type.ime()) == 0) {
279                 return;
280             }
281             if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
282             startAnimation(true /* show */, false /* forceRestart */);
283         }
284 
hideInsets(int types, boolean fromIme)285         public void hideInsets(int types, boolean fromIme) {
286             if ((types & WindowInsets.Type.ime()) == 0) {
287                 return;
288             }
289             if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
290             startAnimation(false /* show */, false /* forceRestart */);
291         }
292 
topFocusedWindowChanged(String packageName)293         public void topFocusedWindowChanged(String packageName) {
294             // no-op
295         }
296 
297         /**
298          * Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
299          */
setVisibleDirectly(boolean visible)300         private void setVisibleDirectly(boolean visible) {
301             mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
302             try {
303                 mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
304             } catch (RemoteException e) {
305             }
306         }
307 
imeTop(float surfaceOffset)308         private int imeTop(float surfaceOffset) {
309             return mImeFrame.top + (int) surfaceOffset;
310         }
311 
calcIsFloating(InsetsSource imeSource)312         private boolean calcIsFloating(InsetsSource imeSource) {
313             final Rect frame = imeSource.getFrame();
314             if (frame.height() == 0) {
315                 return true;
316             }
317             // Some Floating Input Methods will still report a frame, but the frame is actually
318             // a nav-bar inset created by WM and not part of the IME (despite being reported as
319             // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar
320             // frame height so any reported frame that is <= nav-bar frame height is assumed to
321             // be floating.
322             return frame.height() <= mDisplayController.getDisplayLayout(mDisplayId)
323                     .navBarFrameHeight();
324         }
325 
startAnimation(final boolean show, final boolean forceRestart)326         private void startAnimation(final boolean show, final boolean forceRestart) {
327             final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
328             if (imeSource == null || mImeSourceControl == null) {
329                 return;
330             }
331             final Rect newFrame = imeSource.getFrame();
332             final boolean isFloating = calcIsFloating(imeSource) && show;
333             if (isFloating) {
334                 // This is a "floating" or "expanded" IME, so to get animations, just
335                 // pretend the ime has some size just below the screen.
336                 mImeFrame.set(newFrame);
337                 final int floatingInset = (int) (
338                         mDisplayController.getDisplayLayout(mDisplayId).density()
339                                 * FLOATING_IME_BOTTOM_INSET);
340                 mImeFrame.bottom -= floatingInset;
341             } else if (newFrame.height() != 0) {
342                 // Don't set a new frame if it's empty and hiding -- this maintains continuity
343                 mImeFrame.set(newFrame);
344             }
345             if (DEBUG) {
346                 Slog.d(TAG, "Run startAnim  show:" + show + "  was:"
347                         + (mAnimationDirection == DIRECTION_SHOW ? "SHOW"
348                         : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE")));
349             }
350             if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show)
351                     || (mAnimationDirection == DIRECTION_HIDE && !show)) {
352                 return;
353             }
354             boolean seek = false;
355             float seekValue = 0;
356             if (mAnimation != null) {
357                 if (mAnimation.isRunning()) {
358                     seekValue = (float) mAnimation.getAnimatedValue();
359                     seek = true;
360                 }
361                 mAnimation.cancel();
362             }
363             final float defaultY = mImeSourceControl.getSurfacePosition().y;
364             final float x = mImeSourceControl.getSurfacePosition().x;
365             final float hiddenY = defaultY + mImeFrame.height();
366             final float shownY = defaultY;
367             final float startY = show ? hiddenY : shownY;
368             final float endY = show ? shownY : hiddenY;
369             if (mAnimationDirection == DIRECTION_NONE && mImeShowing && show) {
370                 // IME is already showing, so set seek to end
371                 seekValue = shownY;
372                 seek = true;
373             }
374             mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
375             mImeShowing = show;
376             mAnimation = ValueAnimator.ofFloat(startY, endY);
377             mAnimation.setDuration(
378                     show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
379             if (seek) {
380                 mAnimation.setCurrentFraction((seekValue - startY) / (endY - startY));
381             }
382 
383             mAnimation.addUpdateListener(animation -> {
384                 SurfaceControl.Transaction t = mTransactionPool.acquire();
385                 float value = (float) animation.getAnimatedValue();
386                 t.setPosition(mImeSourceControl.getLeash(), x, value);
387                 final float alpha = (mAnimateAlpha || isFloating)
388                         ? (value - hiddenY) / (shownY - hiddenY) : 1.f;
389                 t.setAlpha(mImeSourceControl.getLeash(), alpha);
390                 dispatchPositionChanged(mDisplayId, imeTop(value), t);
391                 t.apply();
392                 mTransactionPool.release(t);
393             });
394             mAnimation.setInterpolator(INTERPOLATOR);
395             mAnimation.addListener(new AnimatorListenerAdapter() {
396                 private boolean mCancelled = false;
397                 @Override
398                 public void onAnimationStart(Animator animation) {
399                     SurfaceControl.Transaction t = mTransactionPool.acquire();
400                     t.setPosition(mImeSourceControl.getLeash(), x, startY);
401                     if (DEBUG) {
402                         Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
403                                 + imeTop(hiddenY) + "->" + imeTop(shownY)
404                                 + " showing:" + (mAnimationDirection == DIRECTION_SHOW));
405                     }
406                     int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY),
407                             imeTop(shownY), mAnimationDirection == DIRECTION_SHOW, isFloating, t);
408                     mAnimateAlpha = (flags & ImePositionProcessor.IME_ANIMATION_NO_ALPHA) == 0;
409                     final float alpha = (mAnimateAlpha || isFloating)
410                             ? (startY - hiddenY) / (shownY - hiddenY)
411                             : 1.f;
412                     t.setAlpha(mImeSourceControl.getLeash(), alpha);
413                     if (mAnimationDirection == DIRECTION_SHOW) {
414                         t.show(mImeSourceControl.getLeash());
415                     }
416                     t.apply();
417                     mTransactionPool.release(t);
418                 }
419                 @Override
420                 public void onAnimationCancel(Animator animation) {
421                     mCancelled = true;
422                 }
423                 @Override
424                 public void onAnimationEnd(Animator animation) {
425                     if (DEBUG) Slog.d(TAG, "onAnimationEnd " + mCancelled);
426                     SurfaceControl.Transaction t = mTransactionPool.acquire();
427                     if (!mCancelled) {
428                         t.setPosition(mImeSourceControl.getLeash(), x, endY);
429                         t.setAlpha(mImeSourceControl.getLeash(), 1.f);
430                     }
431                     dispatchEndPositioning(mDisplayId, mCancelled, t);
432                     if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
433                         t.hide(mImeSourceControl.getLeash());
434                         removeImeSurface();
435                     }
436                     t.apply();
437                     mTransactionPool.release(t);
438 
439                     mAnimationDirection = DIRECTION_NONE;
440                     mAnimation = null;
441                 }
442             });
443             if (!show) {
444                 // When going away, queue up insets change first, otherwise any bounds changes
445                 // can have a "flicker" of ime-provided insets.
446                 setVisibleDirectly(false /* visible */);
447             }
448             mAnimation.start();
449             if (show) {
450                 // When showing away, queue up insets change last, otherwise any bounds changes
451                 // can have a "flicker" of ime-provided insets.
452                 setVisibleDirectly(true /* visible */);
453             }
454         }
455 
456         @VisibleForTesting
457         @BinderThread
458         public class DisplayWindowInsetsControllerImpl
459                 extends IDisplayWindowInsetsController.Stub {
460             @Override
topFocusedWindowChanged(String packageName)461             public void topFocusedWindowChanged(String packageName) throws RemoteException {
462                 mMainExecutor.execute(() -> {
463                     PerDisplay.this.topFocusedWindowChanged(packageName);
464                 });
465             }
466 
467             @Override
insetsChanged(InsetsState insetsState)468             public void insetsChanged(InsetsState insetsState) throws RemoteException {
469                 mMainExecutor.execute(() -> {
470                     PerDisplay.this.insetsChanged(insetsState);
471                 });
472             }
473 
474             @Override
insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)475             public void insetsControlChanged(InsetsState insetsState,
476                     InsetsSourceControl[] activeControls) throws RemoteException {
477                 mMainExecutor.execute(() -> {
478                     PerDisplay.this.insetsControlChanged(insetsState, activeControls);
479                 });
480             }
481 
482             @Override
showInsets(int types, boolean fromIme)483             public void showInsets(int types, boolean fromIme) throws RemoteException {
484                 mMainExecutor.execute(() -> {
485                     PerDisplay.this.showInsets(types, fromIme);
486                 });
487             }
488 
489             @Override
hideInsets(int types, boolean fromIme)490             public void hideInsets(int types, boolean fromIme) throws RemoteException {
491                 mMainExecutor.execute(() -> {
492                     PerDisplay.this.hideInsets(types, fromIme);
493                 });
494             }
495         }
496     }
497 
removeImeSurface()498     void removeImeSurface() {
499         final IInputMethodManager imms = getImms();
500         if (imms != null) {
501             try {
502                 // Remove the IME surface to make the insets invisible for
503                 // non-client controlled insets.
504                 imms.removeImeSurface();
505             } catch (RemoteException e) {
506                 Slog.e(TAG, "Failed to remove IME surface.", e);
507             }
508         }
509     }
510 
511     /**
512      * Allows other things to synchronize with the ime position
513      */
514     public interface ImePositionProcessor {
515         /**
516          * Indicates that ime shouldn't animate alpha. It will always be opaque. Used when stuff
517          * behind the IME shouldn't be visible (for example during split-screen adjustment where
518          * there is nothing behind the ime).
519          */
520         int IME_ANIMATION_NO_ALPHA = 1;
521 
522         /** @hide */
523         @IntDef(prefix = { "IME_ANIMATION_" }, value = {
524                 IME_ANIMATION_NO_ALPHA,
525         })
526         @interface ImeAnimationFlags {}
527 
528         /**
529          * Called when the IME position is starting to animate.
530          *
531          * @param hiddenTop The y position of the top of the IME surface when it is hidden.
532          * @param shownTop  The y position of the top of the IME surface when it is shown.
533          * @param showing   {@code true} when we are animating from hidden to shown, {@code false}
534          *                  when animating from shown to hidden.
535          * @param isFloating {@code true} when the ime is a floating ime (doesn't inset).
536          * @return flags that may alter how ime itself is animated (eg. no-alpha).
537          */
538         @ImeAnimationFlags
onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, boolean isFloating, SurfaceControl.Transaction t)539         default int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
540                 boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
541             return 0;
542         }
543 
544         /**
545          * Called when the ime position changed. This is expected to be a synchronous call on the
546          * animation thread. Operations can be added to the transaction to be applied in sync.
547          *
548          * @param imeTop The current y position of the top of the IME surface.
549          */
onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)550         default void onImePositionChanged(int displayId, int imeTop,
551                 SurfaceControl.Transaction t) {}
552 
553         /**
554          * Called when the IME position is done animating.
555          *
556          * @param cancel {@code true} if this was cancelled. This implies another start is coming.
557          */
onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t)558         default void onImeEndPositioning(int displayId, boolean cancel,
559                 SurfaceControl.Transaction t) {}
560     }
561 
getImms()562     public IInputMethodManager getImms() {
563         return IInputMethodManager.Stub.asInterface(
564                 ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
565     }
566 
haveSameLeash(InsetsSourceControl a, InsetsSourceControl b)567     private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) {
568         if (a == b) {
569             return true;
570         }
571         if (a == null || b == null) {
572             return false;
573         }
574         if (a.getLeash() == b.getLeash()) {
575             return true;
576         }
577         if (a.getLeash() == null || b.getLeash() == null) {
578             return false;
579         }
580         return a.getLeash().isSameSurface(b.getLeash());
581     }
582 }
583