5/n: Add face settings

This change connects the face status preference controller with the
settings fragment/activity. This change also implements the basic settings
page showing a video, a toggle for keyguard, improve/remove targets,
and footer.

Bug: 111321762

Test: manual
Change-Id: Ifc65f5acbf6551679074df63ef22ffba75229f37
This commit is contained in:
Kevin Chyn
2018-07-10 17:20:29 -07:00
parent 4fb1e1a633
commit 7189997bfa
17 changed files with 605 additions and 56 deletions

View File

@@ -16,6 +16,8 @@
package com.android.settings.biometrics;
import static android.app.Activity.RESULT_FIRST_USER;
import android.annotation.Nullable;
import android.content.Intent;
import android.content.res.Resources;
@@ -39,11 +41,33 @@ import com.android.setupwizardlib.GlifLayout;
*/
public abstract class BiometricEnrollBase extends InstrumentedActivity
implements View.OnClickListener {
public static final int RESULT_FINISHED = BiometricSettings.RESULT_FINISHED;
public static final int RESULT_SKIP = BiometricSettings.RESULT_SKIP;
public static final int RESULT_TIMEOUT = BiometricSettings.RESULT_TIMEOUT;
public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock";
/**
* Used by the choose fingerprint wizard to indicate the wizard is
* finished, and each activity in the wizard should finish.
* <p>
* Previously, each activity in the wizard would finish itself after
* starting the next activity. However, this leads to broken 'Back'
* behavior. So, now an activity does not finish itself until it gets this
* result.
*/
public static final int RESULT_FINISHED = RESULT_FIRST_USER;
/**
* Used by the enrolling screen during setup wizard to skip over setting up fingerprint, which
* will be useful if the user accidentally entered this flow.
*/
public static final int RESULT_SKIP = RESULT_FIRST_USER + 1;
/**
* Like {@link #RESULT_FINISHED} except this one indicates enrollment failed because the
* device was left idle. This is used to clear the credential token to require the user to
* re-enter their pin/pattern/password before continuing.
*/
public static final int RESULT_TIMEOUT = RESULT_FIRST_USER + 2;
public static final int CONFIRM_REQUEST = 1;
public static final int ENROLLING = 2;

View File

@@ -16,8 +16,8 @@
package com.android.settings.biometrics;
import static com.android.settings.biometrics.BiometricSettings.RESULT_FINISHED;
import static com.android.settings.biometrics.BiometricSettings.RESULT_TIMEOUT;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
import android.app.Activity;
import android.app.Dialog;

View File

@@ -1,48 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.settings.biometrics;
import com.android.settings.SubSettings;
/**
* Abstract base class for biometric settings, such as Fingerprint, Face, Iris
*/
public abstract class BiometricSettings extends SubSettings {
/**
* Used by the choose fingerprint wizard to indicate the wizard is
* finished, and each activity in the wizard should finish.
* <p>
* Previously, each activity in the wizard would finish itself after
* starting the next activity. However, this leads to broken 'Back'
* behavior. So, now an activity does not finish itself until it gets this
* result.
*/
protected static final int RESULT_FINISHED = RESULT_FIRST_USER;
/**
* Used by the enrolling screen during setup wizard to skip over setting up fingerprint, which
* will be useful if the user accidentally entered this flow.
*/
protected static final int RESULT_SKIP = RESULT_FIRST_USER + 1;
/**
* Like {@link #RESULT_FINISHED} except this one indicates enrollment failed because the
* device was left idle. This is used to clear the credential token to require the user to
* re-enter their pin/pattern/password before continuing.
*/
protected static final int RESULT_TIMEOUT = RESULT_FIRST_USER + 2;
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics.face;
import static com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST;
import android.content.Context;
import android.hardware.face.FaceManager;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import android.util.Log;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Settings screen for face authentication.
*/
@SearchIndexable
public class FaceSettings extends DashboardFragment {
private static final String TAG = "FaceSettings";
private static final String KEY_LAUNCHED_CONFIRM = "key_launched_confirm";
private boolean mLaunchedConfirm;
public static boolean isAvailable(Context context) {
FaceManager manager = Utils.getFaceManagerOrNull(context);
return manager != null && manager.isHardwareDetected();
}
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.FACE;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.security_settings_face;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_LAUNCHED_CONFIRM, mLaunchedConfirm);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mLaunchedConfirm = savedInstanceState.getBoolean(KEY_LAUNCHED_CONFIRM, false);
}
if (!mLaunchedConfirm) {
ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(getActivity(), this);
if (!helper.launchConfirmationActivity(CONFIRM_REQUEST,
getString(R.string.security_settings_face_preference_title))) {
Log.e(TAG, "Password not set");
finish();
}
}
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context, getSettingsLifecycle());
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new FaceSettingsVideoPreferenceController(context));
controllers.add(new FaceSettingsImprovePreferenceController(context));
controllers.add(new FaceSettingsUnlockPreferenceController(context));
controllers.add(new FaceSettingsRemoveButtonPreferenceController(context));
controllers.add(new FaceSettingsFooterPreferenceController(context));
return controllers;
}
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.security_settings_face;
return Arrays.asList(sir);
}
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context, null /* lifecycle */);
}
@Override
protected boolean isPageSearchEnabled(Context context) {
return isAvailable(context);
}
};
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.settings.biometrics.face;
import android.content.Context;
import android.content.Intent;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.utils.AnnotationSpan;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.widget.FooterPreference;
import androidx.preference.Preference;
/**
* Footer for face settings showing the help text and help link.
*/
public class FaceSettingsFooterPreferenceController extends BasePreferenceController {
private static final String ANNOTATION_URL = "url";
public FaceSettingsFooterPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
public FaceSettingsFooterPreferenceController(Context context) {
this(context, FooterPreference.KEY_FOOTER);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
final Intent helpIntent = HelpUtils.getHelpIntent(
mContext, mContext.getString(R.string.help_url_face), getClass().getName());
final AnnotationSpan.LinkInfo linkInfo =
new AnnotationSpan.LinkInfo(mContext, ANNOTATION_URL, helpIntent);
preference.setTitle(AnnotationSpan.linkify(
mContext.getText(R.string.security_settings_face_settings_footer), linkInfo));
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.settings.biometrics.face;
import android.content.Context;
import com.android.settings.core.BasePreferenceController;
import androidx.preference.Preference;
/**
* Preference controller which allows the user to update their enrolled face.
*/
public class FaceSettingsImprovePreferenceController extends BasePreferenceController {
private static final String KEY = "security_settings_face_improve";
public FaceSettingsImprovePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
public FaceSettingsImprovePreferenceController(Context context) {
this(context, KEY);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
return false;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.settings.biometrics.face;
import android.content.Context;
import android.view.View;
import android.widget.Button;
import com.android.settings.R;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.core.BasePreferenceController;
import androidx.preference.Preference;
/**
* Controller for the remove button.
*/
public class FaceSettingsRemoveButtonPreferenceController extends BasePreferenceController
implements View.OnClickListener {
private static final String KEY = "security_settings_face_delete_faces_container";
private Button mButton;
public FaceSettingsRemoveButtonPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
public FaceSettingsRemoveButtonPreferenceController(Context context) {
this(context, KEY);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
mButton = ((LayoutPreference) preference)
.findViewById(R.id.security_settings_face_settings_remove_button);
mButton.setOnClickListener(this);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public void onClick(View v) {
if (v == mButton) {
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.settings.biometrics.face;
import static android.provider.Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
import androidx.preference.Preference;
/**
* Preference controller for Face settings page controlling the ability to unlock the phone
* with face.
*/
public class FaceSettingsUnlockPreferenceController extends TogglePreferenceController {
private static final String KEY = "security_settings_face_unlock";
private static final int ON = 1;
private static final int OFF = 0;
private static final int DEFAULT = ON; // face unlock is enabled on keyguard by default
public FaceSettingsUnlockPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
public FaceSettingsUnlockPreferenceController(Context context) {
this(context, KEY);
}
@Override
public boolean isChecked() {
if (!FaceSettings.isAvailable(mContext)) {
return false;
} else if (adminDisabled()) {
return false;
}
return Settings.Secure.getInt(
mContext.getContentResolver(), FACE_UNLOCK_KEYGUARD_ENABLED, DEFAULT) == ON;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Secure.putInt(mContext.getContentResolver(), FACE_UNLOCK_KEYGUARD_ENABLED,
isChecked ? ON : OFF);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (!FaceSettings.isAvailable(mContext)) {
preference.setEnabled(false);
} else if (adminDisabled()) {
preference.setEnabled(false);
} else {
preference.setEnabled(true);
}
}
private boolean adminDisabled() {
DevicePolicyManager dpm =
(DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
return dpm != null &&
(dpm.getKeyguardDisabledFeatures(null, UserHandle.myUserId())
& DevicePolicyManager.KEYGUARD_DISABLE_FACE)
!= 0;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.settings.biometrics.face;
import android.content.Context;
import com.android.settings.core.BasePreferenceController;
public class FaceSettingsVideoPreferenceController extends BasePreferenceController {
private static final String PREF_KEY_VIDEO = "security_settings_face_video";
public FaceSettingsVideoPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
}
public FaceSettingsVideoPreferenceController(Context context) {
this(context, PREF_KEY_VIDEO);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
}

View File

@@ -20,6 +20,7 @@ import android.content.Context;
import android.hardware.face.FaceManager;
import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricStatusPreferenceController;
@@ -62,7 +63,7 @@ public class FaceStatusPreferenceController extends BiometricStatusPreferenceCon
@Override
protected String getSettingsClassName() {
return null;
return Settings.FaceSettingsActivity.class.getName();
}
@Override

View File

@@ -40,8 +40,9 @@ import android.widget.Toast;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.SubSettings;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricSettings;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
@@ -66,7 +67,7 @@ import androidx.preference.PreferenceViewHolder;
/**
* Settings screen for fingerprints
*/
public class FingerprintSettings extends BiometricSettings {
public class FingerprintSettings extends SubSettings {
private static final String TAG = "FingerprintSettings";
@@ -75,6 +76,10 @@ public class FingerprintSettings extends BiometricSettings {
public static final String ANNOTATION_URL = "url";
public static final String ANNOTATION_ADMIN_DETAILS = "admin_details";
private static final int RESULT_FINISHED = BiometricEnrollBase.RESULT_FINISHED;
private static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP;
private static final int RESULT_TIMEOUT = BiometricEnrollBase.RESULT_TIMEOUT;
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());