blob: 4f9ecd5753263c26d22cf794f8d20ea23aed351e [file] [log] [blame]
Jorim Jaggif96c90a2018-09-26 16:55:15 +02001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -080019import static android.view.InsetsState.TYPE_IME;
20
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -080021import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.animation.ObjectAnimator;
24import android.animation.TypeEvaluator;
25import android.annotation.IntDef;
Jorim Jaggib6030952018-10-23 18:31:52 +020026import android.annotation.NonNull;
Jorim Jaggi02a741f2018-12-12 17:40:19 -080027import android.graphics.Insets;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020028import android.graphics.Rect;
Jorim Jaggie35c0592018-11-06 16:21:08 +010029import android.os.RemoteException;
Jorim Jaggib6030952018-10-23 18:31:52 +020030import android.util.ArraySet;
Jorim Jaggie35c0592018-11-06 16:21:08 +010031import android.util.Log;
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -080032import android.util.Property;
Jorim Jaggib6030952018-10-23 18:31:52 +020033import android.util.SparseArray;
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -080034import android.view.InsetsState.InternalInsetType;
Jorim Jaggib6030952018-10-23 18:31:52 +020035import android.view.SurfaceControl.Transaction;
36import android.view.WindowInsets.Type.InsetType;
Jorim Jaggib6030952018-10-23 18:31:52 +020037
38import com.android.internal.annotations.VisibleForTesting;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020039
40import java.io.PrintWriter;
Jorim Jaggi5bb571d2018-11-06 14:42:04 +010041import java.util.ArrayList;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020042
43/**
44 * Implements {@link WindowInsetsController} on the client.
Jorim Jaggib6030952018-10-23 18:31:52 +020045 * @hide
Jorim Jaggif96c90a2018-09-26 16:55:15 +020046 */
Jorim Jaggib6030952018-10-23 18:31:52 +020047public class InsetsController implements WindowInsetsController {
Jorim Jaggif96c90a2018-09-26 16:55:15 +020048
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -080049 // TODO: Use animation scaling and more optimal duration.
50 private static final int ANIMATION_DURATION_MS = 400;
51 private static final int DIRECTION_NONE = 0;
52 private static final int DIRECTION_SHOW = 1;
53 private static final int DIRECTION_HIDE = 2;
54 @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
55 private @interface AnimationDirection{}
56
57 /**
58 * Translation animation evaluator.
59 */
60 private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of(
61 0,
62 (int) (startValue.top + fraction * (endValue.top - startValue.top)),
63 0,
64 (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
65
66 /**
67 * Linear animation property
68 */
69 private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> {
70 InsetsProperty() {
71 super(Insets.class, "Insets");
72 }
73
74 @Override
75 public Insets get(WindowInsetsAnimationController object) {
76 return object.getCurrentInsets();
77 }
78 @Override
79 public void set(WindowInsetsAnimationController object, Insets value) {
80 object.changeInsets(value);
81 }
82 }
83
Jorim Jaggie35c0592018-11-06 16:21:08 +010084 private final String TAG = "InsetsControllerImpl";
85
Jorim Jaggif96c90a2018-09-26 16:55:15 +020086 private final InsetsState mState = new InsetsState();
Jorim Jaggie35c0592018-11-06 16:21:08 +010087 private final InsetsState mTmpState = new InsetsState();
88
Jorim Jaggif96c90a2018-09-26 16:55:15 +020089 private final Rect mFrame = new Rect();
Jorim Jaggib6030952018-10-23 18:31:52 +020090 private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>();
Jorim Jaggic8d60382018-10-31 17:06:06 +010091 private final ViewRootImpl mViewRoot;
Jorim Jaggib6030952018-10-23 18:31:52 +020092
93 private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
Jorim Jaggi5bb571d2018-11-06 14:42:04 +010094 private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>();
Jorim Jaggi02a741f2018-12-12 17:40:19 -080095 private WindowInsets mLastInsets;
96
97 private boolean mAnimCallbackScheduled;
98
99 private final Runnable mAnimCallback;
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200100
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100101 private final Rect mLastLegacyContentInsets = new Rect();
102 private final Rect mLastLegacyStableInsets = new Rect();
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -0800103 private ObjectAnimator mAnimator;
104 private @AnimationDirection int mAnimationDirection;
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100105
Jorim Jaggic8d60382018-10-31 17:06:06 +0100106 public InsetsController(ViewRootImpl viewRoot) {
107 mViewRoot = viewRoot;
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800108 mAnimCallback = () -> {
109 mAnimCallbackScheduled = false;
110 if (mAnimationControls.isEmpty()) {
111 return;
112 }
113
114 InsetsState state = new InsetsState(mState, true /* copySources */);
115 for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
116 mAnimationControls.get(i).applyChangeInsets(state);
117 }
118 WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(),
119 mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(),
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100120 mLastLegacyContentInsets, mLastLegacyStableInsets,
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800121 null /* typeSideMap */);
122 mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets);
123 };
Jorim Jaggic8d60382018-10-31 17:06:06 +0100124 }
125
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200126 void onFrameChanged(Rect frame) {
127 mFrame.set(frame);
128 }
129
130 public InsetsState getState() {
131 return mState;
132 }
133
Jorim Jaggic8d60382018-10-31 17:06:06 +0100134 boolean onStateChanged(InsetsState state) {
135 if (mState.equals(state)) {
136 return false;
137 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200138 mState.set(state);
Jorim Jaggie35c0592018-11-06 16:21:08 +0100139 mTmpState.set(state, true /* copySources */);
Jorim Jaggic8d60382018-10-31 17:06:06 +0100140 applyLocalVisibilityOverride();
141 mViewRoot.notifyInsetsChanged();
Jorim Jaggie35c0592018-11-06 16:21:08 +0100142 if (!mState.equals(mTmpState)) {
143 sendStateToWindowManager();
144 }
Jorim Jaggic8d60382018-10-31 17:06:06 +0100145 return true;
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200146 }
147
148 /**
149 * @see InsetsState#calculateInsets
150 */
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100151 @VisibleForTesting
152 public WindowInsets calculateInsets(boolean isScreenRound,
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100153 boolean alwaysConsumeNavBar, DisplayCutout cutout, Rect legacyContentInsets,
154 Rect legacyStableInsets) {
155 mLastLegacyContentInsets.set(legacyContentInsets);
156 mLastLegacyStableInsets.set(legacyStableInsets);
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800157 mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout,
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100158 legacyContentInsets, legacyStableInsets,
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100159 null /* typeSideMap */);
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800160 return mLastInsets;
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200161 }
162
Jorim Jaggib6030952018-10-23 18:31:52 +0200163 /**
164 * Called when the server has dispatched us a new set of inset controls.
165 */
166 public void onControlsChanged(InsetsSourceControl[] activeControls) {
167 if (activeControls != null) {
168 for (InsetsSourceControl activeControl : activeControls) {
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -0800169 if (activeControl != null) {
170 // TODO(b/122982984): Figure out why it can be null.
171 mTmpControlArray.put(activeControl.getType(), activeControl);
172 }
Jorim Jaggib6030952018-10-23 18:31:52 +0200173 }
174 }
175
176 // Ensure to update all existing source consumers
177 for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
178 final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
179 final InsetsSourceControl control = mTmpControlArray.get(consumer.getType());
180
181 // control may be null, but we still need to update the control to null if it got
182 // revoked.
183 consumer.setControl(control);
184 }
185
186 // Ensure to create source consumers if not available yet.
187 for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
188 final InsetsSourceControl control = mTmpControlArray.valueAt(i);
189 getSourceConsumer(control.getType()).setControl(control);
190 }
191 mTmpControlArray.clear();
192 }
193
194 @Override
195 public void show(@InsetType int types) {
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -0800196 int typesReady = 0;
Jorim Jaggib6030952018-10-23 18:31:52 +0200197 final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
198 for (int i = internalTypes.size() - 1; i >= 0; i--) {
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -0800199 InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
200 if (mAnimationDirection == DIRECTION_HIDE) {
201 // Only one animator (with multiple InsetType) can run at a time.
202 // previous one should be cancelled for simplicity.
203 cancelExistingAnimation();
204 } else if (consumer.isVisible() || mAnimationDirection == DIRECTION_SHOW) {
205 // no-op: already shown or animating in.
206 // TODO: When we have more than one types: handle specific case when
207 // show animation is going on, but the current type is not becoming visible.
208 continue;
209 }
210 typesReady |= InsetsState.toPublicType(consumer.getType());
Jorim Jaggib6030952018-10-23 18:31:52 +0200211 }
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -0800212 applyAnimation(typesReady, true /* show */);
Jorim Jaggib6030952018-10-23 18:31:52 +0200213 }
214
215 @Override
216 public void hide(@InsetType int types) {
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -0800217 int typesReady = 0;
Jorim Jaggib6030952018-10-23 18:31:52 +0200218 final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
219 for (int i = internalTypes.size() - 1; i >= 0; i--) {
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -0800220 InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
221 if (mAnimationDirection == DIRECTION_SHOW) {
222 cancelExistingAnimation();
223 } else if (!consumer.isVisible() || mAnimationDirection == DIRECTION_HIDE) {
224 // no-op: already hidden or animating out.
225 continue;
226 }
227 typesReady |= InsetsState.toPublicType(consumer.getType());
Jorim Jaggib6030952018-10-23 18:31:52 +0200228 }
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -0800229 applyAnimation(typesReady, false /* show */);
Jorim Jaggib6030952018-10-23 18:31:52 +0200230 }
231
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100232 @Override
233 public void controlWindowInsetsAnimation(@InsetType int types,
234 WindowInsetsAnimationControlListener listener) {
235
236 // TODO: Check whether we already have a controller.
237 final ArraySet<Integer> internalTypes = mState.toInternalType(types);
238 final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();
239 for (int i = internalTypes.size() - 1; i >= 0; i--) {
240 InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
241 if (consumer.getControl() != null) {
242 consumers.put(consumer.getType(), consumer);
243 } else {
244 // TODO: Let calling app know it's not possible, or wait
245 // TODO: Remove it from types
246 }
247 }
248 final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
249 mFrame, mState, listener, types,
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800250 () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100251 mAnimationControls.add(controller);
252 }
253
Jorim Jaggic8d60382018-10-31 17:06:06 +0100254 private void applyLocalVisibilityOverride() {
255 for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
256 final InsetsSourceConsumer controller = mSourceConsumers.valueAt(i);
257 controller.applyLocalVisibilityOverride();
258 }
259 }
260
Jorim Jaggib6030952018-10-23 18:31:52 +0200261 @VisibleForTesting
262 public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetType int type) {
263 InsetsSourceConsumer controller = mSourceConsumers.get(type);
264 if (controller != null) {
265 return controller;
266 }
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -0800267 controller = createConsumerOfType(type);
Jorim Jaggib6030952018-10-23 18:31:52 +0200268 mSourceConsumers.put(type, controller);
269 return controller;
270 }
271
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100272 @VisibleForTesting
273 public void notifyVisibilityChanged() {
Jorim Jaggic8d60382018-10-31 17:06:06 +0100274 mViewRoot.notifyInsetsChanged();
Jorim Jaggie35c0592018-11-06 16:21:08 +0100275 sendStateToWindowManager();
276 }
277
278 /**
Tarandeep Singh2cbcd7f2019-01-25 11:47:57 -0800279 * Called when current window gains focus.
280 */
281 public void onWindowFocusGained() {
282 getSourceConsumer(TYPE_IME).onWindowFocusGained();
283 }
284
285 /**
286 * Called when current window loses focus.
287 */
288 public void onWindowFocusLost() {
289 getSourceConsumer(TYPE_IME).onWindowFocusLost();
290 }
291
292 ViewRootImpl getViewRoot() {
293 return mViewRoot;
294 }
295
296 private InsetsSourceConsumer createConsumerOfType(int type) {
297 if (type == TYPE_IME) {
298 return new ImeInsetsSourceConsumer(mState, Transaction::new, this);
299 } else {
300 return new InsetsSourceConsumer(type, mState, Transaction::new, this);
301 }
302 }
303
304 /**
Jorim Jaggie35c0592018-11-06 16:21:08 +0100305 * Sends the local visibility state back to window manager.
306 */
307 private void sendStateToWindowManager() {
308 InsetsState tmpState = new InsetsState();
309 for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
310 final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
311 if (consumer.getControl() != null) {
312 tmpState.addSource(mState.getSource(consumer.getType()));
313 }
314 }
315
316 // TODO: Put this on a dispatcher thread.
317 try {
318 mViewRoot.mWindowSession.insetsModified(mViewRoot.mWindow, tmpState);
319 } catch (RemoteException e) {
320 Log.e(TAG, "Failed to call insetsModified", e);
321 }
Jorim Jaggic8d60382018-10-31 17:06:06 +0100322 }
323
Tarandeep Singh22f2b4c2019-01-10 19:41:30 -0800324 private void applyAnimation(@InsetType final int types, boolean show) {
325 if (types == 0) {
326 // nothing to animate.
327 return;
328 }
329 WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
330 @Override
331 public void onReady(WindowInsetsAnimationController controller, int types) {
332 mAnimator = ObjectAnimator.ofObject(
333 controller,
334 new InsetsProperty(),
335 sEvaluator,
336 show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
337 show ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
338 );
339 mAnimator.setDuration(ANIMATION_DURATION_MS);
340 mAnimator.addListener(new AnimatorListenerAdapter() {
341 @Override
342 public void onAnimationCancel(Animator animation) {
343 onAnimationFinish();
344 }
345
346 @Override
347 public void onAnimationEnd(Animator animation) {
348 onAnimationFinish();
349 }
350 });
351 mAnimator.start();
352 }
353
354 @Override
355 public void onCancelled() {}
356
357 private void onAnimationFinish() {
358 mAnimationDirection = DIRECTION_NONE;
359 if (show) {
360 showOnAnimationEnd(types);
361 } else {
362 hideOnAnimationEnd(types);
363 }
364 }
365 };
366 // TODO: Instead of clearing this here, properly wire up
367 // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls.
368 mAnimationControls.clear();
369 controlWindowInsetsAnimation(types, listener);
370 }
371
372 private void hideOnAnimationEnd(@InsetType int types) {
373 final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
374 for (int i = internalTypes.size() - 1; i >= 0; i--) {
375 getSourceConsumer(internalTypes.valueAt(i)).hide();
376 }
377 }
378
379 private void showOnAnimationEnd(@InsetType int types) {
380 final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
381 for (int i = internalTypes.size() - 1; i >= 0; i--) {
382 getSourceConsumer(internalTypes.valueAt(i)).show();
383 }
384 }
385
386 /**
387 * Cancel on-going animation to show/hide {@link InsetType}.
388 */
389 @VisibleForTesting
390 public void cancelExistingAnimation() {
391 mAnimationDirection = DIRECTION_NONE;
392 if (mAnimator != null) {
393 mAnimator.cancel();
394 }
395 }
396
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200397 void dump(String prefix, PrintWriter pw) {
398 pw.println(prefix); pw.println("InsetsController:");
399 mState.dump(prefix + " ", pw);
400 }
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800401
Jorim Jaggifae3e272019-01-14 14:05:05 +0100402 @VisibleForTesting
403 public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800404 mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
405 }
406
Jorim Jaggifae3e272019-01-14 14:05:05 +0100407 @VisibleForTesting
408 public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800409 mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
410 }
411
Jorim Jaggifae3e272019-01-14 14:05:05 +0100412 @VisibleForTesting
413 public void scheduleApplyChangeInsets() {
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800414 if (!mAnimCallbackScheduled) {
415 mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
416 mAnimCallback, null /* token*/);
417 mAnimCallbackScheduled = true;
418 }
419 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200420}