diff --git a/res/drawable/volume_dialog_button_background_outline.xml b/res/drawable/volume_dialog_button_background_outline.xml
index 78f3fcf3c5b..efbaec63bd5 100644
--- a/res/drawable/volume_dialog_button_background_outline.xml
+++ b/res/drawable/volume_dialog_button_background_outline.xml
@@ -14,18 +14,21 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-
-
-
-
-
-
\ No newline at end of file
+ android:insetTop="6dp"
+ android:insetBottom="6dp">
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/volume_dialog_button_background_solid.xml b/res/drawable/volume_dialog_button_background_solid.xml
index 1fa8f20cf31..697131db247 100644
--- a/res/drawable/volume_dialog_button_background_solid.xml
+++ b/res/drawable/volume_dialog_button_background_solid.xml
@@ -15,17 +15,20 @@
limitations under the License.
-->
-
-
-
-
-
-
\ No newline at end of file
+ android:insetTop="6dp"
+ android:insetBottom="6dp">
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/panel_layout.xml b/res/layout/panel_layout.xml
index 2b9daebf85d..f154abc40cb 100644
--- a/res/layout/panel_layout.xml
+++ b/res/layout/panel_layout.xml
@@ -117,7 +117,7 @@
android:id="@+id/see_more"
style="@style/PanelOptionRoundedOutlinedButton"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:minWidth="0dp"
android:text="@string/settings_button"/>
@@ -130,7 +130,7 @@
android:id="@+id/done"
style="@style/PanelOptionRoundedSolidButton"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:minWidth="0dp"
android:text="@string/done"/>
diff --git a/res/layout/sfps_enroll_finish_base.xml b/res/layout/sfps_enroll_finish_base.xml
index 6e468c6472d..8d062d9409c 100644
--- a/res/layout/sfps_enroll_finish_base.xml
+++ b/res/layout/sfps_enroll_finish_base.xml
@@ -55,16 +55,20 @@
android:src="@drawable/sfps_enroll_finish" />
-
-
+
+
+ android:layout_gravity="center_horizontal|bottom" />
-
+
\ No newline at end of file
diff --git a/res/layout/sfps_require_screen_on_to_auth_toggle.xml b/res/layout/sfps_require_screen_on_to_auth_toggle.xml
new file mode 100644
index 00000000000..929b64bb2c4
--- /dev/null
+++ b/res/layout/sfps_require_screen_on_to_auth_toggle.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 03a103e8125..96ffb8d3480 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1012,6 +1012,8 @@
Fingerprint
+
+ When using Fingerprint Unlock
Fingerprint for work
@@ -1293,6 +1295,12 @@
Now you can use your fingerprint to unlock your device or verify it\u2019s you, like when you sign in to apps
Now you can use your fingerprint to unlock your phone or verify it\u2019s you, like when you sign in to apps
+
+ Unlock only when screen is on
+
+ The screen must be on before you can unlock with your fingerprint. This makes accidental unlocking less likely.
+
+ Screen, Unlock
Do it later
diff --git a/res/xml/security_settings_fingerprint.xml b/res/xml/security_settings_fingerprint.xml
index 804ef881630..a4ce545a7ff 100644
--- a/res/xml/security_settings_fingerprint.xml
+++ b/res/xml/security_settings_fingerprint.xml
@@ -16,5 +16,20 @@
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/security_settings_fingerprint_preference_title">
+
+
+
+
+
+
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
index 0c7ef982408..45063104134 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
@@ -25,6 +25,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
+import android.widget.CompoundButton;
import androidx.annotation.VisibleForTesting;
@@ -44,16 +45,24 @@ import java.util.List;
public class FingerprintEnrollFinish extends BiometricEnrollBase {
private static final String TAG = "FingerprintEnrollFinish";
+ private static final String KEY_REQUIRE_SCREEN_ON_TO_AUTH = "require_screen_on_to_auth_toggle";
private static final String ACTION_FINGERPRINT_SETTINGS =
"android.settings.FINGERPRINT_SETTINGS";
@VisibleForTesting
static final String FINGERPRINT_SUGGESTION_ACTIVITY =
"com.android.settings.SetupFingerprintSuggestionActivity";
+
private FingerprintManager mFingerprintManager;
+
+ private FingerprintSettingsRequireScreenOnToAuthPreferenceController
+ mRequireScreenOnToAuthPreferenceController;
+ private FingerprintRequireScreenOnToAuthToggle mRequireScreenOnToAuthToggle;
private boolean mCanAssumeSfps;
private boolean mIsAddAnotherOrFinish;
+ private CompoundButton.OnCheckedChangeListener mRequireScreenOnToAuthToggleListener;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -63,6 +72,11 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
mCanAssumeSfps = props != null && props.size() == 1 && props.get(0).isAnySidefpsType();
if (mCanAssumeSfps) {
setContentView(R.layout.sfps_enroll_finish);
+ mRequireScreenOnToAuthPreferenceController =
+ new FingerprintSettingsRequireScreenOnToAuthPreferenceController(
+ getApplicationContext(),
+ KEY_REQUIRE_SCREEN_ON_TO_AUTH
+ );
} else {
setContentView(R.layout.fingerprint_enroll_finish);
}
@@ -90,6 +104,20 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
.setTheme(R.style.SudGlifButton_Primary)
.build()
);
+
+ if (mCanAssumeSfps) {
+ mRequireScreenOnToAuthToggleListener =
+ (buttonView, isChecked) -> {
+ mRequireScreenOnToAuthPreferenceController.setChecked(isChecked);
+ };
+ mRequireScreenOnToAuthToggle = findViewById(R.id.require_screen_on_to_auth_toggle);
+ mRequireScreenOnToAuthToggle.setChecked(
+ mRequireScreenOnToAuthPreferenceController.isChecked());
+ mRequireScreenOnToAuthToggle.setListener(mRequireScreenOnToAuthToggleListener);
+ mRequireScreenOnToAuthToggle.setOnClickListener(v -> {
+ mRequireScreenOnToAuthToggle.getSwitch().toggle();
+ });
+ }
}
@Override
@@ -103,6 +131,12 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
@Override
protected void onResume() {
super.onResume();
+ if (mCanAssumeSfps) {
+ mRequireScreenOnToAuthToggleListener.onCheckedChanged(
+ mRequireScreenOnToAuthToggle.getSwitch(),
+ mRequireScreenOnToAuthToggle.isChecked()
+ );
+ }
FooterButton addButton = mFooterBarMixin.getSecondaryButton();
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintRequireScreenOnToAuthToggle.java b/src/com/android/settings/biometrics/fingerprint/FingerprintRequireScreenOnToAuthToggle.java
new file mode 100644
index 00000000000..f88c9aaec54
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintRequireScreenOnToAuthToggle.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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.fingerprint;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+
+import com.android.settings.R;
+
+/**
+ * A layout that contains a start-justified title, and an end-justified switch.
+ */
+public class FingerprintRequireScreenOnToAuthToggle extends LinearLayout {
+ private Switch mSwitch;
+
+ public FingerprintRequireScreenOnToAuthToggle(Context context) {
+ this(context, null /* attrs */);
+ }
+
+ public FingerprintRequireScreenOnToAuthToggle(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FingerprintRequireScreenOnToAuthToggle(
+ Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ LayoutInflater.from(context).inflate(R.layout.sfps_require_screen_on_to_auth_toggle,
+ this, true /* attachToRoot */);
+
+ mSwitch = findViewById(R.id.toggle);
+ mSwitch.setClickable(true);
+ mSwitch.setFocusable(false);
+ }
+
+ public boolean isChecked() {
+ return mSwitch.isChecked();
+ }
+
+ /**
+ *
+ * @param checked
+ */
+ public void setChecked(boolean checked) {
+ mSwitch.setChecked(checked);
+ }
+
+ /**
+ *
+ * @param listener
+ */
+ public void setListener(CompoundButton.OnCheckedChangeListener listener) {
+ mSwitch.setOnCheckedChangeListener(listener);
+ }
+
+ public Switch getSwitch() {
+ return mSwitch;
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index c031fe646dd..1c7c891899e 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -56,21 +56,24 @@ import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreference;
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.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.transition.SettingsTransitionHelper;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.TwoTargetPreference;
@@ -115,7 +118,26 @@ public class FingerprintSettings extends SubSettings {
setTitle(msg);
}
- public static class FingerprintSettingsFragment extends SettingsPreferenceFragment
+ /**
+ * @param context
+ * @return true if the Fingerprint hardware is detected.
+ */
+ public static boolean isFingerprintHardwareDetected(Context context) {
+ FingerprintManager manager = Utils.getFingerprintManagerOrNull(context);
+ boolean isHardwareDetected = false;
+ if (manager == null) {
+ Log.d(TAG, "FingerprintManager is null");
+ } else {
+ isHardwareDetected = manager.isHardwareDetected();
+ Log.d(TAG, "FingerprintManager is not null. Hardware detected: " + isHardwareDetected);
+ }
+ return manager != null && isHardwareDetected;
+ }
+
+ /**
+ *
+ */
+ public static class FingerprintSettingsFragment extends DashboardFragment
implements OnPreferenceChangeListener, FingerprintPreference.OnDeleteClickListener {
private static class FooterColumn {
@@ -134,6 +156,8 @@ public class FingerprintSettings extends SubSettings {
private static final String KEY_LAUNCHED_CONFIRM = "launched_confirm";
private static final String KEY_HAS_FIRST_ENROLLED = "has_first_enrolled";
private static final String KEY_IS_ENROLLING = "is_enrolled";
+ private static final String KEY_REQUIRE_SCREEN_ON_TO_AUTH =
+ "security_settings_require_screen_on_to_auth";
private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000;
private static final int MSG_FINGER_AUTH_SUCCESS = 1001;
@@ -149,6 +173,11 @@ public class FingerprintSettings extends SubSettings {
protected static final boolean DEBUG = false;
+ private List mControllers;
+ private FingerprintSettingsRequireScreenOnToAuthPreferenceController
+ mRequireScreenOnToAuthPreferenceController;
+ private RestrictedSwitchPreference mRequireScreenOnToAuthPreference;
+
private FingerprintManager mFingerprintManager;
private FingerprintUpdater mFingerprintUpdater;
private List mSensorProperties;
@@ -214,6 +243,7 @@ public class FingerprintSettings extends SubSettings {
}
private void updateDialog() {
+ setRequireScreenOnToAuthVisibility();
RenameDialog renameDialog = (RenameDialog) getFragmentManager().
findFragmentByTag(RenameDialog.class.getName());
if (renameDialog != null) {
@@ -448,13 +478,36 @@ public class FingerprintSettings extends SubSettings {
if (root != null) {
root.removeAll();
}
- addPreferencesFromResource(R.xml.security_settings_fingerprint);
root = getPreferenceScreen();
addFingerprintItemPreferences(root);
+ addPreferencesFromResource(getPreferenceScreenResId());
+ mRequireScreenOnToAuthPreference = findPreference(KEY_REQUIRE_SCREEN_ON_TO_AUTH);
+ for (AbstractPreferenceController controller : mControllers) {
+ ((FingerprintSettingsPreferenceController) controller).setUserId(mUserId);
+ }
+ mRequireScreenOnToAuthPreference.setChecked(
+ mRequireScreenOnToAuthPreferenceController.isChecked());
+ mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener(
+ (preference, newValue) -> {
+ boolean isChecked = ((SwitchPreference) preference).isChecked();
+ mRequireScreenOnToAuthPreferenceController.setChecked(!isChecked);
+ return true;
+ });
setPreferenceScreen(root);
return root;
}
+ private void setRequireScreenOnToAuthVisibility() {
+ int fingerprintsEnrolled = mFingerprintManager.getEnrolledFingerprints(mUserId).size();
+ final boolean removalInProgress = mRemovalSidecar.inProgress();
+ // Removing last remaining fingerprint
+ if (fingerprintsEnrolled == 0 && removalInProgress) {
+ mRequireScreenOnToAuthPreference.setVisible(false);
+ } else {
+ mRequireScreenOnToAuthPreference.setVisible(true);
+ }
+ }
+
private void addFingerprintItemPreferences(PreferenceGroup root) {
root.removeAll();
final List items = mFingerprintManager.getEnrolledFingerprints(mUserId);
@@ -477,6 +530,7 @@ public class FingerprintSettings extends SubSettings {
root.addPreference(pref);
pref.setOnPreferenceChangeListener(this);
}
+
Preference addPreference = new Preference(root.getContext());
addPreference.setKey(KEY_FINGERPRINT_ADD);
addPreference.setTitle(R.string.fingerprint_add_title);
@@ -568,6 +622,16 @@ public class FingerprintSettings extends SubSettings {
}
}
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.security_settings_fingerprint;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
@Override
public void onSaveInstanceState(final Bundle outState) {
outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
@@ -662,6 +726,27 @@ public class FingerprintSettings extends SubSettings {
return R.string.help_url_fingerprint;
}
+ @Override
+ protected List createPreferenceControllers(Context context) {
+ if (!isFingerprintHardwareDetected(context)) {
+ return null;
+ }
+
+ mControllers = buildPreferenceControllers(context);
+ return mControllers;
+ }
+
+ private List buildPreferenceControllers(Context context) {
+ final List controllers = new ArrayList<>();
+ mRequireScreenOnToAuthPreferenceController =
+ new FingerprintSettingsRequireScreenOnToAuthPreferenceController(
+ context,
+ KEY_REQUIRE_SCREEN_ON_TO_AUTH
+ );
+ controllers.add(mRequireScreenOnToAuthPreferenceController);
+ return controllers;
+ }
+
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsPreferenceController.java
new file mode 100644
index 00000000000..2ca5da8928e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsPreferenceController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.fingerprint;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+
+import com.android.settings.core.TogglePreferenceController;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.settingslib.RestrictedLockUtilsInternal;
+
+/**
+ * Abstract base class for all fingerprint settings toggles.
+ */
+public abstract class FingerprintSettingsPreferenceController extends TogglePreferenceController {
+
+ private int mUserId;
+
+ public FingerprintSettingsPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ public void setUserId(int userId) {
+ mUserId = userId;
+ }
+
+ protected int getUserId() {
+ return mUserId;
+ }
+
+ protected EnforcedAdmin getRestrictingAdmin() {
+ return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
+ mContext, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId);
+ }
+
+ @Override
+ public final boolean isSliceable() {
+ return false;
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ // not needed since it's not sliceable
+ return NO_RES;
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java
new file mode 100644
index 00000000000..5b183c1433f
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 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.fingerprint;
+
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.Utils;
+
+/**
+ * Preference controller that controls whether a SFPS device is required to be interactive for
+ * fingerprint authentication to unlock the device.
+ */
+public class FingerprintSettingsRequireScreenOnToAuthPreferenceController
+ extends FingerprintSettingsPreferenceController {
+ private static final String TAG =
+ "FingerprintSettingsRequireScreenOnToAuthPreferenceController";
+
+ @VisibleForTesting
+ protected FingerprintManager mFingerprintManager;
+
+ public FingerprintSettingsRequireScreenOnToAuthPreferenceController(
+ Context context, String prefKey) {
+ super(context, prefKey);
+ mFingerprintManager = Utils.getFingerprintManagerOrNull(context);
+ }
+
+ @Override
+ public boolean isChecked() {
+ if (!FingerprintSettings.isFingerprintHardwareDetected(mContext)) {
+ return false;
+ } else if (getRestrictingAdmin() != null) {
+ return false;
+ }
+ int defaultValue = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
+
+ return Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ defaultValue,
+ getUserHandle()) != 0;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ isChecked ? 1 : 0,
+ getUserHandle());
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ if (!FingerprintSettings.isFingerprintHardwareDetected(mContext)) {
+ preference.setEnabled(false);
+ } else if (!mFingerprintManager.hasEnrolledTemplates(getUserId())) {
+ preference.setEnabled(false);
+ } else {
+ preference.setEnabled(true);
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (mFingerprintManager != null
+ && mFingerprintManager.isHardwareDetected()
+ && mFingerprintManager.isPowerbuttonFps()) {
+ return mFingerprintManager.hasEnrolledTemplates(getUserId())
+ ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
+ } else {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ }
+
+ private int getUserHandle() {
+ return UserHandle.of(getUserId()).getIdentifier();
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
index a62bbe10e0c..8542fcd2461 100644
--- a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
@@ -24,7 +24,6 @@ import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.os.PowerManager;
import android.os.UserManager;
import android.util.Log;
@@ -126,15 +125,8 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver {
// Create an intent triggered by clicking on the
// "Clear All Notifications" button
- String bluetoothName;
- try {
- bluetoothName = Utils.findBluetoothPackageName(context);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- return;
- }
Intent deleteIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
- deleteIntent.setPackage(bluetoothName);
+ deleteIntent.setPackage("com.android.bluetooth");
deleteIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
deleteIntent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
BluetoothDevice.CONNECTION_ACCESS_NO);
diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java
old mode 100644
new mode 100755
index 24fe4e151ff..ca8f9d39a8c
--- a/src/com/android/settings/bluetooth/Utils.java
+++ b/src/com/android/settings/bluetooth/Utils.java
@@ -16,18 +16,11 @@
package com.android.settings.bluetooth;
-import static android.os.Process.BLUETOOTH_UID;
-
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
@@ -196,48 +189,4 @@ public final class Utils {
}
return false;
}
-
- /**
- * Returns the Bluetooth Package name
- */
- public static String findBluetoothPackageName(Context context)
- throws NameNotFoundException {
- // this activity will always be in the package where the rest of Bluetooth lives
- String sentinelActivity = "com.android.bluetooth.opp.BluetoothOppLauncherActivity";
- PackageManager packageManager = context.createContextAsUser(UserHandle.SYSTEM, 0)
- .getPackageManager();
- String[] allPackages = packageManager.getPackagesForUid(BLUETOOTH_UID);
- String matchedPackage = null;
- for (String candidatePackage : allPackages) {
- PackageInfo packageInfo;
- try {
- packageInfo =
- packageManager.getPackageInfo(
- candidatePackage,
- PackageManager.GET_ACTIVITIES
- | PackageManager.MATCH_ANY_USER
- | PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS);
- } catch (NameNotFoundException e) {
- // rethrow
- throw e;
- }
- if (packageInfo.activities == null) {
- continue;
- }
- for (ActivityInfo activity : packageInfo.activities) {
- if (sentinelActivity.equals(activity.name)) {
- if (matchedPackage == null) {
- matchedPackage = candidatePackage;
- } else {
- throw new NameNotFoundException("multiple main bluetooth packages found");
- }
- }
- }
- }
- if (matchedPackage != null) {
- return matchedPackage;
- }
- throw new NameNotFoundException("Could not find main bluetooth package");
- }
}
diff --git a/src/com/android/settings/development/BluetoothMaxConnectedAudioDevicesPreferenceController.java b/src/com/android/settings/development/BluetoothMaxConnectedAudioDevicesPreferenceController.java
index f1677f29509..bd8169a80fe 100644
--- a/src/com/android/settings/development/BluetoothMaxConnectedAudioDevicesPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothMaxConnectedAudioDevicesPreferenceController.java
@@ -16,8 +16,9 @@
package com.android.settings.development;
-import android.bluetooth.BluetoothManager;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.os.SystemProperties;
import androidx.annotation.VisibleForTesting;
@@ -41,15 +42,18 @@ public class BluetoothMaxConnectedAudioDevicesPreferenceController extends
private int mDefaultMaxConnectedAudioDevices = 0;
- private final BluetoothManager mBluetoothManager;
-
public BluetoothMaxConnectedAudioDevicesPreferenceController(Context context) {
super(context);
- mBluetoothManager = context.getSystemService(BluetoothManager.class);
-
- mDefaultMaxConnectedAudioDevices =
- mBluetoothManager.getAdapter().getMaxConnectedAudioDevices();
+ try {
+ Resources res = context.getPackageManager().getResourcesForApplication(
+ "com.android.bluetooth");
+ mDefaultMaxConnectedAudioDevices = res.getInteger(res.getIdentifier(
+ "config_bluetooth_max_connected_audio_devices",
+ "integer", "com.android.bluetooth"));
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
}
@Override
diff --git a/src/com/android/settings/notification/NotificationVolumePreferenceController.java b/src/com/android/settings/notification/NotificationVolumePreferenceController.java
index 322bb6c229b..112debca647 100644
--- a/src/com/android/settings/notification/NotificationVolumePreferenceController.java
+++ b/src/com/android/settings/notification/NotificationVolumePreferenceController.java
@@ -16,6 +16,7 @@
package com.android.settings.notification;
+import android.app.ActivityThread;
import android.app.INotificationManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -29,26 +30,32 @@ import android.os.Looper;
import android.os.Message;
import android.os.ServiceManager;
import android.os.Vibrator;
+import android.provider.DeviceConfig;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.Log;
import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.PreferenceScreen;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.Objects;
+import java.util.Set;
/**
- * Update notification volume icon in Settings in response to user adjusting volume
+ * Update notification volume icon in Settings in response to user adjusting volume.
*/
public class NotificationVolumePreferenceController extends VolumeSeekBarPreferenceController {
private static final String TAG = "NotificationVolumePreferenceController";
private static final String KEY_NOTIFICATION_VOLUME = "notification_volume";
+ private static final boolean CONFIG_DEFAULT_VAL = false;
+ private boolean mSeparateNotification;
private Vibrator mVibrator;
private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
@@ -56,39 +63,74 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere
private final RingReceiver mReceiver = new RingReceiver();
private final H mHandler = new H();
private INotificationManager mNoMan;
-
-
private int mMuteIcon;
private final int mNormalIconId = R.drawable.ic_notifications;
private final int mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
private final int mSilentIconId = R.drawable.ic_notifications_off_24dp;
- private final boolean mRingNotificationAliased;
-
-
public NotificationVolumePreferenceController(Context context) {
this(context, KEY_NOTIFICATION_VOLUME);
}
public NotificationVolumePreferenceController(Context context, String key) {
super(context, key);
+
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
if (mVibrator != null && !mVibrator.hasVibrator()) {
mVibrator = null;
}
- mRingNotificationAliased = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_alias_ring_notif_stream_types);
updateRingerMode();
}
+ /**
+ * Allow for notification slider to be enabled in the scenario where the config switches on
+ * while settings page is already on the screen by always configuring the preference, even if it
+ * is currently inactive.
+ */
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ if (mPreference == null) {
+ setupVolPreference(screen);
+ }
+ mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+ if (mPreference != null) {
+ mPreference.setVisible(getAvailabilityStatus() == AVAILABLE);
+ }
+ updateEffectsSuppressor();
+ updatePreferenceIconAndSliderState();
+ }
+
+ /**
+ * Only display the notification slider when the corresponding device config flag is set
+ */
+ private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set changeSet = properties.getKeyset();
+
+ if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+ boolean newVal = properties.getBoolean(
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+ if (newVal != mSeparateNotification) {
+ mSeparateNotification = newVal;
+ // manually hiding the preference because being unavailable does not do the job
+ if (mPreference != null) {
+ mPreference.setVisible(getAvailabilityStatus() == AVAILABLE);
+ }
+ }
+ }
+ }
+
+
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@Override
public void onResume() {
super.onResume();
mReceiver.register(true);
- updateEffectsSuppressor();
- updatePreferenceIconAndSliderState();
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ ActivityThread.currentApplication().getMainExecutor(),
+ this::onDeviceConfigChange);
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
@@ -96,16 +138,17 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere
public void onPause() {
super.onPause();
mReceiver.register(false);
+ DeviceConfig.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
}
@Override
public int getAvailabilityStatus() {
+ boolean separateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
- // Show separate notification slider if ring/notification are not aliased by AudioManager --
- // if they are, notification volume is controlled by RingVolumePreferenceController.
return mContext.getResources().getBoolean(R.bool.config_show_notification_volume)
- && (!mRingNotificationAliased || !Utils.isVoiceCapable(mContext))
&& !mHelper.isSingleVolume()
+ && (separateNotification || !Utils.isVoiceCapable(mContext))
? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
diff --git a/src/com/android/settings/notification/RingVolumePreferenceController.java b/src/com/android/settings/notification/RingVolumePreferenceController.java
index a78689f5a0a..7fdb1e16141 100644
--- a/src/com/android/settings/notification/RingVolumePreferenceController.java
+++ b/src/com/android/settings/notification/RingVolumePreferenceController.java
@@ -16,6 +16,7 @@
package com.android.settings.notification;
+import android.app.ActivityThread;
import android.app.INotificationManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -29,6 +30,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.ServiceManager;
import android.os.Vibrator;
+import android.provider.DeviceConfig;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.Log;
@@ -36,11 +38,13 @@ import android.util.Log;
import androidx.lifecycle.OnLifecycleEvent;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.Objects;
+import java.util.Set;
/**
* This slider can represent both ring and notification, if the corresponding streams are aliased,
@@ -59,24 +63,21 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
private int mMuteIcon;
- /*
- * Whether ring and notification streams are aliased together by AudioManager.
- * If they are, we'll present one volume control for both.
- * If not, we'll present separate volume controls.
- */
- private final boolean mRingAliasNotif;
-
- private final int mNormalIconId;
+ private int mNormalIconId;
@VisibleForTesting
- final int mVibrateIconId;
+ int mVibrateIconId;
@VisibleForTesting
- final int mSilentIconId;
+ int mSilentIconId;
@VisibleForTesting
- final int mTitleId;
+ int mTitleId;
+
+ private boolean mSeparateNotification;
private INotificationManager mNoMan;
+ private static final boolean CONFIG_DEFAULT_VAL = false;
+
public RingVolumePreferenceController(Context context) {
this(context, KEY_RING_VOLUME);
}
@@ -87,29 +88,56 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
if (mVibrator != null && !mVibrator.hasVibrator()) {
mVibrator = null;
}
-
- mRingAliasNotif = isRingAliasNotification();
- if (mRingAliasNotif) {
- mTitleId = R.string.ring_volume_option_title;
-
- mNormalIconId = R.drawable.ic_notifications;
- mSilentIconId = R.drawable.ic_notifications_off_24dp;
- } else {
- mTitleId = R.string.separate_ring_volume_option_title;
-
- mNormalIconId = R.drawable.ic_ring_volume;
- mSilentIconId = R.drawable.ic_ring_volume_off;
- }
- // todo: set a distinct vibrate icon for ring vs notification
- mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
-
+ mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+ loadPreferenceIconResources(mSeparateNotification);
updateRingerMode();
}
- @VisibleForTesting
- boolean isRingAliasNotification() {
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_alias_ring_notif_stream_types);
+ private void loadPreferenceIconResources(boolean separateNotification) {
+ if (separateNotification) {
+ mTitleId = R.string.separate_ring_volume_option_title;
+ mNormalIconId = R.drawable.ic_ring_volume;
+ mSilentIconId = R.drawable.ic_ring_volume_off;
+ } else {
+ mTitleId = R.string.ring_volume_option_title;
+ mNormalIconId = R.drawable.ic_notifications;
+ mSilentIconId = R.drawable.ic_notifications_off_24dp;
+ }
+ // todo: set a distinct vibrate icon for ring vs notification
+ mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
+ }
+
+ /**
+ * As the responsibility of this slider changes, so should its title & icon
+ */
+ public void onDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set changeSet = properties.getKeyset();
+ if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+ boolean valueUpdated = readSeparateNotificationVolumeConfig();
+ if (valueUpdated) {
+ updateEffectsSuppressor();
+ selectPreferenceIconState();
+ setPreferenceTitle();
+ }
+ }
+ }
+
+ /**
+ * side effect: updates the cached value of the config, and also the icon
+ * @return has the config changed?
+ */
+ private boolean readSeparateNotificationVolumeConfig() {
+ boolean newVal = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+
+ boolean valueUpdated = newVal != mSeparateNotification;
+ if (valueUpdated) {
+ mSeparateNotification = newVal;
+ loadPreferenceIconResources(newVal);
+ }
+
+ return valueUpdated;
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@@ -117,8 +145,11 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
public void onResume() {
super.onResume();
mReceiver.register(true);
+ readSeparateNotificationVolumeConfig();
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ ActivityThread.currentApplication().getMainExecutor(), this::onDeviceConfigChange);
updateEffectsSuppressor();
- updatePreferenceIcon();
+ selectPreferenceIconState();
setPreferenceTitle();
}
@@ -127,6 +158,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
public void onPause() {
super.onPause();
mReceiver.register(false);
+ DeviceConfig.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
}
@Override
@@ -170,7 +202,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
final int ringerMode = mHelper.getRingerModeInternal();
if (mRingerMode == ringerMode) return;
mRingerMode = ringerMode;
- updatePreferenceIcon();
+ selectPreferenceIconState();
}
private void updateEffectsSuppressor() {
@@ -190,7 +222,8 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
return;
}
- if (hintsMatch(hints, mRingAliasNotif)) {
+ if (hintsMatch(hints, DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false))) {
mSuppressor = suppressor;
if (mPreference != null) {
final String text = SuppressorHelper.getSuppressionText(mContext, suppressor);
@@ -200,11 +233,11 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
}
@VisibleForTesting
- boolean hintsMatch(int hints, boolean ringNotificationAliased) {
+ boolean hintsMatch(int hints, boolean notificationSeparated) {
return (hints & NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS) != 0
|| (hints & NotificationListenerService.HINT_HOST_DISABLE_EFFECTS) != 0
|| ((hints & NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS)
- != 0 && ringNotificationAliased);
+ != 0 && !notificationSeparated);
}
@VisibleForTesting
@@ -217,7 +250,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
mVibrator = vibrator;
}
- private void updatePreferenceIcon() {
+ private void selectPreferenceIconState() {
if (mPreference != null) {
if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
mPreference.showIcon(mNormalIconId);
diff --git a/src/com/android/settings/notification/VolumeSeekBarPreferenceController.java b/src/com/android/settings/notification/VolumeSeekBarPreferenceController.java
index d1701599c34..0414565721e 100644
--- a/src/com/android/settings/notification/VolumeSeekBarPreferenceController.java
+++ b/src/com/android/settings/notification/VolumeSeekBarPreferenceController.java
@@ -55,13 +55,17 @@ public abstract class VolumeSeekBarPreferenceController extends
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
if (isAvailable()) {
- mPreference = screen.findPreference(getPreferenceKey());
- mPreference.setCallback(mVolumePreferenceCallback);
- mPreference.setStream(getAudioStream());
- mPreference.setMuteIcon(getMuteIcon());
+ setupVolPreference(screen);
}
}
+ protected void setupVolPreference(PreferenceScreen screen) {
+ mPreference = screen.findPreference(getPreferenceKey());
+ mPreference.setCallback(mVolumePreferenceCallback);
+ mPreference.setStream(getAudioStream());
+ mPreference.setMuteIcon(getMuteIcon());
+ }
+
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
if (mPreference != null) {
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java
new file mode 100644
index 00000000000..ff74d59782a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 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.fingerprint;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.provider.Settings;
+
+import com.android.settings.testutils.shadow.ShadowUtils;
+import com.android.settingslib.RestrictedSwitchPreference;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowUtils.class})
+public class FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest {
+
+ @Mock
+ private FingerprintManager mFingerprintManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private RestrictedSwitchPreference mPreference;
+
+ private Context mContext;
+ private FingerprintSettingsRequireScreenOnToAuthPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ when(mContext.getSystemService(eq(Context.FINGERPRINT_SERVICE))).thenReturn(
+ mFingerprintManager);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+
+ mController = spy(new FingerprintSettingsRequireScreenOnToAuthPreferenceController(mContext,
+ "test_key"));
+ ReflectionHelpers.setField(mController, "mFingerprintManager", mFingerprintManager);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowUtils.reset();
+ }
+
+ @Test
+ public void onPreferenceChange_settingIsUpdated() {
+ boolean state = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, 1) != 0;
+
+ assertThat(mController.isChecked()).isFalse();
+ assertThat(mController.onPreferenceChange(mPreference, !state)).isTrue();
+ boolean newState = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, 1) != 0;
+ assertThat(newState).isEqualTo(!state);
+ }
+
+ @Test
+ public void isAvailable_isEnabled_whenSfpsHardwareDetected_AndHasEnrolledFingerprints() {
+ assertThat(mController.isAvailable()).isEqualTo(false);
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ configure_hardwareDetected_isSfps_hasEnrolledTemplates(
+ true /* isHardwareDetected */,
+ true /* isPowerbuttonFps */,
+ true /* hasEnrolledTemplates */);
+ assertThat(mController.isAvailable()).isEqualTo(true);
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void isAvailable_isDisabled_whenSfpsHardwareDetected_AndNoEnrolledFingerprints() {
+ assertThat(mController.isAvailable()).isEqualTo(false);
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ configure_hardwareDetected_isSfps_hasEnrolledTemplates(
+ true /* isHardwareDetected */,
+ true /* isPowerbuttonFps */,
+ false /* hasEnrolledTemplates */);
+ assertThat(mController.isAvailable()).isEqualTo(true);
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING);
+ }
+
+ @Test
+ public void isUnavailable_whenHardwareNotDetected() {
+ assertThat(mController.isAvailable()).isFalse();
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ configure_hardwareDetected_isSfps_hasEnrolledTemplates(
+ false /* isHardwareDetected */,
+ true /* isPowerbuttonFps */,
+ true /* hasEnrolledTemplates */);
+ assertThat(mController.isAvailable()).isFalse();
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ }
+
+ @Test
+ public void isUnavailable_onNonSfpsDevice() {
+ assertThat(mController.isAvailable()).isFalse();
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ configure_hardwareDetected_isSfps_hasEnrolledTemplates(
+ true /* isHardwareDetected */,
+ false /* isPowerbuttonFps */,
+ true /* hasEnrolledTemplates */);
+ assertThat(mController.isAvailable()).isFalse();
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ }
+
+ private void configure_hardwareDetected_isSfps_hasEnrolledTemplates(
+ boolean isHardwareDetected, boolean isPowerbuttonFps, boolean hasEnrolledTemplates) {
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(isHardwareDetected);
+ when(mFingerprintManager.isPowerbuttonFps()).thenReturn(isPowerbuttonFps);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(hasEnrolledTemplates);
+ }
+
+
+}
diff --git a/tests/robotests/src/com/android/settings/development/BluetoothMaxConnectedAudioDevicesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothMaxConnectedAudioDevicesPreferenceControllerTest.java
index 7ab311fe2ab..72477b94585 100644
--- a/tests/robotests/src/com/android/settings/development/BluetoothMaxConnectedAudioDevicesPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/BluetoothMaxConnectedAudioDevicesPreferenceControllerTest.java
@@ -24,9 +24,9 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothManager;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.os.SystemProperties;
@@ -54,11 +54,6 @@ public class BluetoothMaxConnectedAudioDevicesPreferenceControllerTest {
@Spy
private Context mSpyContext = RuntimeEnvironment.application;
- @Mock
- private BluetoothManager mBluetoothManager;
- @Mock
- private BluetoothAdapter mBluetoothAdapter;
-
private ListPreference mPreference;
private BluetoothMaxConnectedAudioDevicesPreferenceController mController;
@@ -68,15 +63,19 @@ public class BluetoothMaxConnectedAudioDevicesPreferenceControllerTest {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- doReturn(mBluetoothManager).when(mSpyContext).getSystemService(BluetoothManager.class);
- doReturn(mBluetoothAdapter).when(mBluetoothManager).getAdapter();
// Get XML values without mock
// Setup test list preference using XML values
mPreference = new ListPreference(mSpyContext);
mPreference.setEntries(R.array.bluetooth_max_connected_audio_devices);
mPreference.setEntryValues(R.array.bluetooth_max_connected_audio_devices_values);
- doReturn(TEST_MAX_CONNECTED_AUDIO_DEVICES).when(mBluetoothAdapter)
- .getMaxConnectedAudioDevices();
+ // Retrieve default max connected audio devices to a test controlled value
+ try {
+ Resources res = mSpyContext.getPackageManager().getResourcesForApplication("com.android.bluetooth");
+ TEST_MAX_CONNECTED_AUDIO_DEVICES = res.getInteger(res.getIdentifier("config_bluetooth_max_connected_audio_devices", "integer", "com.android.bluetooth"));
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+
// Init the actual controller
mController = new BluetoothMaxConnectedAudioDevicesPreferenceController(mSpyContext);
// Construct preference in the controller via a mocked preference screen object
diff --git a/tests/robotests/src/com/android/settings/notification/NotificationVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/NotificationVolumePreferenceControllerTest.java
index 96b9e6219c8..7e7ad10d8c1 100644
--- a/tests/robotests/src/com/android/settings/notification/NotificationVolumePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/NotificationVolumePreferenceControllerTest.java
@@ -18,6 +18,7 @@ package com.android.settings.notification;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -25,10 +26,17 @@ import android.content.Context;
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.Vibrator;
+import android.provider.DeviceConfig;
import android.service.notification.NotificationListenerService;
import android.telephony.TelephonyManager;
-import com.android.internal.R;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import org.junit.Before;
import org.junit.Test;
@@ -37,11 +45,12 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowDeviceConfig.class})
public class NotificationVolumePreferenceControllerTest {
-
@Mock
private AudioHelper mHelper;
@Mock
@@ -52,6 +61,11 @@ public class NotificationVolumePreferenceControllerTest {
private Vibrator mVibrator;
@Mock
private Resources mResources;
+ @Mock
+ private PreferenceManager mPreferenceManager;
+
+ private static final String READ_DEVICE_CONFIG_PERMISSION =
+ "android.permission.READ_DEVICE_CONFIG";
private Context mContext;
private NotificationVolumePreferenceController mController;
@@ -87,7 +101,9 @@ public class NotificationVolumePreferenceControllerTest {
public void isAvailable_voiceCapable_aliasedWithRing_shouldReturnFalse() {
when(mResources.getBoolean(
com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true);
- when(mResources.getBoolean(R.bool.config_alias_ring_notif_stream_types)).thenReturn(true);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
NotificationVolumePreferenceController controller =
new NotificationVolumePreferenceController(mContext);
@@ -105,7 +121,9 @@ public class NotificationVolumePreferenceControllerTest {
public void isAvailable_voiceCapable_separatedFromRing_shouldReturnTrue() {
when(mResources.getBoolean(
com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true);
- when(mResources.getBoolean(R.bool.config_alias_ring_notif_stream_types)).thenReturn(false);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
NotificationVolumePreferenceController controller =
new NotificationVolumePreferenceController(mContext);
@@ -170,4 +188,70 @@ public class NotificationVolumePreferenceControllerTest {
.isTrue();
}
+ @Test
+ public void enableSeparateNotificationConfig_controllerBecomesAvailable() {
+ PreferenceScreen screen = spy(new PreferenceScreen(mContext, null));
+ VolumeSeekBarPreference volumeSeekBarPreference = mock(VolumeSeekBarPreference.class);
+ when(screen.getPreferenceManager()).thenReturn(mPreferenceManager);
+ when(screen.getContext()).thenReturn(mContext);
+ when(mResources.getBoolean(
+ com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true);
+ // block the alternative condition to enable controller
+ when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+ NotificationVolumePreferenceController controller =
+ new NotificationVolumePreferenceController(mContext);
+ when(screen.findPreference(controller.getPreferenceKey()))
+ .thenReturn(volumeSeekBarPreference);
+
+ // allow the controller to subscribe
+ Shadows.shadowOf((android.app.Application) ApplicationProvider.getApplicationContext())
+ .grantPermissions(READ_DEVICE_CONFIG_PERMISSION);
+ controller.onResume();
+ controller.displayPreference(screen);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, Boolean.toString(true),
+ false);
+
+ assertThat(controller.getAvailabilityStatus()
+ == BasePreferenceController.AVAILABLE).isTrue();
+ }
+
+ @Test
+ public void disableSeparateNotificationConfig_controllerBecomesUnavailable() {
+ PreferenceScreen screen = spy(new PreferenceScreen(mContext, null));
+ VolumeSeekBarPreference volumeSeekBarPreference = mock(VolumeSeekBarPreference.class);
+ when(screen.getPreferenceManager()).thenReturn(mPreferenceManager);
+ when(screen.getContext()).thenReturn(mContext);
+ when(mResources.getBoolean(
+ com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true);
+
+ // block the alternative condition to enable controller
+ when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
+ NotificationVolumePreferenceController controller =
+ new NotificationVolumePreferenceController(mContext);
+
+ when(screen.findPreference(controller.getPreferenceKey()))
+ .thenReturn(volumeSeekBarPreference);
+
+ Shadows.shadowOf((android.app.Application) ApplicationProvider.getApplicationContext())
+ .grantPermissions(READ_DEVICE_CONFIG_PERMISSION);
+ controller.onResume();
+ controller.displayPreference(screen);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+ assertThat(controller.getAvailabilityStatus()
+ == BasePreferenceController.UNSUPPORTED_ON_DEVICE).isTrue();
+ }
+
}
diff --git a/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java
index 02757d52874..1ad26c71546 100644
--- a/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java
@@ -27,10 +27,13 @@ import android.content.Context;
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.Vibrator;
+import android.provider.DeviceConfig;
import android.service.notification.NotificationListenerService;
import android.telephony.TelephonyManager;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.settings.R;
+import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import org.junit.Before;
import org.junit.Test;
@@ -39,9 +42,11 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowDeviceConfig.class})
public class RingVolumePreferenceControllerTest {
@Mock
@@ -124,9 +129,10 @@ public class RingVolumePreferenceControllerTest {
// todo: verify that the title change is displayed, by examining the underlying preference
@Test
public void ringNotificationStreamsNotAliased_sliderTitleSetToRingOnly() {
- when(mResources.getBoolean(
- com.android.internal.R.bool.config_alias_ring_notif_stream_types))
- .thenReturn(false);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
final RingVolumePreferenceController controller =
new RingVolumePreferenceController(mContext);
@@ -138,8 +144,9 @@ public class RingVolumePreferenceControllerTest {
@Test
public void ringNotificationStreamsAliased_sliderTitleIncludesBothRingNotification() {
- when(mResources.getBoolean(
- com.android.internal.R.bool.config_alias_ring_notif_stream_types)).thenReturn(true);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
final RingVolumePreferenceController control = new RingVolumePreferenceController(mContext);
int expectedTitleId = R.string.ring_volume_option_title;
@@ -150,39 +157,39 @@ public class RingVolumePreferenceControllerTest {
@Test
public void setHintsRing_aliased_Matches() {
assertThat(mController.hintsMatch(
- NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, true)).isTrue();
+ NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, false)).isTrue();
}
@Test
public void setHintsRingNotification_aliased_Matches() {
assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS,
- true)).isTrue();
+ false)).isTrue();
}
@Test
public void setHintNotification_aliased_Matches() {
assertThat(mController
.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS,
- true)).isTrue();
+ false)).isTrue();
}
@Test
public void setHintsRing_unaliased_Matches() {
assertThat(mController.hintsMatch(
- NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, false)).isTrue();
+ NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, true)).isTrue();
}
@Test
public void setHintsRingNotification_unaliased_Matches() {
assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS,
- false)).isTrue();
+ true)).isTrue();
}
@Test
public void setHintNotification_unaliased_doesNotMatch() {
assertThat(mController
.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS,
- false)).isFalse();
+ true)).isFalse();
}
@Test