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