ChooseLockGeneric.java revision 31dac17c4b0b49b4968b42b2af6f0799a0508a2f
1/*
2 * Copyright (C) 2010 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 com.android.settings;
18
19import android.app.Activity;
20import android.app.PendingIntent;
21import android.app.admin.DevicePolicyManager;
22import android.content.Context;
23import android.content.Intent;
24import android.os.Bundle;
25import android.os.SystemProperties;
26import android.preference.Preference;
27import android.preference.PreferenceActivity;
28import android.preference.PreferenceScreen;
29import android.security.KeyStore;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.ListView;
34
35import com.android.internal.widget.LockPatternUtils;
36
37public class ChooseLockGeneric extends PreferenceActivity {
38
39    @Override
40    public Intent getIntent() {
41        Intent modIntent = new Intent(super.getIntent());
42        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockGenericFragment.class.getName());
43        modIntent.putExtra(EXTRA_NO_HEADERS, true);
44        return modIntent;
45    }
46
47    public static class ChooseLockGenericFragment extends SettingsPreferenceFragment {
48        private static final int MIN_PASSWORD_LENGTH = 4;
49        private static final String KEY_UNLOCK_BACKUP_INFO = "unlock_backup_info";
50        private static final String KEY_UNLOCK_SET_OFF = "unlock_set_off";
51        private static final String KEY_UNLOCK_SET_NONE = "unlock_set_none";
52        private static final String KEY_UNLOCK_SET_BIOMETRIC_WEAK = "unlock_set_biometric_weak";
53        private static final String KEY_UNLOCK_SET_PIN = "unlock_set_pin";
54        private static final String KEY_UNLOCK_SET_PASSWORD = "unlock_set_password";
55        private static final String KEY_UNLOCK_SET_PATTERN = "unlock_set_pattern";
56        private static final int CONFIRM_EXISTING_REQUEST = 100;
57        private static final int FALLBACK_REQUEST = 101;
58        private static final String PASSWORD_CONFIRMED = "password_confirmed";
59        private static final String CONFIRM_CREDENTIALS = "confirm_credentials";
60        public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
61
62        private ChooseLockSettingsHelper mChooseLockSettingsHelper;
63        private DevicePolicyManager mDPM;
64        private KeyStore mKeyStore;
65        private boolean mPasswordConfirmed = false;
66
67        @Override
68        public void onCreate(Bundle savedInstanceState) {
69            super.onCreate(savedInstanceState);
70
71            mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
72            mKeyStore = KeyStore.getInstance();
73            mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());
74
75            // Defaults to needing to confirm credentials
76            final boolean confirmCredentials = getActivity().getIntent()
77                .getBooleanExtra(CONFIRM_CREDENTIALS, true);
78            mPasswordConfirmed = !confirmCredentials;
79
80            if (savedInstanceState != null) {
81                mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
82            }
83
84            if (!mPasswordConfirmed) {
85                ChooseLockSettingsHelper helper =
86                        new ChooseLockSettingsHelper(this.getActivity(), this);
87                if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) {
88                    mPasswordConfirmed = true; // no password set, so no need to confirm
89                    updatePreferencesOrFinish();
90                }
91            } else {
92                updatePreferencesOrFinish();
93            }
94        }
95
96        @Override
97        public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
98                Preference preference) {
99            final String key = preference.getKey();
100            boolean handled = true;
101            if (KEY_UNLOCK_SET_OFF.equals(key)) {
102                updateUnlockMethodAndFinish(
103                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, true);
104            } else if (KEY_UNLOCK_SET_NONE.equals(key)) {
105                updateUnlockMethodAndFinish(
106                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, false);
107            } else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) {
108                updateUnlockMethodAndFinish(
109                        DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, false);
110            }else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
111                updateUnlockMethodAndFinish(
112                        DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false);
113            } else if (KEY_UNLOCK_SET_PIN.equals(key)) {
114                updateUnlockMethodAndFinish(
115                        DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, false);
116            } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
117                updateUnlockMethodAndFinish(
118                        DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, false);
119            } else {
120                handled = false;
121            }
122            return handled;
123        }
124
125        @Override
126        public View onCreateView(LayoutInflater inflater, ViewGroup container,
127                Bundle savedInstanceState) {
128            View v = super.onCreateView(inflater, container, savedInstanceState);
129            final boolean onlyShowFallback = getActivity().getIntent()
130                    .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
131            if (onlyShowFallback) {
132                View header = v.inflate(getActivity(),
133                        R.layout.weak_biometric_fallback_header, null);
134                ((ListView) v.findViewById(android.R.id.list)).addHeaderView(header, null, false);
135            }
136
137            return v;
138        }
139
140        @Override
141        public void onActivityResult(int requestCode, int resultCode, Intent data) {
142            super.onActivityResult(requestCode, resultCode, data);
143            if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) {
144                mPasswordConfirmed = true;
145                updatePreferencesOrFinish();
146            } else if(requestCode == FALLBACK_REQUEST) {
147                mChooseLockSettingsHelper.utils().deleteTempGallery();
148                getActivity().setResult(resultCode);
149                finish();
150            } else {
151                getActivity().setResult(Activity.RESULT_CANCELED);
152                finish();
153            }
154        }
155
156        @Override
157        public void onSaveInstanceState(Bundle outState) {
158            super.onSaveInstanceState(outState);
159            // Saved so we don't force user to re-enter their password if configuration changes
160            outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed);
161        }
162
163        private void updatePreferencesOrFinish() {
164            Intent intent = getActivity().getIntent();
165            int quality = intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1);
166            if (quality == -1) {
167                // If caller didn't specify password quality, show UI and allow the user to choose.
168                quality = intent.getIntExtra(MINIMUM_QUALITY_KEY, -1);
169                quality = upgradeQuality(quality);
170                final PreferenceScreen prefScreen = getPreferenceScreen();
171                if (prefScreen != null) {
172                    prefScreen.removeAll();
173                }
174                addPreferencesFromResource(R.xml.security_settings_picker);
175                disableUnusablePreferences(quality);
176            } else {
177                updateUnlockMethodAndFinish(quality, false);
178            }
179        }
180
181        private int upgradeQuality(int quality) {
182            quality = upgradeQualityForDPM(quality);
183            quality = upgradeQualityForEncryption(quality);
184            quality = upgradeQualityForKeyStore(quality);
185            return quality;
186        }
187
188        private int upgradeQualityForDPM(int quality) {
189            // Compare min allowed password quality
190            int minQuality = mDPM.getPasswordQuality(null);
191            if (quality < minQuality) {
192                quality = minQuality;
193            }
194            return quality;
195        }
196
197        /**
198         * Mix in "encryption minimums" to any given quality value.  This prevents users
199         * from downgrading the pattern/pin/password to a level below the minimums.
200         *
201         * ASSUMPTION:  Setting quality is sufficient (e.g. minimum lengths will be set
202         * appropriately.)
203         */
204        private int upgradeQualityForEncryption(int quality) {
205            int encryptionStatus = mDPM.getStorageEncryptionStatus();
206            boolean encrypted = (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE)
207                    || (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING);
208            if (encrypted) {
209                if (quality < CryptKeeperSettings.MIN_PASSWORD_QUALITY) {
210                    quality = CryptKeeperSettings.MIN_PASSWORD_QUALITY;
211                }
212            }
213            return quality;
214        }
215
216        private int upgradeQualityForKeyStore(int quality) {
217            if (!mKeyStore.isEmpty()) {
218                if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) {
219                    quality = CredentialStorage.MIN_PASSWORD_QUALITY;
220                }
221            }
222            return quality;
223        }
224
225        /***
226         * Disables preferences that are less secure than required quality.
227         *
228         * @param quality the requested quality.
229         */
230        private void disableUnusablePreferences(final int quality) {
231            final PreferenceScreen entries = getPreferenceScreen();
232            final boolean onlyShowFallback = getActivity().getIntent()
233                    .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
234            final boolean weakBiometricAvailable = isBiometricSensorAvailable(
235                    DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK);
236            for (int i = entries.getPreferenceCount() - 1; i >= 0; --i) {
237                Preference pref = entries.getPreference(i);
238                if (pref instanceof PreferenceScreen) {
239                    final String key = ((PreferenceScreen) pref).getKey();
240                    boolean enabled = true;
241                    boolean visible = true;
242                    if (KEY_UNLOCK_SET_OFF.equals(key)) {
243                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
244                    } else if (KEY_UNLOCK_SET_NONE.equals(key)) {
245                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
246                    } else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) {
247                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
248                        visible = weakBiometricAvailable; // If not available, then don't show it.
249                    } else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
250                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
251                    } else if (KEY_UNLOCK_SET_PIN.equals(key)) {
252                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
253                    } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
254                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
255                    }
256                    if (!visible || (onlyShowFallback && !allowedForFallback(key))) {
257                        entries.removePreference(pref);
258                    } else if (!enabled) {
259                        pref.setSummary(R.string.unlock_set_unlock_disabled_summary);
260                        pref.setEnabled(false);
261                    }
262                }
263            }
264        }
265
266        /**
267         * Check whether the key is allowed for fallback (e.g. bio sensor). Returns true if it's
268         * supported as a backup.
269         *
270         * @param key
271         * @return true if allowed
272         */
273        private boolean allowedForFallback(String key) {
274            return KEY_UNLOCK_BACKUP_INFO.equals(key)  ||
275                    KEY_UNLOCK_SET_PATTERN.equals(key) || KEY_UNLOCK_SET_PIN.equals(key);
276        }
277
278        private boolean isBiometricSensorAvailable(int quality) {
279            return SystemProperties.getBoolean("ro.lockscreen.facelock_enabled", false);
280        }
281
282        private Intent getBiometricSensorIntent(int quality) {
283            Intent fallBackIntent = new Intent().setClass(getActivity(), ChooseLockGeneric.class);
284            fallBackIntent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, true);
285            fallBackIntent.putExtra(CONFIRM_CREDENTIALS, false);
286            fallBackIntent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE,
287                    R.string.backup_lock_settings_picker_title);
288            fallBackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
289
290            Intent intent = new Intent().setClassName("com.android.facelock",
291                    "com.android.facelock.SetupFaceLock");
292            PendingIntent pending = PendingIntent.getActivity(getActivity(), 0, fallBackIntent, 0);
293            intent.putExtra("PendingIntent", pending);
294            return intent;
295        }
296
297        /**
298         * Invokes an activity to change the user's pattern, password or PIN based on given quality
299         * and minimum quality specified by DevicePolicyManager. If quality is
300         * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, password is cleared.
301         *
302         * @param quality the desired quality. Ignored if DevicePolicyManager requires more security
303         * @param disabled whether or not to show LockScreen at all. Only meaningful when quality is
304         * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}
305         */
306        void updateUnlockMethodAndFinish(int quality, boolean disabled) {
307            // Sanity check. We should never get here without confirming user's existing password.
308            if (!mPasswordConfirmed) {
309                throw new IllegalStateException("Tried to update password without confirming it");
310            }
311
312            final boolean isFallback = getActivity().getIntent()
313                .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
314
315            quality = upgradeQuality(quality);
316
317            if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
318                int minLength = mDPM.getPasswordMinimumLength(null);
319                if (minLength < MIN_PASSWORD_LENGTH) {
320                    minLength = MIN_PASSWORD_LENGTH;
321                }
322                final int maxLength = mDPM.getPasswordMaximumLength(quality);
323                Intent intent = new Intent().setClass(getActivity(), ChooseLockPassword.class);
324                intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
325                intent.putExtra(ChooseLockPassword.PASSWORD_MIN_KEY, minLength);
326                intent.putExtra(ChooseLockPassword.PASSWORD_MAX_KEY, maxLength);
327                intent.putExtra(CONFIRM_CREDENTIALS, false);
328                intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
329                        isFallback);
330                if(isFallback) {
331                    startActivityForResult(intent, FALLBACK_REQUEST);
332                    return;
333                }
334                else {
335                    intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
336                    startActivity(intent);
337                }
338            } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
339                boolean showTutorial = !mChooseLockSettingsHelper.utils().isPatternEverChosen();
340                Intent intent = new Intent();
341                intent.setClass(getActivity(), showTutorial
342                        ? ChooseLockPatternTutorial.class
343                        : ChooseLockPattern.class);
344                intent.putExtra("key_lock_method", "pattern");
345                intent.putExtra(CONFIRM_CREDENTIALS, false);
346                intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
347                        isFallback);
348                if(isFallback) {
349                    startActivityForResult(intent, FALLBACK_REQUEST);
350                    return;
351                }
352                else {
353                    intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
354                    startActivity(intent);
355                }
356            } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
357                Intent intent = getBiometricSensorIntent(quality);
358                startActivity(intent);
359            } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
360                mChooseLockSettingsHelper.utils().clearLock(false);
361                mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled);
362                getActivity().setResult(Activity.RESULT_OK);
363            }
364            finish();
365        }
366    }
367}
368