1 /*
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 
17 package android.view;
18 
19 import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
20 import static android.view.InsetsController.AnimationType;
21 import static android.view.InsetsController.DEBUG;
22 import static android.view.InsetsState.ISIDE_BOTTOM;
23 import static android.view.InsetsState.ISIDE_LEFT;
24 import static android.view.InsetsState.ISIDE_RIGHT;
25 import static android.view.InsetsState.ISIDE_TOP;
26 import static android.view.InsetsState.ITYPE_IME;
27 
28 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
29 
30 import android.annotation.Nullable;
31 import android.graphics.Insets;
32 import android.graphics.Matrix;
33 import android.graphics.Rect;
34 import android.util.ArraySet;
35 import android.util.Log;
36 import android.util.SparseArray;
37 import android.util.SparseIntArray;
38 import android.util.SparseSetArray;
39 import android.view.InsetsState.InternalInsetsSide;
40 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
41 import android.view.WindowInsets.Type.InsetsType;
42 import android.view.WindowInsetsAnimation.Bounds;
43 import android.view.WindowManager.LayoutParams;
44 import android.view.animation.Interpolator;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 
48 import java.util.ArrayList;
49 
50 /**
51  * Implements {@link WindowInsetsAnimationController}
52  * @hide
53  */
54 @VisibleForTesting
55 public class InsetsAnimationControlImpl implements WindowInsetsAnimationController,
56         InsetsAnimationControlRunner {
57 
58     private static final String TAG = "InsetsAnimationCtrlImpl";
59 
60     private final Rect mTmpFrame = new Rect();
61 
62     private final WindowInsetsAnimationControlListener mListener;
63     private final SparseArray<InsetsSourceControl> mControls;
64     private final SparseIntArray mTypeSideMap = new SparseIntArray();
65     private final SparseSetArray<InsetsSourceControl> mSideSourceMap = new SparseSetArray<>();
66 
67     /** @see WindowInsetsAnimationController#getHiddenStateInsets */
68     private final Insets mHiddenInsets;
69 
70     /** @see WindowInsetsAnimationController#getShownStateInsets */
71     private final Insets mShownInsets;
72     private final Matrix mTmpMatrix = new Matrix();
73     private final InsetsState mInitialInsetsState;
74     private final @AnimationType int mAnimationType;
75     private final @InsetsType int mTypes;
76     private final InsetsAnimationControlCallbacks mController;
77     private final WindowInsetsAnimation mAnimation;
78     /** @see WindowInsetsAnimationController#hasZeroInsetsIme */
79     private final boolean mHasZeroInsetsIme;
80     private Insets mCurrentInsets;
81     private Insets mPendingInsets;
82     private float mPendingFraction;
83     private boolean mFinished;
84     private boolean mCancelled;
85     private boolean mShownOnFinish;
86     private float mCurrentAlpha = 1.0f;
87     private float mPendingAlpha = 1.0f;
88     @VisibleForTesting(visibility = PACKAGE)
89     public boolean mReadyDispatched;
90     private Boolean mPerceptible;
91 
92     @VisibleForTesting
InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, @AnimationType int animationType)93     public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame,
94             InsetsState state, WindowInsetsAnimationControlListener listener,
95             @InsetsType int types,
96             InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator,
97             @AnimationType int animationType) {
98         mControls = controls;
99         mListener = listener;
100         mTypes = types;
101         mController = controller;
102         mInitialInsetsState = new InsetsState(state, true /* copySources */);
103         mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
104         mPendingInsets = mCurrentInsets;
105         mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
106                 null /* typeSideMap */);
107         mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
108                 mTypeSideMap);
109         mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
110         if (mHasZeroInsetsIme) {
111             // IME has shownInsets of ZERO, and can't map to a side by default.
112             // Map zero insets IME to bottom, making it a special case of bottom insets.
113             mTypeSideMap.put(ITYPE_IME, ISIDE_BOTTOM);
114         }
115         buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls);
116 
117         mAnimation = new WindowInsetsAnimation(mTypes, interpolator,
118                 durationMs);
119         mAnimation.setAlpha(getCurrentAlpha());
120         mAnimationType = animationType;
121         mController.startAnimation(this, listener, types, mAnimation,
122                 new Bounds(mHiddenInsets, mShownInsets));
123     }
124 
calculatePerceptible(Insets currentInsets, float currentAlpha)125     private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) {
126         return 100 * currentInsets.left >= 5 * (mShownInsets.left - mHiddenInsets.left)
127                 && 100 * currentInsets.top >= 5 * (mShownInsets.top - mHiddenInsets.top)
128                 && 100 * currentInsets.right >= 5 * (mShownInsets.right - mHiddenInsets.right)
129                 && 100 * currentInsets.bottom >= 5 * (mShownInsets.bottom - mHiddenInsets.bottom)
130                 && currentAlpha >= 0.5f;
131     }
132 
133     @Override
hasZeroInsetsIme()134     public boolean hasZeroInsetsIme() {
135         return mHasZeroInsetsIme;
136     }
137 
138     @Override
getHiddenStateInsets()139     public Insets getHiddenStateInsets() {
140         return mHiddenInsets;
141     }
142 
143     @Override
getShownStateInsets()144     public Insets getShownStateInsets() {
145         return mShownInsets;
146     }
147 
148     @Override
getCurrentInsets()149     public Insets getCurrentInsets() {
150         return mCurrentInsets;
151     }
152 
153     @Override
getCurrentAlpha()154     public float getCurrentAlpha() {
155         return mCurrentAlpha;
156     }
157 
158     @Override
getTypes()159     @InsetsType public int getTypes() {
160         return mTypes;
161     }
162 
163     @Override
getAnimationType()164     public @AnimationType int getAnimationType() {
165         return mAnimationType;
166     }
167 
168     @Override
setInsetsAndAlpha(Insets insets, float alpha, float fraction)169     public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
170         setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */);
171     }
172 
setInsetsAndAlpha(Insets insets, float alpha, float fraction, boolean allowWhenFinished)173     private void setInsetsAndAlpha(Insets insets, float alpha, float fraction,
174             boolean allowWhenFinished) {
175         if (!allowWhenFinished && mFinished) {
176             throw new IllegalStateException(
177                     "Can't change insets on an animation that is finished.");
178         }
179         if (mCancelled) {
180             throw new IllegalStateException(
181                     "Can't change insets on an animation that is cancelled.");
182         }
183         mPendingFraction = sanitize(fraction);
184         mPendingInsets = sanitize(insets);
185         mPendingAlpha = sanitize(alpha);
186         mController.scheduleApplyChangeInsets(this);
187         boolean perceptible = calculatePerceptible(mPendingInsets, mPendingAlpha);
188         if (mPerceptible == null || perceptible != mPerceptible) {
189             mController.reportPerceptible(mTypes, perceptible);
190             mPerceptible = perceptible;
191         }
192     }
193 
194     @VisibleForTesting
195     /**
196      * @return Whether the finish callback of this animation should be invoked.
197      */
applyChangeInsets(InsetsState state)198     public boolean applyChangeInsets(InsetsState state) {
199         if (mCancelled) {
200             if (DEBUG) Log.d(TAG, "applyChangeInsets canceled");
201             return false;
202         }
203         final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
204         ArrayList<SurfaceParams> params = new ArrayList<>();
205         updateLeashesForSide(ISIDE_LEFT, offset.left, mShownInsets.left, mPendingInsets.left,
206                 params, state, mPendingAlpha);
207         updateLeashesForSide(ISIDE_TOP, offset.top, mShownInsets.top, mPendingInsets.top, params,
208                 state, mPendingAlpha);
209         updateLeashesForSide(ISIDE_RIGHT, offset.right, mShownInsets.right, mPendingInsets.right,
210                 params, state, mPendingAlpha);
211         updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mShownInsets.bottom,
212                 mPendingInsets.bottom, params, state, mPendingAlpha);
213 
214         mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
215         mCurrentInsets = mPendingInsets;
216         mAnimation.setFraction(mPendingFraction);
217         mCurrentAlpha = mPendingAlpha;
218         mAnimation.setAlpha(mPendingAlpha);
219         if (mFinished) {
220             if (DEBUG) Log.d(TAG, String.format(
221                     "notifyFinished shown: %s, currentAlpha: %f, currentInsets: %s",
222                     mShownOnFinish, mCurrentAlpha, mCurrentInsets));
223             mController.notifyFinished(this, mShownOnFinish);
224             releaseLeashes();
225         }
226         if (DEBUG) Log.d(TAG, "Animation finished abruptly.");
227         return mFinished;
228     }
229 
releaseLeashes()230     private void releaseLeashes() {
231         for (int i = mControls.size() - 1; i >= 0; i--) {
232             final InsetsSourceControl c = mControls.valueAt(i);
233             if (c == null) continue;
234             c.release(mController::releaseSurfaceControlFromRt);
235         }
236     }
237 
238     @Override
finish(boolean shown)239     public void finish(boolean shown) {
240         if (mCancelled || mFinished) {
241             if (DEBUG) Log.d(TAG, "Animation already canceled or finished, not notifying.");
242             return;
243         }
244         mShownOnFinish = shown;
245         mFinished = true;
246         setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */,
247                 true /* allowWhenFinished */);
248 
249         if (DEBUG) Log.d(TAG, "notify control request finished for types: " + mTypes);
250         mListener.onFinished(this);
251     }
252 
253     @Override
254     @VisibleForTesting
getCurrentFraction()255     public float getCurrentFraction() {
256         return mAnimation.getFraction();
257     }
258 
259     @Override
cancel()260     public void cancel() {
261         if (mFinished) {
262             return;
263         }
264         mCancelled = true;
265         mListener.onCancelled(mReadyDispatched ? this : null);
266         if (DEBUG) Log.d(TAG, "notify Control request cancelled for types: " + mTypes);
267 
268         releaseLeashes();
269     }
270 
271     @Override
isFinished()272     public boolean isFinished() {
273         return mFinished;
274     }
275 
276     @Override
isCancelled()277     public boolean isCancelled() {
278         return mCancelled;
279     }
280 
281     @Override
getAnimation()282     public WindowInsetsAnimation getAnimation() {
283         return mAnimation;
284     }
285 
getListener()286     WindowInsetsAnimationControlListener getListener() {
287         return mListener;
288     }
289 
getControls()290     SparseArray<InsetsSourceControl> getControls() {
291         return mControls;
292     }
293 
calculateInsets(InsetsState state, Rect frame, SparseArray<InsetsSourceControl> controls, boolean shown, @Nullable @InternalInsetsSide SparseIntArray typeSideMap)294     private Insets calculateInsets(InsetsState state, Rect frame,
295             SparseArray<InsetsSourceControl> controls, boolean shown,
296             @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
297         for (int i = controls.size() - 1; i >= 0; i--) {
298             // control may be null if it got revoked.
299             if (controls.valueAt(i) == null) continue;
300             state.getSource(controls.valueAt(i).getType()).setVisible(shown);
301         }
302         return getInsetsFromState(state, frame, typeSideMap);
303     }
304 
getInsetsFromState(InsetsState state, Rect frame, @Nullable @InternalInsetsSide SparseIntArray typeSideMap)305     private Insets getInsetsFromState(InsetsState state, Rect frame,
306             @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
307         return state.calculateInsets(frame, null /* ignoringVisibilityState */,
308                 false /* isScreenRound */,
309                 false /* alwaysConsumeSystemBars */, null /* displayCutout */,
310                 LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/,
311                 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, typeSideMap)
312                .getInsets(mTypes);
313     }
314 
sanitize(Insets insets)315     private Insets sanitize(Insets insets) {
316         if (insets == null) {
317             insets = getCurrentInsets();
318         }
319         if (hasZeroInsetsIme()) {
320             return insets;
321         }
322         return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets);
323     }
324 
sanitize(float alpha)325     private static float sanitize(float alpha) {
326         return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha);
327     }
328 
updateLeashesForSide(@nternalInsetsSide int side, int offset, int maxInset, int inset, ArrayList<SurfaceParams> surfaceParams, InsetsState state, Float alpha)329     private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int maxInset,
330             int inset, ArrayList<SurfaceParams> surfaceParams, InsetsState state, Float alpha) {
331         ArraySet<InsetsSourceControl> items = mSideSourceMap.get(side);
332         if (items == null) {
333             return;
334         }
335         // TODO: Implement behavior when inset spans over multiple types
336         for (int i = items.size() - 1; i >= 0; i--) {
337             final InsetsSourceControl control = items.valueAt(i);
338             final InsetsSource source = mInitialInsetsState.getSource(control.getType());
339             final SurfaceControl leash = control.getLeash();
340 
341             mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
342             mTmpFrame.set(source.getFrame());
343             addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
344 
345             final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM
346                     ? (mAnimationType == ANIMATION_TYPE_SHOW ? true : !mFinished)
347                     : inset != 0;
348 
349             state.getSource(source.getType()).setVisible(visible);
350             state.getSource(source.getType()).setFrame(mTmpFrame);
351 
352             // If the system is controlling the insets source, the leash can be null.
353             if (leash != null) {
354                 SurfaceParams params = new SurfaceParams.Builder(leash)
355                         .withAlpha(alpha)
356                         .withMatrix(mTmpMatrix)
357                         .withVisibility(visible)
358                         .build();
359                 surfaceParams.add(params);
360             }
361         }
362     }
363 
addTranslationToMatrix(@nternalInsetsSide int side, int inset, Matrix m, Rect frame)364     private void addTranslationToMatrix(@InternalInsetsSide int side, int inset, Matrix m,
365             Rect frame) {
366         switch (side) {
367             case ISIDE_LEFT:
368                 m.postTranslate(-inset, 0);
369                 frame.offset(-inset, 0);
370                 break;
371             case ISIDE_TOP:
372                 m.postTranslate(0, -inset);
373                 frame.offset(0, -inset);
374                 break;
375             case ISIDE_RIGHT:
376                 m.postTranslate(inset, 0);
377                 frame.offset(inset, 0);
378                 break;
379             case ISIDE_BOTTOM:
380                 m.postTranslate(0, inset);
381                 frame.offset(0, inset);
382                 break;
383         }
384     }
385 
buildTypeSourcesMap(SparseIntArray typeSideMap, SparseSetArray<InsetsSourceControl> sideSourcesMap, SparseArray<InsetsSourceControl> controls)386     private static void buildTypeSourcesMap(SparseIntArray typeSideMap,
387             SparseSetArray<InsetsSourceControl> sideSourcesMap,
388             SparseArray<InsetsSourceControl> controls) {
389         for (int i = typeSideMap.size() - 1; i >= 0; i--) {
390             final int type = typeSideMap.keyAt(i);
391             final int side = typeSideMap.valueAt(i);
392             final InsetsSourceControl control = controls.get(type);
393             if (control == null) {
394                 // If the types that we are controlling are less than the types that the system has,
395                 // there can be some null controllers.
396                 continue;
397             }
398             sideSourcesMap.add(side, control);
399         }
400     }
401 }
402