blob: 7dad05df8f2ce5923468d89c88309dc2f50f5704 [file] [log] [blame]
Evan Rosky8d782e02019-10-14 15:43:53 -07001/*
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
17package com.android.systemui.wm;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.content.res.Configuration;
23import android.os.Handler;
24import android.os.RemoteException;
25import android.util.Slog;
26import android.util.SparseArray;
27import android.view.IDisplayWindowInsetsController;
28import android.view.InsetsSource;
29import android.view.InsetsSourceControl;
30import android.view.InsetsState;
31import android.view.Surface;
32import android.view.SurfaceControl;
33import android.view.WindowInsets;
34import android.view.animation.Interpolator;
35import android.view.animation.PathInterpolator;
36
37import com.android.systemui.dagger.qualifiers.Main;
38
39import java.util.ArrayList;
40
41import javax.inject.Inject;
42import javax.inject.Singleton;
43
44/**
45 * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode.
46 */
47@Singleton
Winson Chung95c9fca2020-01-22 09:48:40 -080048public class DisplayImeController implements DisplayController.OnDisplaysChangedListener {
Evan Rosky8d782e02019-10-14 15:43:53 -070049 private static final String TAG = "DisplayImeController";
50
51 static final int ANIMATION_DURATION_SHOW_MS = 275;
52 static final int ANIMATION_DURATION_HIDE_MS = 340;
53 static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
54 private static final int DIRECTION_NONE = 0;
55 private static final int DIRECTION_SHOW = 1;
56 private static final int DIRECTION_HIDE = 2;
57
58 SystemWindows mSystemWindows;
59 final Handler mHandler;
60
61 final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
62
63 final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
64
65 @Inject
Winson Chung95c9fca2020-01-22 09:48:40 -080066 public DisplayImeController(SystemWindows syswin, DisplayController displayController,
Evan Rosky8d782e02019-10-14 15:43:53 -070067 @Main Handler mainHandler) {
68 mHandler = mainHandler;
69 mSystemWindows = syswin;
70 displayController.addDisplayWindowListener(this);
71 }
72
73 @Override
74 public void onDisplayAdded(int displayId) {
75 // Add's a system-ui window-manager specifically for ime. This type is special because
76 // WM will defer IME inset handling to it in multi-window scenarious.
77 PerDisplay pd = new PerDisplay(displayId,
78 mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation());
79 try {
80 mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
81 } catch (RemoteException e) {
82 Slog.w(TAG, "Unable to set insets controller on display " + displayId);
83 }
84 mImePerDisplay.put(displayId, pd);
85 }
86
87 @Override
88 public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
89 PerDisplay pd = mImePerDisplay.get(displayId);
90 if (pd == null) {
91 return;
92 }
93 if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()
94 != pd.mRotation && isImeShowing(displayId)) {
95 pd.startAnimation(true);
96 }
97 }
98
99 @Override
100 public void onDisplayRemoved(int displayId) {
101 try {
102 mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
103 } catch (RemoteException e) {
104 Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
105 }
106 mImePerDisplay.remove(displayId);
107 }
108
109 private boolean isImeShowing(int displayId) {
110 PerDisplay pd = mImePerDisplay.get(displayId);
111 if (pd == null) {
112 return false;
113 }
114 final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME);
115 return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible();
116 }
117
118 private void dispatchPositionChanged(int displayId, int imeTop,
119 SurfaceControl.Transaction t) {
120 synchronized (mPositionProcessors) {
121 for (ImePositionProcessor pp : mPositionProcessors) {
122 pp.onImePositionChanged(displayId, imeTop, t);
123 }
124 }
125 }
126
127 private void dispatchStartPositioning(int displayId, int imeTop, int finalImeTop,
128 boolean show, SurfaceControl.Transaction t) {
129 synchronized (mPositionProcessors) {
130 for (ImePositionProcessor pp : mPositionProcessors) {
131 pp.onImeStartPositioning(displayId, imeTop, finalImeTop, show, t);
132 }
133 }
134 }
135
136 private void dispatchEndPositioning(int displayId, int imeTop, boolean show,
137 SurfaceControl.Transaction t) {
138 synchronized (mPositionProcessors) {
139 for (ImePositionProcessor pp : mPositionProcessors) {
140 pp.onImeEndPositioning(displayId, imeTop, show, t);
141 }
142 }
143 }
144
145 /**
146 * Adds an {@link ImePositionProcessor} to be called during ime position updates.
147 */
148 public void addPositionProcessor(ImePositionProcessor processor) {
149 synchronized (mPositionProcessors) {
150 if (mPositionProcessors.contains(processor)) {
151 return;
152 }
153 mPositionProcessors.add(processor);
154 }
155 }
156
157 /**
158 * Removes an {@link ImePositionProcessor} to be called during ime position updates.
159 */
160 public void removePositionProcessor(ImePositionProcessor processor) {
161 synchronized (mPositionProcessors) {
162 mPositionProcessors.remove(processor);
163 }
164 }
165
166 class PerDisplay extends IDisplayWindowInsetsController.Stub {
167 final int mDisplayId;
168 final InsetsState mInsetsState = new InsetsState();
169 InsetsSourceControl mImeSourceControl = null;
170 int mAnimationDirection = DIRECTION_NONE;
171 ValueAnimator mAnimation = null;
172 int mRotation = Surface.ROTATION_0;
173
174 PerDisplay(int displayId, int initialRotation) {
175 mDisplayId = displayId;
176 mRotation = initialRotation;
177 }
178
179 @Override
180 public void insetsChanged(InsetsState insetsState) {
181 if (mInsetsState.equals(insetsState)) {
182 return;
183 }
184 mInsetsState.set(insetsState, true /* copySources */);
185 }
186
187 @Override
188 public void insetsControlChanged(InsetsState insetsState,
189 InsetsSourceControl[] activeControls) {
190 insetsChanged(insetsState);
191 if (activeControls != null) {
192 for (InsetsSourceControl activeControl : activeControls) {
193 if (activeControl == null) {
194 continue;
195 }
196 if (activeControl.getType() == InsetsState.ITYPE_IME) {
197 mImeSourceControl = activeControl;
198 }
199 }
200 }
201 }
202
203 @Override
204 public void showInsets(int types, boolean fromIme) {
205 if ((types & WindowInsets.Type.ime()) == 0) {
206 return;
207 }
208 startAnimation(true /* show */);
209 }
210
211 @Override
212 public void hideInsets(int types, boolean fromIme) {
213 if ((types & WindowInsets.Type.ime()) == 0) {
214 return;
215 }
216 startAnimation(false /* show */);
217 }
218
219 /**
220 * Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
221 */
222 private void setVisibleDirectly(boolean visible) {
223 mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
224 try {
225 mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
226 } catch (RemoteException e) {
227 }
228 }
229
230 private int imeTop(InsetsSource imeSource, float surfaceOffset) {
231 return imeSource.getFrame().top + (int) surfaceOffset;
232 }
233
234 private void startAnimation(final boolean show) {
235 final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
236 if (imeSource == null || mImeSourceControl == null) {
237 return;
238 }
239 if ((mAnimationDirection == DIRECTION_SHOW && show)
240 || (mAnimationDirection == DIRECTION_HIDE && !show)) {
241 return;
242 }
243 if (mAnimationDirection != DIRECTION_NONE) {
244 mAnimation.end();
245 mAnimationDirection = DIRECTION_NONE;
246 }
247 mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
248 mHandler.post(() -> {
249 final float defaultY = mImeSourceControl.getSurfacePosition().y;
250 final float x = mImeSourceControl.getSurfacePosition().x;
251 final float startY = show ? defaultY + imeSource.getFrame().height() : defaultY;
252 final float endY = show ? defaultY : defaultY + imeSource.getFrame().height();
253 mAnimation = ValueAnimator.ofFloat(startY, endY);
254 mAnimation.setDuration(
255 show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
256
257 mAnimation.addUpdateListener(animation -> {
258 SurfaceControl.Transaction t = new SurfaceControl.Transaction();
259 float value = (float) animation.getAnimatedValue();
260 t.setPosition(mImeSourceControl.getLeash(), x, value);
261 dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t);
262 t.apply();
263 t.close();
264 });
265 mAnimation.setInterpolator(INTERPOLATOR);
266 mAnimation.addListener(new AnimatorListenerAdapter() {
267 @Override
268 public void onAnimationStart(Animator animation) {
269 SurfaceControl.Transaction t = new SurfaceControl.Transaction();
270 t.setPosition(mImeSourceControl.getLeash(), x, startY);
271 dispatchStartPositioning(mDisplayId, imeTop(imeSource, startY),
272 imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW,
273 t);
274 if (mAnimationDirection == DIRECTION_SHOW) {
275 t.show(mImeSourceControl.getLeash());
276 }
277 t.apply();
278 t.close();
279 }
280 @Override
281 public void onAnimationEnd(Animator animation) {
282 SurfaceControl.Transaction t = new SurfaceControl.Transaction();
283 t.setPosition(mImeSourceControl.getLeash(), x, endY);
284 dispatchEndPositioning(mDisplayId, imeTop(imeSource, endY),
285 mAnimationDirection == DIRECTION_SHOW, t);
286 if (mAnimationDirection == DIRECTION_HIDE) {
287 t.hide(mImeSourceControl.getLeash());
288 }
289 t.apply();
290 t.close();
291
292 mAnimationDirection = DIRECTION_NONE;
293 mAnimation = null;
294 }
295 });
296 if (!show) {
297 // When going away, queue up insets change first, otherwise any bounds changes
298 // can have a "flicker" of ime-provided insets.
299 setVisibleDirectly(false /* visible */);
300 }
301 mAnimation.start();
302 if (show) {
303 // When showing away, queue up insets change last, otherwise any bounds changes
304 // can have a "flicker" of ime-provided insets.
305 setVisibleDirectly(true /* visible */);
306 }
307 });
308 }
309 }
310
311 /**
312 * Allows other things to synchronize with the ime position
313 */
314 public interface ImePositionProcessor {
315 /**
316 * Called when the IME position is starting to animate.
317 */
Winson Chung95c9fca2020-01-22 09:48:40 -0800318 default void onImeStartPositioning(int displayId, int imeTop, int finalImeTop,
319 boolean showing, SurfaceControl.Transaction t) {}
Evan Rosky8d782e02019-10-14 15:43:53 -0700320
321 /**
322 * Called when the ime position changed. This is expected to be a synchronous call on the
323 * animation thread. Operations can be added to the transaction to be applied in sync.
324 */
Winson Chung95c9fca2020-01-22 09:48:40 -0800325 default void onImePositionChanged(int displayId, int imeTop,
326 SurfaceControl.Transaction t) {}
Evan Rosky8d782e02019-10-14 15:43:53 -0700327
328 /**
329 * Called when the IME position is done animating.
330 */
Winson Chung95c9fca2020-01-22 09:48:40 -0800331 default void onImeEndPositioning(int displayId, int imeTop, boolean showing,
332 SurfaceControl.Transaction t) {}
Evan Rosky8d782e02019-10-14 15:43:53 -0700333 }
334}