1 /*
2  * Copyright (C) 2012 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.keyguard;
18 
19 import static android.view.WindowInsets.Type.ime;
20 
21 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
22 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
23 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT;
24 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
25 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
26 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE;
27 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
28 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
29 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
30 
31 import android.animation.Animator;
32 import android.animation.AnimatorListenerAdapter;
33 import android.animation.ValueAnimator;
34 import android.content.Context;
35 import android.content.res.ColorStateList;
36 import android.graphics.Insets;
37 import android.graphics.Rect;
38 import android.os.Trace;
39 import android.util.AttributeSet;
40 import android.view.WindowInsets;
41 import android.view.WindowInsetsAnimationControlListener;
42 import android.view.WindowInsetsAnimationController;
43 import android.view.animation.AnimationUtils;
44 import android.view.animation.Interpolator;
45 import android.widget.TextView;
46 
47 import androidx.annotation.NonNull;
48 import androidx.annotation.Nullable;
49 
50 import com.android.app.animation.Interpolators;
51 import com.android.internal.widget.LockscreenCredential;
52 import com.android.internal.widget.TextViewInputDisabler;
53 import com.android.systemui.DejankUtils;
54 import com.android.systemui.R;
55 /**
56  * Displays an alphanumeric (latin-1) key entry for the user to enter
57  * an unlock password
58  */
59 public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
60 
61     private final int mDisappearYTranslation;
62 
63     private static final long IME_DISAPPEAR_DURATION_MS = 125;
64 
65     // A delay constant to be used in a workaround for the situation where InputMethodManagerService
66     // is not switched to the new user yet.
67     // TODO: Remove this by ensuring such a race condition never happens.
68 
69     private TextView mPasswordEntry;
70     private TextViewInputDisabler mPasswordEntryDisabler;
71 
72     private Interpolator mLinearOutSlowInInterpolator;
73     private Interpolator mFastOutLinearInInterpolator;
74     private DisappearAnimationListener mDisappearAnimationListener;
75     private static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled};
76     private static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled};
77 
KeyguardPasswordView(Context context)78     public KeyguardPasswordView(Context context) {
79         this(context, null);
80     }
81 
KeyguardPasswordView(Context context, AttributeSet attrs)82     public KeyguardPasswordView(Context context, AttributeSet attrs) {
83         super(context, attrs);
84         mDisappearYTranslation = getResources().getDimensionPixelSize(
85                 R.dimen.disappear_y_translation);
86         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
87                 context, android.R.interpolator.linear_out_slow_in);
88         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
89                 context, android.R.interpolator.fast_out_linear_in);
90     }
91 
92     @Override
resetState()93     protected void resetState() {
94     }
95 
96     @Override
getPasswordTextViewId()97     protected int getPasswordTextViewId() {
98         return R.id.passwordEntry;
99     }
100 
101     @Override
getPromptReasonStringRes(int reason)102     protected int getPromptReasonStringRes(int reason) {
103         switch (reason) {
104             case PROMPT_REASON_RESTART:
105                 return R.string.kg_prompt_reason_restart_password;
106             case PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE:
107                 return R.string.kg_prompt_after_update_password;
108             case PROMPT_REASON_TIMEOUT:
109                 return R.string.kg_prompt_reason_timeout_password;
110             case PROMPT_REASON_DEVICE_ADMIN:
111                 return R.string.kg_prompt_reason_device_admin;
112             case PROMPT_REASON_USER_REQUEST:
113                 return R.string.kg_prompt_after_user_lockdown_password;
114             case PROMPT_REASON_PREPARE_FOR_UPDATE:
115                 return R.string.kg_prompt_unattended_update_password;
116             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
117                 return R.string.kg_prompt_reason_timeout_password;
118             case PROMPT_REASON_TRUSTAGENT_EXPIRED:
119                 return R.string.kg_prompt_reason_timeout_password;
120             case PROMPT_REASON_NONE:
121                 return 0;
122             default:
123                 return R.string.kg_prompt_reason_timeout_password;
124         }
125     }
126 
127 
128     @Override
onFinishInflate()129     protected void onFinishInflate() {
130         super.onFinishInflate();
131 
132         mPasswordEntry = findViewById(getPasswordTextViewId());
133         mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
134     }
135 
136     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)137     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
138         // send focus to the password field
139         return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
140     }
141 
142     @Override
resetPasswordText(boolean animate, boolean announce)143     protected void resetPasswordText(boolean animate, boolean announce) {
144         mPasswordEntry.setText("");
145     }
146 
147     @Override
getEnteredCredential()148     protected LockscreenCredential getEnteredCredential() {
149         return LockscreenCredential.createPasswordOrNone(mPasswordEntry.getText());
150     }
151 
152     @Override
setPasswordEntryEnabled(boolean enabled)153     protected void setPasswordEntryEnabled(boolean enabled) {
154         int color = mPasswordEntry.getTextColors().getColorForState(
155                 enabled ? ENABLE_STATE_SET : DISABLE_STATE_SET, 0);
156         mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(color));
157         mPasswordEntry.setCursorVisible(enabled);
158     }
159 
160     @Override
setPasswordEntryInputEnabled(boolean enabled)161     protected void setPasswordEntryInputEnabled(boolean enabled) {
162         mPasswordEntryDisabler.setInputEnabled(enabled);
163     }
164 
165     @Override
getWrongPasswordStringId()166     public int getWrongPasswordStringId() {
167         return R.string.kg_wrong_password;
168     }
169 
170     @Override
startAppearAnimation()171     public void startAppearAnimation() {
172         // Reset state, and let IME animation reveal the view as it slides in, if one exists.
173         // It is possible for an IME to have no view, so provide a default animation since no
174         // calls to animateForIme would occur
175         setAlpha(0f);
176         animate()
177             .alpha(1f)
178             .setDuration(300)
179             .start();
180 
181         setTranslationY(0f);
182     }
183 
184     @Override
startDisappearAnimation(Runnable finishRunnable)185     public boolean startDisappearAnimation(Runnable finishRunnable) {
186         getWindowInsetsController().controlWindowInsetsAnimation(ime(),
187                 100,
188                 Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
189 
190                     @Override
191                     public void onReady(@NonNull WindowInsetsAnimationController controller,
192                             int types) {
193                         ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
194                         anim.addUpdateListener(animation -> {
195                             if (controller.isCancelled()) {
196                                 return;
197                             }
198                             float value = (float) animation.getAnimatedValue();
199                             float fraction = anim.getAnimatedFraction();
200                             Insets shownInsets = controller.getShownStateInsets();
201                             int dist = (int) (-shownInsets.bottom / 4
202                                     * fraction);
203                             Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0, dist));
204                             if (mDisappearAnimationListener != null) {
205                                 mDisappearAnimationListener.setTranslationY(-dist);
206                             }
207 
208                             controller.setInsetsAndAlpha(insets, value, fraction);
209                             setAlpha(value);
210                         });
211                         anim.addListener(new AnimatorListenerAdapter() {
212                             @Override
213                             public void onAnimationStart(Animator animation) {
214                             }
215 
216                             @Override
217                             public void onAnimationEnd(Animator animation) {
218                                 // Run this in the next frame since it results in a slow binder call
219                                 // to InputMethodManager#hideSoftInput()
220                                 DejankUtils.postAfterTraversal(() -> {
221                                     Trace.beginSection("KeyguardPasswordView#onAnimationEnd");
222                                     // // TODO(b/230620476): Make hideSoftInput oneway
223                                     // controller.finish() eventually calls hideSoftInput
224                                     controller.finish(false);
225                                     runOnFinishImeAnimationRunnable();
226                                     finishRunnable.run();
227                                     mDisappearAnimationListener = null;
228                                     Trace.endSection();
229                                 });
230                             }
231                         });
232                         anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
233                         anim.start();
234                     }
235 
236                     @Override
237                     public void onFinished(
238                             @NonNull WindowInsetsAnimationController controller) {
239                     }
240 
241                     @Override
242                     public void onCancelled(
243                             @Nullable WindowInsetsAnimationController controller) {
244                         // It is possible to be denied control of ime insets, which means onReady
245                         // is never called. We still need to notify the runnables in order to
246                         // complete the bouncer disappearing
247                         runOnFinishImeAnimationRunnable();
248                         finishRunnable.run();
249                     }
250                 });
251         return true;
252     }
253 
254     @Override
getTitle()255     public CharSequence getTitle() {
256         return getResources().getString(
257                 com.android.internal.R.string.keyguard_accessibility_password_unlock);
258     }
259 
260     @Override
onApplyWindowInsets(WindowInsets insets)261     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
262         if (!mPasswordEntry.isFocused() && isVisibleToUser()) {
263             mPasswordEntry.requestFocus();
264         }
265         return super.onApplyWindowInsets(insets);
266     }
267 
268     @Override
onWindowFocusChanged(boolean hasWindowFocus)269     public void onWindowFocusChanged(boolean hasWindowFocus) {
270         super.onWindowFocusChanged(hasWindowFocus);
271         if (hasWindowFocus) {
272             if (isVisibleToUser()) {
273                 showKeyboard();
274             } else {
275                 hideKeyboard();
276             }
277         }
278     }
279 
280     /**
281      * Sends signal to the focused window to show the keyboard.
282      */
showKeyboard()283     public void showKeyboard() {
284         post(() -> {
285             if (mPasswordEntry.isAttachedToWindow()
286                     && !mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
287                 mPasswordEntry.requestFocus();
288                 mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
289             }
290         });
291     }
292 
293     /**
294      * Sends signal to the focused window to hide the keyboard.
295      */
hideKeyboard()296     public void hideKeyboard() {
297         post(() -> {
298             if (mPasswordEntry.isAttachedToWindow()
299                     && mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
300                 mPasswordEntry.clearFocus();
301                 mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
302             }
303         });
304     }
305 
306     /**
307      * Listens to the progress of the disappear animation and handles it.
308      */
309     interface DisappearAnimationListener {
setTranslationY(int transY)310         void setTranslationY(int transY);
311     }
312 
313     /**
314      * Set an instance of the disappear animation listener to this class. This will be
315      * removed when the animation completes.
316      */
setDisappearAnimationListener(DisappearAnimationListener listener)317     public void setDisappearAnimationListener(DisappearAnimationListener listener) {
318         mDisappearAnimationListener = listener;
319     }
320 }
321