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