1 /*
2  * Copyright (C) 2021 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 package com.android.launcher3.anim;
17 
18 import android.os.Build;
19 import android.view.View;
20 import android.view.WindowInsets;
21 import android.view.WindowInsetsAnimation;
22 
23 import androidx.annotation.RequiresApi;
24 
25 import com.android.launcher3.Utilities;
26 
27 import java.util.List;
28 
29 /**
30  * Callback that animates views above the IME.
31  * <p>
32  * The expected stages of a keyboard transition are:
33  * <p>
34  * <ul>
35  *   <li>PREPARING: Keyboard insets haven't changed yet, but are about to.</li>
36  *   <li>STARTED: Keyboard insets have temporarily changed to the end state, but not drawn.</li>
37  *   <li>PROGRESSING: At least one frame of the animation has been drawn.</li>
38  *   <li>FINISHED: Keyboard has reached its end state, and animation is complete.</li>
39  * </ul>
40  */
41 @RequiresApi(api = Build.VERSION_CODES.R)
42 public class KeyboardInsetAnimationCallback extends WindowInsetsAnimation.Callback {
43     private final View mView;
44 
45     private float mInitialTranslation;
46     private float mTerminalTranslation;
47     private KeyboardTranslationState mKeyboardTranslationState = KeyboardTranslationState.SYSTEM;
48 
49     /** Current state of the keyboard. */
50     public enum KeyboardTranslationState {
51         // We are not controlling the keyboard, and it may or may not be translating.
52         SYSTEM,
53         // We are about to gain control of the keyboard, but the current state may be transient.
54         MANUAL_PREPARED,
55         // We are manually translating the keyboard.
56         MANUAL_ONGOING
57     }
58 
KeyboardInsetAnimationCallback(View view)59     public KeyboardInsetAnimationCallback(View view) {
60         super(DISPATCH_MODE_STOP);
61         mView = view;
62     }
63 
getKeyboardTranslationState()64     public KeyboardTranslationState getKeyboardTranslationState() {
65         return mKeyboardTranslationState;
66     }
67 
68     @Override
onPrepare(WindowInsetsAnimation animation)69     public void onPrepare(WindowInsetsAnimation animation) {
70         mKeyboardTranslationState = KeyboardTranslationState.MANUAL_PREPARED;
71         mInitialTranslation = mView.getTranslationY();
72     }
73 
74     @Override
onStart(WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds)75     public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation,
76             WindowInsetsAnimation.Bounds bounds) {
77         // Final insets have temporarily been applied, so store the current translation as final.
78         mTerminalTranslation = mView.getTranslationY();
79         // Reset the translation in case the view is drawn before onProgress gets called.
80         mView.setTranslationY(mInitialTranslation);
81         mKeyboardTranslationState = KeyboardTranslationState.MANUAL_ONGOING;
82         if (mView instanceof KeyboardInsetListener) {
83             ((KeyboardInsetListener) mView).onTranslationStart();
84         }
85         return super.onStart(animation, bounds);
86     }
87 
88     @Override
onProgress(WindowInsets windowInsets, List<WindowInsetsAnimation> list)89     public WindowInsets onProgress(WindowInsets windowInsets, List<WindowInsetsAnimation> list) {
90         if (list.size() == 0) {
91             mView.setTranslationY(mInitialTranslation);
92             return windowInsets;
93         }
94         WindowInsetsAnimation animation = list.get(0);
95 
96         if (animation.getDurationMillis() > -1) {
97             float progress = animation.getInterpolatedFraction();
98             mView.setTranslationY(
99                     Utilities.mapRange(progress, mInitialTranslation, mTerminalTranslation));
100         } else {
101             // Manually controlled animation: Set translation to keyboard height.
102             int translationY = -windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
103             if (mView.getParent() instanceof View) {
104                 // Offset any translation of the parent (e.g. All Apps parallax).
105                 translationY -= ((View) mView.getParent()).getTranslationY();
106             }
107             mView.setTranslationY(translationY);
108         }
109 
110         if (mView instanceof KeyboardInsetListener) {
111             ((KeyboardInsetListener) mView).onKeyboardAlphaChanged(animation.getAlpha());
112         }
113 
114         return windowInsets;
115     }
116 
117     @Override
onEnd(WindowInsetsAnimation animation)118     public void onEnd(WindowInsetsAnimation animation) {
119         if (mView instanceof KeyboardInsetListener) {
120             ((KeyboardInsetListener) mView).onTranslationEnd();
121         }
122         mKeyboardTranslationState = KeyboardTranslationState.SYSTEM;
123     }
124 
125     /**
126      * Interface Allowing views to listen for keyboard translation events
127      */
128     public interface KeyboardInsetListener {
129         /**
130          * Called from {@link KeyboardInsetAnimationCallback#onStart}
131          */
onTranslationStart()132         void onTranslationStart();
133 
134         /**
135          * Called from {@link KeyboardInsetAnimationCallback#onProgress}
136          *
137          * @param alpha the current IME alpha
138          */
onKeyboardAlphaChanged(float alpha)139         default void onKeyboardAlphaChanged(float alpha) {}
140 
141         /**
142          * Called from {@link KeyboardInsetAnimationCallback#onEnd}
143          */
onTranslationEnd()144         void onTranslationEnd();
145     }
146 }
147