mShortcutTargets = Collections.emptySet();
+ private boolean mIsInSetupWizard;
+
+ public ShortcutOptionPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ if (getPreferenceKey().equals(preference.getKey())
+ && preference instanceof ShortcutOptionPreference) {
+ ((ShortcutOptionPreference) preference).setChecked(isChecked());
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (isShortcutAvailable()) {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ /**
+ * Set the targets (i.e. a11y features) to be configured with the a11y shortcut option.
+ *
+ * Note: the shortcutTargets cannot be empty, since the edit a11y shortcut option
+ * is meant to configure the shortcut options for an a11y feature.
+ * >
+ *
+ * @param shortcutTargets the a11y features, like color correction, Talkback, etc.
+ * @throws NullPointerException if the {@code shortcutTargets} was {@code null}
+ * @throws IllegalArgumentException if the {@code shortcutTargets} was empty
+ */
+ public void setShortcutTargets(Set shortcutTargets) {
+ Preconditions.checkCollectionNotEmpty(shortcutTargets, /* valueName= */ "a11y targets");
+
+ this.mShortcutTargets = shortcutTargets;
+ }
+
+ public void setInSetupWizard(boolean isInSetupWizard) {
+ this.mIsInSetupWizard = isInSetupWizard;
+ }
+
+ protected Set getShortcutTargets() {
+ return mShortcutTargets;
+ }
+
+ protected boolean isInSetupWizard() {
+ return mIsInSetupWizard;
+ }
+
+ @Override
+ public final boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
+ enableShortcutForTargets((Boolean) newValue);
+ return false;
+ }
+
+ @ShortcutConstants.UserShortcutType
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.DEFAULT;
+ }
+
+ /**
+ * Returns true if the shortcut is associated to the targets
+ */
+ protected boolean isChecked() {
+ Set targets = ShortcutUtils.getShortcutTargetsFromSettings(
+ mContext, getShortcutType(), UserHandle.myUserId());
+
+ return !targets.isEmpty() && targets.containsAll(getShortcutTargets());
+ }
+
+ /**
+ * Enable or disable the shortcut for the given accessibility features.
+ */
+ protected void enableShortcutForTargets(boolean enable) {
+ Set shortcutTargets = getShortcutTargets();
+ @ShortcutConstants.UserShortcutType int shortcutType = getShortcutType();
+
+ if (enable) {
+ for (String target : shortcutTargets) {
+ ShortcutUtils.optInValueToSettings(mContext, shortcutType, target);
+ }
+ } else {
+ for (String target : shortcutTargets) {
+ ShortcutUtils.optOutValueFromSettings(mContext, shortcutType, target);
+ }
+ }
+ ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+ mContext, shortcutTargets, UserHandle.myUserId());
+ }
+
+ /**
+ * Returns true when the user can associate a shortcut to the targets
+ */
+ protected abstract boolean isShortcutAvailable();
+}
diff --git a/src/com/android/settings/accessibility/shortcuts/SoftwareShortcutOptionPreferenceController.java b/src/com/android/settings/accessibility/shortcuts/SoftwareShortcutOptionPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..24098c81f3866fbe437fb308acd6b9e70d7c062b
--- /dev/null
+++ b/src/com/android/settings/accessibility/shortcuts/SoftwareShortcutOptionPreferenceController.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 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.accessibility.shortcuts;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.view.View;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityButtonFragment;
+import com.android.settings.accessibility.FloatingMenuSizePreferenceController;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.utils.AnnotationSpan;
+
+/**
+ * A base controller for the preference controller of software shortcuts.
+ */
+public abstract class SoftwareShortcutOptionPreferenceController
+ extends ShortcutOptionPreferenceController {
+
+ public SoftwareShortcutOptionPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @ShortcutConstants.UserShortcutType
+ @Override
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.SOFTWARE;
+ }
+
+ private boolean isMagnificationInTargets() {
+ return getShortcutTargets().contains(MAGNIFICATION_CONTROLLER_NAME);
+ }
+
+ protected CharSequence getCustomizeAccessibilityButtonLink() {
+ final View.OnClickListener linkListener = v -> new SubSettingLauncher(mContext)
+ .setDestination(AccessibilityButtonFragment.class.getName())
+ .setSourceMetricsCategory(getMetricsCategory())
+ .launch();
+ final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(
+ AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, linkListener);
+ return AnnotationSpan.linkify(
+ mContext.getText(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating),
+ linkInfo);
+ }
+
+ @Override
+ protected void enableShortcutForTargets(boolean enable) {
+ super.enableShortcutForTargets(enable);
+
+ if (enable) {
+ // Update the A11y FAB size to large when the Magnification shortcut is enabled
+ // and the user hasn't changed the floating button size
+ if (isMagnificationInTargets()
+ && Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSizePreferenceController.Size.UNKNOWN)
+ == FloatingMenuSizePreferenceController.Size.UNKNOWN) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSizePreferenceController.Size.LARGE);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/accessibility/shortcuts/TripleTapShortcutOptionController.java b/src/com/android/settings/accessibility/shortcuts/TripleTapShortcutOptionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..0eb1ee5acd7a06c834aa9a5f05060b1712f8a139
--- /dev/null
+++ b/src/com/android/settings/accessibility/shortcuts/TripleTapShortcutOptionController.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 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.accessibility.shortcuts;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
+import android.content.Context;
+import android.icu.text.MessageFormat;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityUtil;
+import com.android.settings.accessibility.ExpandablePreference;
+
+import java.util.Set;
+
+/**
+ * A controller handles displaying the triple tap shortcut option preference and
+ * configuring the shortcut.
+ */
+public class TripleTapShortcutOptionController extends ShortcutOptionPreferenceController
+ implements ExpandablePreference {
+
+ private boolean mIsExpanded = false;
+
+ public TripleTapShortcutOptionController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final Preference preference = screen.findPreference(getPreferenceKey());
+ if (preference instanceof ShortcutOptionPreference shortcutOptionPreference) {
+ shortcutOptionPreference.setTitle(
+ R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
+ String summary = mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
+ // Format the number '3' in the summary.
+ final Object[] arguments = {3};
+ summary = MessageFormat.format(summary, arguments);
+
+ shortcutOptionPreference.setSummary(summary);
+ shortcutOptionPreference.setIntroImageRawResId(
+ R.raw.a11y_shortcut_type_triple_tap);
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (isExpanded() && isShortcutAvailable()) {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @ShortcutConstants.UserShortcutType
+ @Override
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.TRIPLETAP;
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ mIsExpanded = expanded;
+ }
+
+ @Override
+ public boolean isExpanded() {
+ return mIsExpanded;
+ }
+
+ @Override
+ protected boolean isShortcutAvailable() {
+ Set shortcutTargets = getShortcutTargets();
+ return shortcutTargets.size() == 1
+ && shortcutTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+ }
+
+ @Override
+ protected boolean isChecked() {
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ AccessibilityUtil.State.OFF) == AccessibilityUtil.State.ON;
+ }
+
+ @Override
+ protected void enableShortcutForTargets(boolean enable) {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ enable ? AccessibilityUtil.State.ON : AccessibilityUtil.State.OFF);
+ }
+}
diff --git a/src/com/android/settings/accessibility/shortcuts/TwoFingersDoubleTapShortcutOptionController.java b/src/com/android/settings/accessibility/shortcuts/TwoFingersDoubleTapShortcutOptionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..64ed7bdbd195de37f6af8990bbc6a927a00ba434
--- /dev/null
+++ b/src/com/android/settings/accessibility/shortcuts/TwoFingersDoubleTapShortcutOptionController.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 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.accessibility.shortcuts;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
+import android.content.Context;
+import android.icu.text.MessageFormat;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.server.accessibility.Flags;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityUtil;
+
+import java.util.Set;
+
+/**
+ * A controller handles displaying the two fingers double tap shortcut option preference and
+ * configuring the shortcut.
+ */
+public class TwoFingersDoubleTapShortcutOptionController
+ extends ShortcutOptionPreferenceController {
+
+ public TwoFingersDoubleTapShortcutOptionController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @ShortcutConstants.UserShortcutType
+ @Override
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.TRIPLETAP;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final Preference preference = screen.findPreference(getPreferenceKey());
+ if (preference instanceof ShortcutOptionPreference shortcutOptionPreference) {
+ // TODO (b/306153204): Update shortcut string and image when UX provides them
+ shortcutOptionPreference.setTitle(
+ R.string.accessibility_shortcut_edit_dialog_title_two_finger_double_tap);
+ String summary = mContext.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_two_finger_double_tap);
+ // Format the number '2' in the summary.
+ final Object[] arguments = {2};
+ summary = MessageFormat.format(summary, arguments);
+
+ shortcutOptionPreference.setSummary(summary);
+ shortcutOptionPreference.setIntroImageRawResId(
+ R.raw.a11y_shortcut_type_triple_tap);
+ }
+ }
+
+ @Override
+ protected boolean isShortcutAvailable() {
+ if (!Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
+ return false;
+ }
+ // Only Magnification has two fingers triple tap shortcut option.
+ Set shortcutTargets = getShortcutTargets();
+ return shortcutTargets.size() == 1
+ && shortcutTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+ }
+
+ @Override
+ protected boolean isChecked() {
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ AccessibilityUtil.State.OFF) == AccessibilityUtil.State.ON;
+ }
+
+ @Override
+ protected void enableShortcutForTargets(boolean enable) {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ enable ? AccessibilityUtil.State.ON : AccessibilityUtil.State.OFF);
+ }
+}
diff --git a/src/com/android/settings/accessibility/shortcuts/VolumeKeysShortcutOptionController.java b/src/com/android/settings/accessibility/shortcuts/VolumeKeysShortcutOptionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9083e7ccc880665b5e21efe2fa26a691114d89d5
--- /dev/null
+++ b/src/com/android/settings/accessibility/shortcuts/VolumeKeysShortcutOptionController.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.accessibility.shortcuts;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityUtil;
+
+/**
+ * A controller handles displaying the volume keys shortcut option preference and
+ * configuring the shortcut.
+ */
+public class VolumeKeysShortcutOptionController extends ShortcutOptionPreferenceController {
+
+ public VolumeKeysShortcutOptionController(
+ Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @ShortcutConstants.UserShortcutType
+ @Override
+ protected int getShortcutType() {
+ return ShortcutConstants.UserShortcutType.HARDWARE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final Preference preference = screen.findPreference(getPreferenceKey());
+ if (preference instanceof ShortcutOptionPreference shortcutOptionPreference) {
+ shortcutOptionPreference.setTitle(
+ R.string.accessibility_shortcut_edit_dialog_title_hardware);
+ shortcutOptionPreference.setSummary(
+ R.string.accessibility_shortcut_edit_dialog_summary_hardware);
+ shortcutOptionPreference.setIntroImageResId(
+ R.drawable.a11y_shortcut_type_hardware);
+ }
+ }
+
+ @Override
+ protected boolean isShortcutAvailable() {
+ return true;
+ }
+
+ @Override
+ protected void enableShortcutForTargets(boolean enable) {
+ super.enableShortcutForTargets(enable);
+ if (enable) {
+ AccessibilityUtil.skipVolumeShortcutDialogTimeoutRestriction(mContext);
+ }
+ }
+}
diff --git a/src/com/android/settings/accounts/AccountPreferenceController.java b/src/com/android/settings/accounts/AccountPreferenceController.java
index 3e6feb74094eebebfd7e174f5315c230ea2550ac..2d862d6f0935a70c71a0c92634e3c68c3f3d2c0b 100644
--- a/src/com/android/settings/accounts/AccountPreferenceController.java
+++ b/src/com/android/settings/accounts/AccountPreferenceController.java
@@ -187,6 +187,11 @@ public class AccountPreferenceController extends AbstractPreferenceController
updateUi();
}
+ @Override
+ public void updateRawDataToIndex(List rawData) {
+ rawData.add(newAddAccountRawData());
+ }
+
@Override
public void updateDynamicRawDataToIndex(List rawData) {
if (!isAvailable()) {
@@ -348,6 +353,10 @@ public class AccountPreferenceController extends AbstractPreferenceController
}
return;
}
+ if (mUm.getUserProperties(userInfo.getUserHandle()).getShowInSettings()
+ == UserProperties.SHOW_IN_SETTINGS_NO) {
+ return;
+ }
final Context context = mContext;
final ProfileData profileData = new ProfileData();
profileData.userInfo = userInfo;
@@ -428,6 +437,14 @@ public class AccountPreferenceController extends AbstractPreferenceController
}));
}
+ private SearchIndexableRaw newAddAccountRawData() {
+ SearchIndexableRaw data = new SearchIndexableRaw(mContext);
+ data.key = PREF_KEY_ADD_ACCOUNT;
+ data.title = mContext.getString(R.string.add_account_label);
+ data.iconResId = R.drawable.ic_add_24dp;
+ return data;
+ }
+
private RestrictedPreference newAddAccountPreference() {
RestrictedPreference preference =
new RestrictedPreference(mFragment.getPreferenceManager().getContext());
diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
index 3b79b47d8db62455ce2061efa082c1e1788eba2d..4e39070febd16efc4604e291b42d4c36470199e5 100644
--- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
+++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
@@ -34,6 +34,7 @@ import androidx.window.embedding.SplitPairRule;
import androidx.window.embedding.SplitPlaceholderRule;
import androidx.window.embedding.SplitRule;
+import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.SettingsActivity;
import com.android.settings.SubSettings;
@@ -112,8 +113,9 @@ public class ActivityEmbeddingRulesController {
.setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
.setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
.setClearTop(clearTop)
- .setMinWidthDp(ActivityEmbeddingUtils.getMinCurrentScreenSplitWidthDp())
- .setMinSmallestWidthDp(ActivityEmbeddingUtils.getMinSmallestScreenSplitWidthDp())
+ .setMinWidthDp(ActivityEmbeddingUtils.getMinCurrentScreenSplitWidthDp(context))
+ .setMinSmallestWidthDp(
+ ActivityEmbeddingUtils.getMinSmallestScreenSplitWidthDp(context))
.setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ALWAYS_ALLOW)
.setDefaultSplitAttributes(attributes)
.build();
@@ -233,8 +235,9 @@ public class ActivityEmbeddingRulesController {
.build();
final SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder(
activityFilters, intent)
- .setMinWidthDp(ActivityEmbeddingUtils.getMinCurrentScreenSplitWidthDp())
- .setMinSmallestWidthDp(ActivityEmbeddingUtils.getMinSmallestScreenSplitWidthDp())
+ .setMinWidthDp(ActivityEmbeddingUtils.getMinCurrentScreenSplitWidthDp(mContext))
+ .setMinSmallestWidthDp(
+ ActivityEmbeddingUtils.getMinSmallestScreenSplitWidthDp(mContext))
.setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ALWAYS_ALLOW)
.setSticky(false)
.setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ADJACENT)
@@ -261,8 +264,13 @@ public class ActivityEmbeddingRulesController {
addActivityFilter(activityFilters, FaceEnrollIntroduction.class);
addActivityFilter(activityFilters, RemoteAuthActivity.class);
addActivityFilter(activityFilters, RemoteAuthActivityInternal.class);
- addActivityFilter(activityFilters, AvatarPickerActivity.class);
addActivityFilter(activityFilters, ChooseLockPattern.class);
+ if (android.multiuser.Flags.avatarSync()) {
+ String action = mContext.getString(R.string.config_avatar_picker_action);
+ addActivityFilter(activityFilters, new Intent(action));
+ } else {
+ addActivityFilter(activityFilters, AvatarPickerActivity.class);
+ }
ActivityRule activityRule = new ActivityRule.Builder(activityFilters).setAlwaysExpand(true)
.build();
mRuleController.addRule(activityRule);
diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java
index 74a967332da7557938d61a95ea39f3827b771a36..b91e0e5eacd9c540c9f153b8bf38fe2fe746457f 100644
--- a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java
+++ b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java
@@ -33,11 +33,6 @@ import com.google.android.setupcompat.util.WizardManagerHelper;
/** An util class collecting all common methods for the embedding activity features. */
public class ActivityEmbeddingUtils {
- // The smallest value of current width of the window when the split should be used.
- private static final int MIN_CURRENT_SCREEN_SPLIT_WIDTH_DP = 720;
- // The smallest value of the smallest-width (sw) of the window in any rotation when
- // the split should be used.
- private static final int MIN_SMALLEST_SCREEN_SPLIT_WIDTH_DP = 600;
// The minimum width of the activity to show the regular homepage layout.
private static final float MIN_REGULAR_HOMEPAGE_LAYOUT_WIDTH_DP = 380f;
@@ -58,16 +53,16 @@ public class ActivityEmbeddingUtils {
private static final String TAG = "ActivityEmbeddingUtils";
/** Get the smallest width dp of the window when the split should be used. */
- public static int getMinCurrentScreenSplitWidthDp() {
- return MIN_CURRENT_SCREEN_SPLIT_WIDTH_DP;
+ public static int getMinCurrentScreenSplitWidthDp(Context context) {
+ return context.getResources().getInteger(R.integer.config_activity_embed_split_min_cur_dp);
}
/**
* Get the smallest dp value of the smallest-width (sw) of the window in any rotation when
* the split should be used.
*/
- public static int getMinSmallestScreenSplitWidthDp() {
- return MIN_SMALLEST_SCREEN_SPLIT_WIDTH_DP;
+ public static int getMinSmallestScreenSplitWidthDp(Context context) {
+ return context.getResources().getInteger(R.integer.config_activity_embed_split_min_sw_dp);
}
/**
diff --git a/src/com/android/settings/applications/AppCounter.java b/src/com/android/settings/applications/AppCounter.java
index d5369324279c761762226a82ab321ca0aad0f755..2b1e47eaf2384e26b1c6ce5029c9386206fe31e8 100644
--- a/src/com/android/settings/applications/AppCounter.java
+++ b/src/com/android/settings/applications/AppCounter.java
@@ -22,12 +22,15 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.settings.flags.Flags;
+
import java.util.List;
public abstract class AppCounter extends AsyncTask {
@@ -54,7 +57,7 @@ public abstract class AppCounter extends AsyncTask {
for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
long flags = PackageManager.GET_DISABLED_COMPONENTS
| PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
- | (mFf.archiving() ? PackageManager.MATCH_ARCHIVED_PACKAGES : 0)
+ | (isArchivingEnabled() ? PackageManager.MATCH_ARCHIVED_PACKAGES : 0)
| (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0);
ApplicationInfoFlags infoFlags = ApplicationInfoFlags.of(flags);
final List list =
@@ -68,6 +71,11 @@ public abstract class AppCounter extends AsyncTask {
return count;
}
+ private boolean isArchivingEnabled() {
+ return mFf.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false)
+ || Flags.appArchiving();
+ }
+
@Override
protected void onPostExecute(Integer count) {
onCountComplete(count);
diff --git a/src/com/android/settings/applications/AppLocaleUtil.java b/src/com/android/settings/applications/AppLocaleUtil.java
index 70d284da214609ba466ec69b230576ab295c3b4b..103a2c10033fe61b6a4295436c45789b36cec440 100644
--- a/src/com/android/settings/applications/AppLocaleUtil.java
+++ b/src/com/android/settings/applications/AppLocaleUtil.java
@@ -16,7 +16,6 @@
package com.android.settings.applications;
-import android.annotation.NonNull;
import android.app.LocaleConfig;
import android.content.Context;
import android.content.Intent;
@@ -28,6 +27,7 @@ import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
diff --git a/src/com/android/settings/applications/AppStateClonedAppsBridge.java b/src/com/android/settings/applications/AppStateClonedAppsBridge.java
index 719023d10e23a824e312a403a50e3336ae8ba117..de0251dfffb54bf5bb2d19aac3f5af376cb2c35c 100644
--- a/src/com/android/settings/applications/AppStateClonedAppsBridge.java
+++ b/src/com/android/settings/applications/AppStateClonedAppsBridge.java
@@ -76,7 +76,8 @@ public class AppStateClonedAppsBridge extends AppStateBaseBridge{
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
// Display package if allowlisted but not yet cloned.
// Or if the app is present in clone profile alongwith being in allowlist.
- if (mAllowedApps.contains(pkg) && ((!mCloneProfileApps.contains(pkg) || (app.isCloned)))) {
+ if (mAllowedApps.contains(pkg)
+ && ((!mCloneProfileApps.contains(pkg) || (app.isClonedProfile())))) {
app.extraInfo = Boolean.TRUE;
} else {
app.extraInfo = Boolean.FALSE;
diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
index 80d3947593103f4e93268c6b5e4cd77b524b6115..851d763ea944c598940bd4ef3981276b4587ee5e 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
@@ -16,6 +16,8 @@
package com.android.settings.applications;
+import static android.webkit.Flags.updateServiceV2;
+
import android.Manifest;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -30,6 +32,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.location.LocationManager;
import android.os.RemoteException;
+import android.os.SystemConfigManager;
import android.os.UserManager;
import android.service.euicc.EuiccService;
import android.telecom.DefaultDialerManager;
@@ -41,6 +44,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.telephony.SmsApplication;
import com.android.settings.R;
+import com.android.settings.webview.WebViewUpdateServiceWrapper;
import java.util.ArrayList;
import java.util.List;
@@ -54,6 +58,9 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide
private final IPackageManager mPms;
private final DevicePolicyManager mDpm;
private final UserManager mUm;
+ private final WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
+ private final SystemConfigManager mSystemConfigManager;
+
/** Flags to use when querying PackageManager for Euicc component implementations. */
private static final int EUICC_QUERY_FLAGS =
PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
@@ -61,11 +68,17 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide
public ApplicationFeatureProviderImpl(Context context, PackageManager pm,
IPackageManager pms, DevicePolicyManager dpm) {
+ this(context, pm, pms, dpm, new WebViewUpdateServiceWrapper());
+ }
+ public ApplicationFeatureProviderImpl(Context context, PackageManager pm,
+ IPackageManager pms, DevicePolicyManager dpm, WebViewUpdateServiceWrapper wvusWrapper) {
mContext = context.getApplicationContext();
mPm = pm;
mPms = pms;
mDpm = dpm;
mUm = UserManager.get(mContext);
+ mWebViewUpdateServiceWrapper = wvusWrapper;
+ mSystemConfigManager = context.getSystemService(SystemConfigManager.class);
}
@Override
@@ -159,6 +172,14 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide
keepEnabledPackages.add(euicc.packageName);
}
+ // Keep WebView default package enabled.
+ if (updateServiceV2()) {
+ String packageName = mWebViewUpdateServiceWrapper.getDefaultWebViewPackageName();
+ if (packageName != null) {
+ keepEnabledPackages.add(packageName);
+ }
+ }
+
keepEnabledPackages.addAll(getEnabledPackageAllowlist());
final LocationManager locationManager =
@@ -167,6 +188,7 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide
if (locationHistoryPackage != null) {
keepEnabledPackages.add(locationHistoryPackage);
}
+ keepEnabledPackages.addAll(mSystemConfigManager.getPreventUserDisablePackages());
return keepEnabledPackages;
}
diff --git a/src/com/android/settings/applications/FetchPackageStorageAsyncLoader.java b/src/com/android/settings/applications/FetchPackageStorageAsyncLoader.java
index f3ad32655c6fe11363c3fa1fec7c12e1e4dd56f6..bb628c964132d6778e748f7077fd32679b5a8c8a 100644
--- a/src/com/android/settings/applications/FetchPackageStorageAsyncLoader.java
+++ b/src/com/android/settings/applications/FetchPackageStorageAsyncLoader.java
@@ -16,13 +16,15 @@
package com.android.settings.applications;
-import android.annotation.NonNull;
+
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.internal.util.Preconditions;
import com.android.settingslib.applications.StorageStatsSource;
import com.android.settingslib.applications.StorageStatsSource.AppStorageStats;
diff --git a/src/com/android/settings/applications/ProcessStatsSummary.java b/src/com/android/settings/applications/ProcessStatsSummary.java
index 4044794e719fc7a876b69ba86d4537183d773e3f..ef76cd5840b1d41c3e0e8bc134fb241113a9ead2 100644
--- a/src/com/android/settings/applications/ProcessStatsSummary.java
+++ b/src/com/android/settings/applications/ProcessStatsSummary.java
@@ -16,20 +16,26 @@
package com.android.settings.applications;
import android.app.settings.SettingsEnums;
+import android.content.ContentResolver;
import android.content.Context;
import android.icu.text.MessageFormat;
import android.os.Bundle;
+import android.os.Flags;
+import android.provider.Settings;
import android.text.format.Formatter;
import android.text.format.Formatter.BytesResult;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.SummaryPreference;
import com.android.settings.Utils;
import com.android.settings.applications.ProcStatsData.MemInfo;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.development.DisableDevSettingsDialogFragment;
import java.util.HashMap;
import java.util.Locale;
@@ -37,6 +43,8 @@ import java.util.Map;
public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenceClickListener {
+ private static final String KEY_PREF_SCREEN = "app_list";
+ private static final String KEY_MEMORY_INFO_PREF_GROUP = "memory_info";
private static final String KEY_STATUS_HEADER = "status_header";
private static final String KEY_PERFORMANCE = "performance";
@@ -44,7 +52,9 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc
private static final String KEY_AVERAGY_USED = "average_used";
private static final String KEY_FREE = "free";
private static final String KEY_APP_LIST = "apps_list";
+ private static final String KEY_FORCE_ENABLE_PSS_PROFILING = "force_enable_pss_profiling";
+ private PreferenceCategory mMemoryInfoPrefCategory;
private SummaryPreference mSummaryPref;
private Preference mPerformance;
@@ -52,12 +62,14 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc
private Preference mAverageUsed;
private Preference mFree;
private Preference mAppListPreference;
+ private SwitchPreference mForceEnablePssProfiling;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.process_stats_summary);
+ mMemoryInfoPrefCategory = (PreferenceCategory) findPreference(KEY_MEMORY_INFO_PREF_GROUP);
mSummaryPref = (SummaryPreference) findPreference(KEY_STATUS_HEADER);
mPerformance = findPreference(KEY_PERFORMANCE);
mTotalMemory = findPreference(KEY_TOTAL_MEMORY);
@@ -65,11 +77,37 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc
mFree = findPreference(KEY_FREE);
mAppListPreference = findPreference(KEY_APP_LIST);
mAppListPreference.setOnPreferenceClickListener(this);
+
+ // This preference is only applicable if the flag for PSS deprecation in AppProfiler is
+ // enabled. Otherwise, it can immediately be hidden.
+ mForceEnablePssProfiling =
+ (SwitchPreference) findPreference(KEY_FORCE_ENABLE_PSS_PROFILING);
+ if (Flags.removeAppProfilerPssCollection()) {
+ mForceEnablePssProfiling.setOnPreferenceClickListener(this);
+ // Make the toggle reflect the current state of the global setting.
+ mForceEnablePssProfiling.setChecked(isPssProfilingForceEnabled(getContext()));
+ } else {
+ mForceEnablePssProfiling.setVisible(false);
+ }
+ }
+
+ private void refreshPreferences() {
+ // The memory fields should be static if the flag is not enabled.
+ if (!Flags.removeAppProfilerPssCollection()) {
+ return;
+ }
+ mMemoryInfoPrefCategory.setVisible(mForceEnablePssProfiling.isChecked());
}
@Override
public void refreshUi() {
Context context = getContext();
+ refreshPreferences();
+
+ // If PSS collection is not enabled, none of the following work needs to be done.
+ if (Flags.removeAppProfilerPssCollection() && !isPssProfilingForceEnabled(context)) {
+ return;
+ }
MemInfo memInfo = mStatsManager.getMemInfo();
@@ -100,7 +138,8 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc
String durationString = getString(sDurationLabels[mDurationIndex]);
int numApps = mStatsManager.getEntries().size();
MessageFormat msgFormat = new MessageFormat(
- getResources().getString(R.string.memory_usage_apps_summary), Locale.getDefault());
+ getResources().getString(R.string.memory_usage_apps_summary),
+ Locale.getDefault());
Map arguments = new HashMap<>();
arguments.put("count", numApps);
arguments.put("time", durationString);
@@ -131,7 +170,34 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc
.setSourceMetricsCategory(getMetricsCategory())
.launch();
return true;
+ } else if (preference == mForceEnablePssProfiling) {
+ DisableDevSettingsDialogFragment.show(this);
}
return false;
}
+
+ private boolean isPssProfilingForceEnabled(Context context) {
+ ContentResolver cr = context.getContentResolver();
+ return Settings.Global.getInt(cr, Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
+ }
+
+ /**
+ * Called when the reboot confirmation button is clicked.
+ */
+ public void onRebootDialogConfirmed() {
+ Context context = getContext();
+ ContentResolver cr = context.getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.FORCE_ENABLE_PSS_PROFILING,
+ mForceEnablePssProfiling.isChecked() ? 1 : 0);
+ refreshPreferences();
+ }
+
+ /**
+ * Called when the reboot deny button is clicked.
+ */
+ public void onRebootDialogCanceled() {
+ // Set the toggle to reflect the state of the setting, which should not have changed.
+ mForceEnablePssProfiling.setChecked(isPssProfilingForceEnabled(getContext()));
+ }
+
}
diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java
index b81c7192a22d544c442e0b0f297c6539dda4b168..e111942f7517955359d13a56d4b4e9d1b59d4faa 100644
--- a/src/com/android/settings/applications/UsageAccessDetails.java
+++ b/src/com/android/settings/applications/UsageAccessDetails.java
@@ -38,11 +38,11 @@ import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.Preference.OnPreferenceClickListener;
-import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.applications.AppStateUsageBridge.UsageState;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
@@ -57,7 +57,7 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
// TODO: Break out this functionality into its own class.
private AppStateUsageBridge mUsageBridge;
private AppOpsManager mAppOpsManager;
- private TwoStatePreference mSwitchPref;
+ private RestrictedSwitchPreference mSwitchPref;
private Preference mUsageDesc;
private Intent mSettingsIntent;
private UsageState mUsageState;
@@ -78,7 +78,7 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
mDpm = context.getSystemService(DevicePolicyManager.class);
addPreferencesFromResource(R.xml.app_ops_permissions_details);
- mSwitchPref = (TwoStatePreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
+ mSwitchPref = (RestrictedSwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
mUsageDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC);
getPreferenceScreen().setTitle(R.string.usage_access);
@@ -170,8 +170,16 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
mPackageInfo.applicationInfo.uid);
boolean hasAccess = mUsageState.isPermissible();
+ boolean shouldEnable = mUsageState.permissionDeclared;
+
+ if (shouldEnable && !hasAccess) {
+ mSwitchPref.checkEcmRestrictionAndSetDisabled(AppOpsManager.OPSTR_GET_USAGE_STATS,
+ mPackageName);
+ shouldEnable = !mSwitchPref.isDisabledByEcm();
+ }
+
mSwitchPref.setChecked(hasAccess);
- mSwitchPref.setEnabled(mUsageState.permissionDeclared);
+ mSwitchPref.setEnabled(shouldEnable);
ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent,
PackageManager.GET_META_DATA, mUserId);
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
index 02d5c27e5c667db080ebac2ab54fb177d571283f..ef054dfa405eff71b6a2f2b4ba7b1581f43e928c 100644
--- a/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
@@ -21,6 +21,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
@@ -51,6 +52,9 @@ import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.widget.ActionButtonsPreference;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
import java.util.ArrayList;
import java.util.List;
@@ -63,29 +67,41 @@ public class UserAspectRatioDetails extends AppInfoBase implements
private static final String KEY_HEADER_SUMMARY = "app_aspect_ratio_summary";
private static final String KEY_HEADER_BUTTONS = "header_view";
- private static final String KEY_PREF_FULLSCREEN = "fullscreen_pref";
+
private static final String KEY_PREF_HALF_SCREEN = "half_screen_pref";
private static final String KEY_PREF_DISPLAY_SIZE = "display_size_pref";
private static final String KEY_PREF_16_9 = "16_9_pref";
private static final String KEY_PREF_4_3 = "4_3_pref";
@VisibleForTesting
+ static final String KEY_PREF_FULLSCREEN = "fullscreen_pref";
+ @VisibleForTesting
static final String KEY_PREF_DEFAULT = "app_default_pref";
@VisibleForTesting
static final String KEY_PREF_3_2 = "3_2_pref";
+ @VisibleForTesting
+ @NonNull String mSelectedKey = KEY_PREF_DEFAULT;
+
+ /** Radio button preference key mapped to {@link PackageManager.UserMinAspectRatio} value */
+ @VisibleForTesting
+ final BiMap mKeyToAspectRatioMap = HashBiMap.create();
+
private final List mAspectRatioPreferences = new ArrayList<>();
@NonNull private UserAspectRatioManager mUserAspectRatioManager;
- @NonNull private String mSelectedKey = KEY_PREF_DEFAULT;
+ private boolean mIsOverrideToFullscreenEnabled;
@Override
public void onCreate(@NonNull Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUserAspectRatioManager = new UserAspectRatioManager(getContext());
+ mIsOverrideToFullscreenEnabled = getAspectRatioManager()
+ .isOverrideToFullscreenEnabled(mPackageName, mUserId);
+
initPreferences();
try {
- final int userAspectRatio = mUserAspectRatioManager
+ final int userAspectRatio = getAspectRatioManager()
.getUserMinAspectRatioValue(mPackageName, mUserId);
mSelectedKey = getSelectedKey(userAspectRatio);
} catch (RemoteException e) {
@@ -148,43 +164,23 @@ public class UserAspectRatioDetails extends AppInfoBase implements
}
@PackageManager.UserMinAspectRatio
- private int getSelectedUserMinAspectRatio(@NonNull String selectedKey) {
- switch (selectedKey) {
- case KEY_PREF_FULLSCREEN:
- return USER_MIN_ASPECT_RATIO_FULLSCREEN;
- case KEY_PREF_HALF_SCREEN:
- return USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
- case KEY_PREF_DISPLAY_SIZE:
- return USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
- case KEY_PREF_3_2:
- return USER_MIN_ASPECT_RATIO_3_2;
- case KEY_PREF_4_3:
- return USER_MIN_ASPECT_RATIO_4_3;
- case KEY_PREF_16_9:
- return USER_MIN_ASPECT_RATIO_16_9;
- default:
- return USER_MIN_ASPECT_RATIO_UNSET;
- }
+ @VisibleForTesting
+ int getSelectedUserMinAspectRatio(@NonNull String selectedKey) {
+ final int appDefault = mKeyToAspectRatioMap
+ .getOrDefault(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_UNSET);
+ return mKeyToAspectRatioMap.getOrDefault(selectedKey, appDefault);
}
@NonNull
private String getSelectedKey(@PackageManager.UserMinAspectRatio int userMinAspectRatio) {
- switch (userMinAspectRatio) {
- case USER_MIN_ASPECT_RATIO_FULLSCREEN:
- return KEY_PREF_FULLSCREEN;
- case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
- return KEY_PREF_HALF_SCREEN;
- case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
- return KEY_PREF_DISPLAY_SIZE;
- case USER_MIN_ASPECT_RATIO_3_2:
- return KEY_PREF_3_2;
- case USER_MIN_ASPECT_RATIO_4_3:
- return KEY_PREF_4_3;
- case USER_MIN_ASPECT_RATIO_16_9:
- return KEY_PREF_16_9;
- default:
- return KEY_PREF_DEFAULT;
+ final String appDefault = mKeyToAspectRatioMap.inverse()
+ .getOrDefault(USER_MIN_ASPECT_RATIO_UNSET, KEY_PREF_DEFAULT);
+
+ if (userMinAspectRatio == USER_MIN_ASPECT_RATIO_UNSET && mIsOverrideToFullscreenEnabled) {
+ // Pre-select fullscreen option if device manufacturer has overridden app to fullscreen
+ userMinAspectRatio = USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
+ return mKeyToAspectRatioMap.inverse().getOrDefault(userMinAspectRatio, appDefault);
}
@Override
@@ -217,7 +213,11 @@ public class UserAspectRatioDetails extends AppInfoBase implements
.setButton1Icon(R.drawable.ic_settings_open)
.setButton1OnClickListener(v -> launchApplication());
- addPreference(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_UNSET);
+ if (mIsOverrideToFullscreenEnabled) {
+ addPreference(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_APP_DEFAULT);
+ } else {
+ addPreference(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_UNSET);
+ }
addPreference(KEY_PREF_FULLSCREEN, USER_MIN_ASPECT_RATIO_FULLSCREEN);
addPreference(KEY_PREF_DISPLAY_SIZE, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
addPreference(KEY_PREF_HALF_SCREEN, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
@@ -232,12 +232,13 @@ public class UserAspectRatioDetails extends AppInfoBase implements
if (pref == null) {
return;
}
- if (!mUserAspectRatioManager.hasAspectRatioOption(aspectRatio, mPackageName)) {
+ if (!getAspectRatioManager().hasAspectRatioOption(aspectRatio, mPackageName)) {
pref.setVisible(false);
return;
}
pref.setTitle(mUserAspectRatioManager.getAccessibleEntry(aspectRatio, mPackageName));
pref.setOnClickListener(this);
+ mKeyToAspectRatioMap.put(key, aspectRatio);
mAspectRatioPreferences.add(pref);
}
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
index 3cca5f6771e4183d57fd51542c1cc7668d8f129f..3bf61099f13eb6786cf21e32d329ea84c56ac78f 100644
--- a/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
@@ -16,19 +16,32 @@
package com.android.settings.applications.appcompat;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import static android.os.UserHandle.getUserHandleForUid;
+import static android.os.UserHandle.getUserId;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
import static java.lang.Boolean.FALSE;
import android.app.AppGlobals;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
@@ -37,6 +50,7 @@ import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.Utils;
+import com.android.window.flags.Flags;
import com.google.common.annotations.VisibleForTesting;
@@ -55,6 +69,8 @@ public class UserAspectRatioManager {
"enable_app_compat_user_aspect_ratio_fullscreen";
private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN = true;
+ final boolean mIsUserMinAspectRatioAppDefaultFlagEnabled = Flags.userMinAspectRatioAppDefault();
+
private final Context mContext;
private final IPackageManager mIPm;
/** Apps that have launcher entry defined in manifest */
@@ -62,8 +78,13 @@ public class UserAspectRatioManager {
private final Map mUserAspectRatioA11yMap;
public UserAspectRatioManager(@NonNull Context context) {
+ this(context, AppGlobals.getPackageManager());
+ }
+
+ @VisibleForTesting
+ UserAspectRatioManager(@NonNull Context context, @NonNull IPackageManager pm) {
mContext = context;
- mIPm = AppGlobals.getPackageManager();
+ mIPm = pm;
mUserAspectRatioA11yMap = new ArrayMap<>();
mUserAspectRatioMap = getUserMinAspectRatioMapping();
}
@@ -86,7 +107,7 @@ public class UserAspectRatioManager {
throws RemoteException {
final int aspectRatio = mIPm.getUserMinAspectRatio(packageName, uid);
return hasAspectRatioOption(aspectRatio, packageName)
- ? aspectRatio : PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+ ? aspectRatio : USER_MIN_ASPECT_RATIO_UNSET;
}
/**
@@ -94,11 +115,18 @@ public class UserAspectRatioManager {
*/
@NonNull
public String getUserMinAspectRatioEntry(@PackageManager.UserMinAspectRatio int aspectRatio,
- String packageName) {
- if (!hasAspectRatioOption(aspectRatio, packageName)) {
- return mUserAspectRatioMap.get(PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+ @NonNull String packageName, int userId) {
+ final String appDefault = getAspectRatioStringOrDefault(
+ mUserAspectRatioMap.get(USER_MIN_ASPECT_RATIO_UNSET),
+ USER_MIN_ASPECT_RATIO_UNSET);
+
+ if (!hasAspectRatioOption(aspectRatio, packageName)) {
+ return appDefault;
}
- return mUserAspectRatioMap.get(aspectRatio);
+
+ return isCurrentSelectionFromManufacturerOverride(packageName, userId, aspectRatio)
+ ? getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_FULLSCREEN, packageName, userId)
+ : mUserAspectRatioMap.getOrDefault(aspectRatio, appDefault);
}
/**
@@ -106,19 +134,22 @@ public class UserAspectRatioManager {
*/
@NonNull
public CharSequence getAccessibleEntry(@PackageManager.UserMinAspectRatio int aspectRatio,
- String packageName) {
- return mUserAspectRatioA11yMap.getOrDefault(aspectRatio,
- getUserMinAspectRatioEntry(aspectRatio, packageName));
+ @NonNull String packageName) {
+ final int userId = mContext.getUserId();
+ return isCurrentSelectionFromManufacturerOverride(packageName, userId, aspectRatio)
+ ? getAccessibleEntry(USER_MIN_ASPECT_RATIO_FULLSCREEN, packageName)
+ : mUserAspectRatioA11yMap.getOrDefault(aspectRatio,
+ getUserMinAspectRatioEntry(aspectRatio, packageName, userId));
}
/**
* @return corresponding aspect ratio string for package name and user
*/
@NonNull
- public String getUserMinAspectRatioEntry(@NonNull String packageName, int uid)
+ public String getUserMinAspectRatioEntry(@NonNull String packageName, int userId)
throws RemoteException {
- final int aspectRatio = getUserMinAspectRatioValue(packageName, uid);
- return getUserMinAspectRatioEntry(aspectRatio, packageName);
+ final int aspectRatio = getUserMinAspectRatioValue(packageName, userId);
+ return getUserMinAspectRatioEntry(aspectRatio, packageName, userId);
}
/**
@@ -128,8 +159,7 @@ public class UserAspectRatioManager {
*/
public boolean hasAspectRatioOption(@PackageManager.UserMinAspectRatio int option,
String packageName) {
- if (option == PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN
- && !isFullscreenOptionEnabled(packageName)) {
+ if (option == USER_MIN_ASPECT_RATIO_FULLSCREEN && !isFullscreenOptionEnabled(packageName)) {
return false;
}
return mUserAspectRatioMap.containsKey(option);
@@ -154,6 +184,18 @@ public class UserAspectRatioManager {
return !FALSE.equals(appAllowsUserAspectRatioOverride) && hasLauncherEntry(app);
}
+ /**
+ * Whether the app has been overridden to fullscreen by device manufacturer or
+ * whether the app's aspect ratio has been overridden by the user.
+ */
+ public boolean isAppOverridden(@NonNull ApplicationInfo app,
+ @PackageManager.UserMinAspectRatio int userOverride) {
+ return (userOverride != USER_MIN_ASPECT_RATIO_UNSET
+ && userOverride != USER_MIN_ASPECT_RATIO_APP_DEFAULT)
+ || isCurrentSelectionFromManufacturerOverride(app.packageName, getUserId(app.uid),
+ userOverride);
+ }
+
/**
* Whether fullscreen option in per-app user aspect ratio settings is enabled
*/
@@ -168,6 +210,32 @@ public class UserAspectRatioManager {
DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN);
}
+ /**
+ * Whether the device manufacturer has overridden app's orientation to
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER} to force app to fullscreen
+ * and app has not opted-out from the treatment
+ */
+ boolean isOverrideToFullscreenEnabled(String pkgName, int userId) {
+ Boolean appAllowsOrientationOverride = readComponentProperty(mContext.getPackageManager(),
+ pkgName, PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
+ return mIsUserMinAspectRatioAppDefaultFlagEnabled
+ && hasAspectRatioOption(USER_MIN_ASPECT_RATIO_FULLSCREEN, pkgName)
+ && !FALSE.equals(appAllowsOrientationOverride)
+ && isFullscreenCompatChangeEnabled(pkgName, userId);
+ }
+
+ @VisibleForTesting
+ boolean isFullscreenCompatChangeEnabled(String pkgName, int userId) {
+ return CompatChanges.isChangeEnabled(
+ OVERRIDE_ANY_ORIENTATION_TO_USER, pkgName, UserHandle.of(userId));
+ }
+
+ private boolean isCurrentSelectionFromManufacturerOverride(String pkgName, int userId,
+ @PackageManager.UserMinAspectRatio int aspectRatio) {
+ return aspectRatio == USER_MIN_ASPECT_RATIO_UNSET
+ && isOverrideToFullscreenEnabled(pkgName, userId);
+ }
+
private boolean hasLauncherEntry(@NonNull ApplicationInfo app) {
return !mContext.getSystemService(LauncherApps.class)
.getActivityList(app.packageName, getUserHandleForUid(app.uid))
@@ -197,13 +265,13 @@ public class UserAspectRatioManager {
boolean containsColon = aspectRatioString.contains(":");
switch (aspectRatioVal) {
// Only map known values of UserMinAspectRatio and ignore unknown entries
- case PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN:
- case PackageManager.USER_MIN_ASPECT_RATIO_UNSET:
- case PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
- case PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
- case PackageManager.USER_MIN_ASPECT_RATIO_4_3:
- case PackageManager.USER_MIN_ASPECT_RATIO_16_9:
- case PackageManager.USER_MIN_ASPECT_RATIO_3_2:
+ case USER_MIN_ASPECT_RATIO_FULLSCREEN:
+ case USER_MIN_ASPECT_RATIO_UNSET:
+ case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+ case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+ case USER_MIN_ASPECT_RATIO_4_3:
+ case USER_MIN_ASPECT_RATIO_16_9:
+ case USER_MIN_ASPECT_RATIO_3_2:
if (containsColon) {
String[] aspectRatioDigits = aspectRatioString.split(":");
String accessibleString = getAccessibleOption(aspectRatioDigits[0],
@@ -215,10 +283,18 @@ public class UserAspectRatioManager {
userMinAspectRatioMap.put(aspectRatioVal, aspectRatioString);
}
}
- if (!userMinAspectRatioMap.containsKey(PackageManager.USER_MIN_ASPECT_RATIO_UNSET)) {
+ if (!userMinAspectRatioMap.containsKey(USER_MIN_ASPECT_RATIO_UNSET)) {
throw new RuntimeException("config_userAspectRatioOverrideValues options must have"
+ " USER_MIN_ASPECT_RATIO_UNSET value");
}
+ if (mIsUserMinAspectRatioAppDefaultFlagEnabled) {
+ userMinAspectRatioMap.put(USER_MIN_ASPECT_RATIO_APP_DEFAULT,
+ userMinAspectRatioMap.get(USER_MIN_ASPECT_RATIO_UNSET));
+ if (mUserAspectRatioA11yMap.containsKey(USER_MIN_ASPECT_RATIO_UNSET)) {
+ mUserAspectRatioA11yMap.put(USER_MIN_ASPECT_RATIO_APP_DEFAULT,
+ mUserAspectRatioA11yMap.get(USER_MIN_ASPECT_RATIO_UNSET));
+ }
+ }
return userMinAspectRatioMap;
}
@@ -236,17 +312,17 @@ public class UserAspectRatioManager {
}
// Options are customized per device and if strings are set to @null, use default
switch (aspectRatioVal) {
- case PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN:
+ case USER_MIN_ASPECT_RATIO_FULLSCREEN:
return mContext.getString(R.string.user_aspect_ratio_fullscreen);
- case PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+ case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
return mContext.getString(R.string.user_aspect_ratio_half_screen);
- case PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+ case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
return mContext.getString(R.string.user_aspect_ratio_device_size);
- case PackageManager.USER_MIN_ASPECT_RATIO_4_3:
+ case USER_MIN_ASPECT_RATIO_4_3:
return mContext.getString(R.string.user_aspect_ratio_4_3);
- case PackageManager.USER_MIN_ASPECT_RATIO_16_9:
+ case USER_MIN_ASPECT_RATIO_16_9:
return mContext.getString(R.string.user_aspect_ratio_16_9);
- case PackageManager.USER_MIN_ASPECT_RATIO_3_2:
+ case USER_MIN_ASPECT_RATIO_3_2:
return mContext.getString(R.string.user_aspect_ratio_3_2);
default:
return mContext.getString(R.string.user_aspect_ratio_app_default);
diff --git a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java
index 03053fdeeb63fd32b6c8158dc65996ef5f30ada0..b80de32cf86845e2e37b5d558e4e6572453a3d5d 100644
--- a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java
@@ -32,6 +32,7 @@ import android.content.IntentFilter;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -292,7 +293,8 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp
switch (id) {
case ButtonActionDialogFragment.DialogType.DISABLE:
mMetricsFeatureProvider.action(mActivity,
- SettingsEnums.ACTION_SETTINGS_DISABLE_APP);
+ SettingsEnums.ACTION_SETTINGS_DISABLE_APP,
+ getPackageNameForMetric());
AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER));
break;
@@ -433,10 +435,17 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp
// No preferred default, so permit uninstall only when
// there is more than one candidate
enabled = (mHomePackages.size() > 1);
- } else {
- // There is an explicit default home app -- forbid uninstall of
- // that one, but permit it for installed-but-inactive ones.
- enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName());
+ } else if (mPackageInfo.packageName.equals(currentDefaultHome.getPackageName())) {
+ if (Flags.improveHomeAppBehavior()) {
+ // Allow uninstallation of current home app if it is a non-system app
+ // and/or there are other candidate apps available.
+ if (mPackageInfo.applicationInfo.isSystemApp()
+ || mHomePackages.size() == 1) {
+ enabled = false;
+ }
+ } else {
+ enabled = false;
+ }
}
}
}
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index 82d55f3d9f820732e60285b9e4afa9ea529709c7..90d733e6fe1788d523d3af64a67953cb49e04dba 100644
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -24,6 +24,7 @@ import android.app.Activity;
import android.app.AppOpsManager;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
+import android.app.ecm.EnhancedConfirmationManager;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -490,12 +491,23 @@ public class AppInfoDashboardFragment extends DashboardFragment
return true;
case ACCESS_RESTRICTED_SETTINGS:
showLockScreen(getContext(), () -> {
- final AppOpsManager appOpsManager = getContext().getSystemService(
- AppOpsManager.class);
- appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
- getUid(),
- getPackageName(),
- AppOpsManager.MODE_ALLOWED);
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ && android.security.Flags.extendEcmToAllSettings()) {
+ EnhancedConfirmationManager manager = getContext().getSystemService(
+ EnhancedConfirmationManager.class);
+ try {
+ manager.clearRestriction(getPackageName());
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Exception when retrieving package:" + getPackageName(), e);
+ }
+ } else {
+ final AppOpsManager appOpsManager = getContext().getSystemService(
+ AppOpsManager.class);
+ appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+ getUid(),
+ getPackageName(),
+ AppOpsManager.MODE_ALLOWED);
+ }
getActivity().invalidateOptionsMenu();
final String toastString = getContext().getString(
R.string.toast_allows_restricted_settings_successfully,
@@ -527,14 +539,25 @@ public class AppInfoDashboardFragment extends DashboardFragment
}
private boolean shouldShowAccessRestrictedSettings() {
- try {
- final int mode = getSystemService(AppOpsManager.class).noteOpNoThrow(
- AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, getUid(),
- getPackageName());
- return mode == AppOpsManager.MODE_IGNORED;
- } catch (Exception e) {
- // Fallback in case if app ops is not available in testing.
- return false;
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ && android.security.Flags.extendEcmToAllSettings()) {
+ try {
+ return getSystemService(EnhancedConfirmationManager.class)
+ .isClearRestrictionAllowed(getPackageName());
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Exception when retrieving package:" + getPackageName(), e);
+ return false;
+ }
+ } else {
+ try {
+ final int mode = getSystemService(AppOpsManager.class).noteOpNoThrow(
+ AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, getUid(),
+ getPackageName());
+ return mode == AppOpsManager.MODE_IGNORED;
+ } catch (Exception e) {
+ // Fallback in case if app ops is not available in testing.
+ return false;
+ }
}
}
diff --git a/src/com/android/settings/applications/appops/BackgroundCheckSummary.java b/src/com/android/settings/applications/appops/BackgroundCheckSummary.java
index 58f962ab7162d7662d8c5c37109c83b09f83906b..4ed9002d2e28db9f70eaf3819b52c5264bb55937 100644
--- a/src/com/android/settings/applications/appops/BackgroundCheckSummary.java
+++ b/src/com/android/settings/applications/appops/BackgroundCheckSummary.java
@@ -16,7 +16,6 @@
package com.android.settings.applications.appops;
-import android.annotation.Nullable;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.preference.PreferenceFrameLayout;
@@ -24,6 +23,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentTransaction;
import com.android.settings.R;
diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
index e85413896e50ee528f584a476f2aac65ab849cc0..4f278709f6b94d0f5f0d9c6bcbd11d948d6edf54 100644
--- a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
+++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
@@ -16,17 +16,22 @@
package com.android.settings.applications.credentials;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ServiceInfo;
import android.credentials.CredentialProviderInfo;
import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
@@ -41,6 +46,11 @@ import java.util.Set;
* logic for each row in settings.
*/
public final class CombinedProviderInfo {
+ private static final String TAG = "CombinedProviderInfo";
+ private static final String SETTINGS_ACTIVITY_INTENT_ACTION = "android.intent.action.MAIN";
+ private static final String SETTINGS_ACTIVITY_INTENT_CATEGORY =
+ "android.intent.category.DEFAULT";
+
private final List mCredentialProviderInfos;
private final @Nullable AutofillServiceInfo mAutofillServiceInfo;
private final boolean mIsDefaultAutofillProvider;
@@ -315,4 +325,48 @@ public final class CombinedProviderInfo {
return cmpi;
}
+
+ public static @Nullable Intent createSettingsActivityIntent(
+ @Nullable CharSequence packageName,
+ @Nullable CharSequence settingsActivity) {
+ if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(settingsActivity)) {
+ return null;
+ }
+
+ ComponentName cn =
+ new ComponentName(String.valueOf(packageName), String.valueOf(settingsActivity));
+ if (cn == null) {
+ Log.e(
+ TAG,
+ "Failed to deserialize settingsActivity attribute, we got: "
+ + String.valueOf(packageName)
+ + " and "
+ + String.valueOf(settingsActivity));
+ return null;
+ }
+
+ Intent intent = new Intent(SETTINGS_ACTIVITY_INTENT_ACTION);
+ intent.addCategory(SETTINGS_ACTIVITY_INTENT_CATEGORY);
+ intent.setComponent(cn);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ /** Launches the settings activity intent. */
+ public static void launchSettingsActivityIntent(
+ @NonNull Context context,
+ @Nullable CharSequence packageName,
+ @Nullable CharSequence settingsActivity,
+ int userId) {
+ Intent settingsIntent = createSettingsActivityIntent(packageName, settingsActivity);
+ if (settingsIntent == null) {
+ return;
+ }
+
+ try {
+ context.startActivityAsUser(settingsIntent, UserHandle.of(userId));
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Failed to open settings activity", e);
+ }
+ }
}
diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
index b0905bab077a90ae802e164b8c7f53cd9dd8fd55..e16219b7402e7eafb6db10580d9c941e8ff18588 100644
--- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
@@ -18,8 +18,6 @@ package com.android.settings.applications.credentials;
import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.Dialog;
import android.content.ComponentName;
@@ -34,6 +32,7 @@ import android.content.res.Resources;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.SetEnabledProvidersException;
+import android.credentials.flags.Flags;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -46,9 +45,12 @@ import android.provider.Settings;
import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.view.View;
import android.widget.CompoundButton;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
@@ -75,6 +77,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -95,9 +98,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
private static final String ALTERNATE_INTENT = "android.settings.SYNC_SETTINGS";
private static final String PRIMARY_INTENT = "android.settings.CREDENTIAL_PROVIDER";
private static final int MAX_SELECTABLE_PROVIDERS = 5;
- private static final String SETTINGS_ACTIVITY_INTENT_ACTION = "android.intent.action.MAIN";
- private static final String SETTINGS_ACTIVITY_INTENT_CATEGORY =
- "android.intent.category.LAUNCHER";
private final PackageManager mPm;
private final List mServices;
@@ -108,14 +108,16 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
private final List mPendingServiceInfos = new ArrayList<>();
private final Handler mHandler = new Handler();
private final SettingContentObserver mSettingsContentObserver;
+ private final ImageUtils.IconResizer mIconResizer;
private @Nullable FragmentManager mFragmentManager = null;
private @Nullable Delegate mDelegate = null;
private @Nullable String mFlagOverrideForTest = null;
private @Nullable PreferenceScreen mPreferenceScreen = null;
- private boolean mVisibility = false;
+ private Optional mSimulateHiddenForTests = Optional.empty();
private boolean mIsWorkProfile = false;
+ private boolean mSimulateConnectedForTests = false;
public CredentialManagerPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -129,6 +131,13 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
new SettingContentObserver(mHandler, context.getContentResolver());
mSettingsContentObserver.register();
mSettingsPackageMonitor.register(context, context.getMainLooper(), false);
+ mIconResizer = getResizer(context);
+ }
+
+ private static ImageUtils.IconResizer getResizer(Context context) {
+ final Resources resources = context.getResources();
+ int size = (int) resources.getDimension(android.R.dimen.app_icon_size);
+ return new ImageUtils.IconResizer(size, size, resources.getDisplayMetrics());
}
private @Nullable CredentialManager getCredentialManager(Context context, boolean isTest) {
@@ -147,15 +156,17 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
@Override
public int getAvailabilityStatus() {
- if (mCredentialManager == null) {
+ if (!isConnected()) {
return UNSUPPORTED_ON_DEVICE;
}
- if (!mVisibility) {
+ // If there is no top provider or any providers in the list then
+ // we should hide this pref.
+ if (isHiddenDueToNoProviderSet()) {
return CONDITIONALLY_UNAVAILABLE;
}
- if (mServices.isEmpty()) {
+ if (!hasNonPrimaryServices()) {
return CONDITIONALLY_UNAVAILABLE;
}
@@ -174,7 +185,11 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
@VisibleForTesting
public boolean isConnected() {
- return mCredentialManager != null;
+ return mCredentialManager != null || mSimulateConnectedForTests;
+ }
+
+ public void setSimulateConnectedForTests(boolean simulateConnectedForTests) {
+ mSimulateConnectedForTests = simulateConnectedForTests;
}
/**
@@ -293,7 +308,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
NewProviderConfirmationDialogFragment fragment =
newNewProviderConfirmationDialogFragment(
- serviceInfo.packageName, appName, /* setActivityResult= */ true);
+ serviceInfo.packageName, appName, /* shouldSetActivityResult= */ true);
if (fragment == null || mFragmentManager == null) {
return;
}
@@ -365,17 +380,32 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
}
}
- private void setVisibility(boolean newVisibility) {
- if (newVisibility == mVisibility) {
- return;
- }
-
- mVisibility = newVisibility;
+ @VisibleForTesting
+ public void forceDelegateRefresh() {
if (mDelegate != null) {
mDelegate.forceDelegateRefresh();
}
}
+ @VisibleForTesting
+ public void setSimulateHiddenForTests(Optional simulateHiddenForTests) {
+ mSimulateHiddenForTests = simulateHiddenForTests;
+ }
+
+ @VisibleForTesting
+ public boolean isHiddenDueToNoProviderSet() {
+ return isHiddenDueToNoProviderSet(getProviders());
+ }
+
+ private boolean isHiddenDueToNoProviderSet(
+ Pair, CombinedProviderInfo> providerPair) {
+ if (mSimulateHiddenForTests.isPresent()) {
+ return mSimulateHiddenForTests.get();
+ }
+
+ return (providerPair.first.size() == 0 || providerPair.second == null);
+ }
+
@VisibleForTesting
void setAvailableServices(
List availableServices, String flagOverrideForTest) {
@@ -398,6 +428,17 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
}
}
+ @VisibleForTesting
+ public boolean hasNonPrimaryServices() {
+ for (CredentialProviderInfo availableService : mServices) {
+ if (!availableService.isPrimary()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
@@ -441,10 +482,11 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
return preference;
}
- /** Aggregates the list of services and builds a list of UI prefs to show. */
- @VisibleForTesting
- public Map buildPreferenceList(
- Context context, PreferenceGroup group) {
+ /**
+ * Returns a pair that contains a list of the providers in the first position and the top
+ * provider in the second position.
+ */
+ private Pair, CombinedProviderInfo> getProviders() {
// Get the selected autofill provider. If it is the placeholder then replace it with an
// empty string.
String selectedAutofillProvider =
@@ -457,15 +499,25 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
// Get the list of combined providers.
List providers =
CombinedProviderInfo.buildMergedList(
- AutofillServiceInfo.getAvailableServices(context, getUser()),
+ AutofillServiceInfo.getAvailableServices(mContext, getUser()),
mServices,
selectedAutofillProvider);
+ return new Pair<>(providers, CombinedProviderInfo.getTopProvider(providers));
+ }
- // Get the provider that is displayed at the top. If there is none then hide
- // everything.
- CombinedProviderInfo topProvider = CombinedProviderInfo.getTopProvider(providers);
- if (topProvider == null) {
- setVisibility(false);
+ /** Aggregates the list of services and builds a list of UI prefs to show. */
+ @VisibleForTesting
+ public @NonNull Map buildPreferenceList(
+ @NonNull Context context, @NonNull PreferenceGroup group) {
+ // Get the providers and extract the values.
+ Pair, CombinedProviderInfo> providerPair = getProviders();
+ CombinedProviderInfo topProvider = providerPair.second;
+ List providers = providerPair.first;
+
+ // If the provider is set to "none" or there are no providers then we should not
+ // return any providers.
+ if (isHiddenDueToNoProviderSet(providerPair)) {
+ forceDelegateRefresh();
return new HashMap<>();
}
@@ -475,6 +527,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
// If this provider is displayed at the top then we should not show it.
if (topProvider != null
+ && topProvider.getApplicationInfo() != null
&& topProvider.getApplicationInfo().packageName.equals(packageName)) {
continue;
}
@@ -484,10 +537,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
continue;
}
- // Get the settings activity.
- CharSequence settingsActivity =
- combinedInfo.getCredentialProviderInfos().get(0).getSettingsActivity();
-
Drawable icon = combinedInfo.getAppIcon(context, getUser());
CharSequence title = combinedInfo.getAppName(context);
@@ -499,13 +548,13 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
icon,
packageName,
combinedInfo.getSettingsSubtitle(),
- settingsActivity);
+ combinedInfo.getSettingsActivity());
output.put(packageName, pref);
group.addPreference(pref);
}
// Set the visibility if we have services.
- setVisibility(!output.isEmpty());
+ forceDelegateRefresh();
return output;
}
@@ -530,7 +579,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
*/
@VisibleForTesting
public boolean togglePackageNameEnabled(String packageName) {
- if (mEnabledPackageNames.size() >= MAX_SELECTABLE_PROVIDERS) {
+ if (hasProviderLimitBeenReached()) {
return false;
} else {
mEnabledPackageNames.add(packageName);
@@ -574,6 +623,30 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
return enabledServices;
}
+ @VisibleForTesting
+ public @NonNull Drawable processIcon(@Nullable Drawable icon) {
+ // If we didn't get an icon then we should use the default app icon.
+ if (icon == null) {
+ icon = mPm.getDefaultActivityIcon();
+ }
+
+ Drawable providerIcon = Utils.getSafeIcon(icon);
+ return mIconResizer.createIconThumbnail(providerIcon);
+ }
+
+ private boolean hasProviderLimitBeenReached() {
+ return hasProviderLimitBeenReached(mEnabledPackageNames.size());
+ }
+
+ @VisibleForTesting
+ public static boolean hasProviderLimitBeenReached(int enabledAdditionalProviderCount) {
+ // If the number of package names has reached the maximum limit then
+ // we should stop any new packages from being added. We will also
+ // reserve one place for the primary provider so if the max limit is
+ // five providers this will be four additional plus the primary.
+ return (enabledAdditionalProviderCount + 1) >= MAX_SELECTABLE_PROVIDERS;
+ }
+
private CombiPreference addProviderPreference(
@NonNull Context prefContext,
@NonNull CharSequence title,
@@ -584,13 +657,14 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
final CombiPreference pref =
new CombiPreference(prefContext, mEnabledPackageNames.contains(packageName));
pref.setTitle(title);
+ pref.setLayoutResource(R.layout.preference_icon_credman);
- if (icon != null) {
+ if (Flags.newSettingsUi()) {
+ pref.setIcon(processIcon(icon));
+ } else if (icon != null) {
pref.setIcon(icon);
}
- pref.setLayoutResource(R.layout.preference_icon_credman);
-
if (subtitle != null) {
pref.setSummary(subtitle);
}
@@ -598,19 +672,18 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
pref.setPreferenceListener(
new CombiPreference.OnCombiPreferenceClickListener() {
@Override
- public void onCheckChanged(CombiPreference p, boolean isChecked) {
+ public boolean onCheckChanged(CombiPreference p, boolean isChecked) {
if (isChecked) {
- if (mEnabledPackageNames.size() >= MAX_SELECTABLE_PROVIDERS) {
+ if (hasProviderLimitBeenReached()) {
// Show the error if too many enabled.
- pref.setChecked(false);
final DialogFragment fragment = newErrorDialogFragment();
if (fragment == null || mFragmentManager == null) {
- return;
+ return false;
}
fragment.show(mFragmentManager, ErrorDialogFragment.TAG);
- return;
+ return false;
}
togglePackageNameEnabled(packageName);
@@ -622,47 +695,14 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
} else {
togglePackageNameDisabled(packageName);
}
+
+ return true;
}
@Override
public void onLeftSideClicked() {
- if (settingsActivity == null) {
- Log.w(TAG, "settingsActivity was null");
- return;
- }
-
- String settingsActivityStr = String.valueOf(settingsActivity);
- ComponentName cn = ComponentName.unflattenFromString(settingsActivityStr);
- if (cn == null) {
- Log.w(
- TAG,
- "Failed to deserialize settingsActivity attribute, we got: "
- + settingsActivityStr);
- return;
- }
-
- Intent intent = new Intent(SETTINGS_ACTIVITY_INTENT_ACTION);
- intent.addCategory(SETTINGS_ACTIVITY_INTENT_CATEGORY);
- intent.setComponent(cn);
-
- Context context = mContext;
- int currentUserId = getUser();
- int contextUserId = context.getUser().getIdentifier();
-
- if (currentUserId != contextUserId) {
- Log.d(
- TAG,
- "onLeftSideClicked(): using context for current user ("
- + currentUserId
- + ") instead of user "
- + contextUserId
- + " on headless system user mode");
- context =
- context.createContextAsUser(
- UserHandle.of(currentUserId), /* flags= */ 0);
- }
-
- context.startActivity(intent);
+ CombinedProviderInfo.launchSettingsActivityIntent(
+ mContext, packageName, settingsActivity, getUser());
}
});
@@ -711,13 +751,13 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
newNewProviderConfirmationDialogFragment(
@NonNull String packageName,
@NonNull CharSequence appName,
- boolean setActivityResult) {
+ boolean shouldSetActivityResult) {
DialogHost host =
new DialogHost() {
@Override
public void onDialogClick(int whichButton) {
completeEnableProviderDialogBox(
- whichButton, packageName, setActivityResult);
+ whichButton, packageName, shouldSetActivityResult);
}
@Override
@@ -728,8 +768,8 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
}
@VisibleForTesting
- void completeEnableProviderDialogBox(
- int whichButton, String packageName, boolean setActivityResult) {
+ int completeEnableProviderDialogBox(
+ int whichButton, String packageName, boolean shouldSetActivityResult) {
int activityResult = -1;
if (whichButton == DialogInterface.BUTTON_POSITIVE) {
if (togglePackageNameEnabled(packageName)) {
@@ -746,7 +786,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
final DialogFragment fragment = newErrorDialogFragment();
if (fragment == null || mFragmentManager == null) {
- return;
+ return activityResult;
}
fragment.show(mFragmentManager, ErrorDialogFragment.TAG);
@@ -758,9 +798,11 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
// If the dialog is being shown because of the intent we should
// return a result.
- if (activityResult == -1 || !setActivityResult) {
+ if (activityResult == -1 || !shouldSetActivityResult) {
setActivityResult(activityResult);
}
+
+ return activityResult;
}
private @Nullable ErrorDialogFragment newErrorDialogFragment() {
@@ -865,8 +907,18 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
- .setTitle(getContext().getString(R.string.credman_error_message_title))
- .setMessage(getContext().getString(R.string.credman_error_message))
+ .setTitle(
+ getContext()
+ .getString(
+ Flags.newSettingsUi()
+ ? R.string.credman_limit_error_msg_title
+ : R.string.credman_error_message_title))
+ .setMessage(
+ getContext()
+ .getString(
+ Flags.newSettingsUi()
+ ? R.string.credman_limit_error_msg
+ : R.string.credman_error_message))
.setPositiveButton(android.R.string.ok, this)
.create();
}
@@ -962,8 +1014,13 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
@Override
public void onClick(View buttonView) {
// Forward the event.
- if (mSwitch != null) {
- mOnClickListener.onCheckChanged(CombiPreference.this, mSwitch.isChecked());
+ if (mSwitch != null && mOnClickListener != null) {
+ if (!mOnClickListener.onCheckChanged(CombiPreference.this, mSwitch.isChecked())) {
+ // The update was not successful since there were too
+ // many enabled providers to manually reset any state.
+ mChecked = false;
+ mSwitch.setChecked(false);
+ }
}
}
}
@@ -977,7 +1034,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
public interface OnCombiPreferenceClickListener {
/** Called when the check is updated */
- void onCheckChanged(CombiPreference p, boolean isChecked);
+ boolean onCheckChanged(CombiPreference p, boolean isChecked);
/** Called when the left side is clicked. */
void onLeftSideClicked();
@@ -1002,6 +1059,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
}
}
+ @VisibleForTesting
public boolean isChecked() {
return mChecked;
}
diff --git a/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java
index 495c104d661b79b8ef613dc57a61b7a4408f658b..479a184752f44ad3fa2fba7651fb6ef9267662f7 100644
--- a/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java
+++ b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java
@@ -16,15 +16,53 @@
package com.android.settings.applications.credentials;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.SettingsActivity;
-/** Standalone activity used to launch a {@link DefaultCombinedPicker} fragment. */
+/**
+ * Standalone activity used to launch a {@link DefaultCombinedPicker} fragment if the user is a
+ * normal user, a {@link DefaultCombinedPickerWork} fragment if the user is a work profile or {@link
+ * DefaultCombinedPickerPrivate} fragment if the user is a private profile.
+ */
public class CredentialsPickerActivity extends SettingsActivity {
+ private static final String TAG = "CredentialsPickerActivity";
+
+ /** Injects the fragment name into the intent so the correct fragment is opened. */
+ @VisibleForTesting
+ public static void injectFragmentIntoIntent(Context context, Intent intent) {
+ final int userId = UserHandle.myUserId();
+ final UserManager userManager = UserManager.get(context);
+
+ if (DefaultCombinedPickerWork.isUserHandledByFragment(userManager, userId)) {
+ Slog.d(TAG, "Creating picker fragment using work profile");
+ intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPickerWork.class.getName());
+ } else if (DefaultCombinedPickerPrivate.isUserHandledByFragment(userManager)) {
+ Slog.d(TAG, "Creating picker fragment using private profile");
+ intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPickerPrivate.class.getName());
+ } else {
+ Slog.d(TAG, "Creating picker fragment using normal profile");
+ intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPicker.class.getName());
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ injectFragmentIntoIntent(this, getIntent());
+ super.onCreate(savedInstanceState);
+ }
@Override
protected boolean isValidFragment(String fragmentName) {
return super.isValidFragment(fragmentName)
- || DefaultCombinedPicker.class.getName().equals(fragmentName);
+ || DefaultCombinedPicker.class.getName().equals(fragmentName)
+ || DefaultCombinedPickerWork.class.getName().equals(fragmentName)
+ || DefaultCombinedPickerPrivate.class.getName().equals(fragmentName);
}
}
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
index a813ce4f5ddb46ee12e301f8e2a1dedc7cb0c5e8..d6f5289e07d53a3f7b0c59d9dbfcc483e076b341 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
@@ -16,7 +16,6 @@
package com.android.settings.applications.credentials;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -26,6 +25,7 @@ import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.SetEnabledProvidersException;
+import android.credentials.flags.Flags;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -38,6 +38,7 @@ import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
@@ -113,6 +114,18 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment {
setCancelListener(target.mCancelListener);
super.onCreate(savedInstanceState);
}
+
+ @Override
+ protected CharSequence getPositiveButtonText() {
+ final Bundle bundle = getArguments();
+ if (TextUtils.isEmpty(bundle.getString(EXTRA_KEY))) {
+ return getContext().getString(
+ R.string.credman_confirmation_turn_off_positive_button);
+ }
+
+ return getContext().getString(
+ R.string.credman_confirmation_change_provider_positive_button);
+ }
}
@Override
@@ -305,14 +318,21 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment {
protected CharSequence getConfirmationMessage(CandidateInfo appInfo) {
// If we are selecting none then show a warning label.
if (appInfo == null) {
- final String message = getContext().getString(R.string.credman_confirmation_message);
+ final String message =
+ getContext()
+ .getString(
+ Flags.newSettingsUi()
+ ? R.string.credman_confirmation_message_new_ui
+ : R.string.credman_confirmation_message);
return Html.fromHtml(message);
}
final CharSequence appName = appInfo.loadLabel();
final String message =
getContext()
.getString(
- R.string.credman_autofill_confirmation_message,
+ Flags.newSettingsUi()
+ ? R.string.credman_autofill_confirmation_message_new_ui
+ : R.string.credman_autofill_confirmation_message,
Html.escapeHtml(appName));
return Html.fromHtml(message);
}
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java b/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java
index 722cb1a13434600bbe80a588a3eb5f6ee0fe9758..8d8af0e385d9872edb0f6751d097c46106dc8944 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java
@@ -17,14 +17,29 @@
package com.android.settings.applications.credentials;
import android.os.UserManager;
+import android.util.Slog;
import com.android.settings.Utils;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType;
public class DefaultCombinedPickerPrivate extends DefaultCombinedPicker {
+ private static final String TAG = "DefaultCombinedPickerPrivate";
+
@Override
protected int getUser() {
UserManager userManager = getContext().getSystemService(UserManager.class);
return Utils.getCurrentUserIdOfType(userManager, ProfileType.PRIVATE);
}
+
+ /** Returns whether the user is handled by this fragment. */
+ public static boolean isUserHandledByFragment(UserManager userManager) {
+ try {
+ // If there is no private profile then this will throw an exception.
+ Utils.getCurrentUserIdOfType(userManager, ProfileType.PRIVATE);
+ return true;
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Failed to get private profile user id", e);
+ return false;
+ }
+ }
}
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPickerWork.java b/src/com/android/settings/applications/credentials/DefaultCombinedPickerWork.java
index 9808502b63edb0b5d4465831ac2c7a29584dce3e..945d6b8525438fa261e7de8f112aed46fe422784 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPickerWork.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPickerWork.java
@@ -19,13 +19,16 @@ package com.android.settings.applications.credentials;
import android.os.UserHandle;
import android.os.UserManager;
-import com.android.settings.Utils;
-
public class DefaultCombinedPickerWork extends DefaultCombinedPicker {
+ private static final String TAG = "DefaultCombinedPickerWork";
@Override
protected int getUser() {
- UserHandle workProfile = Utils.getManagedProfile(UserManager.get(getContext()));
- return workProfile.getIdentifier();
+ return UserHandle.myUserId();
+ }
+
+ /** Returns whether the user is handled by this fragment. */
+ public static boolean isUserHandledByFragment(UserManager userManager, int userId) {
+ return userManager.isManagedProfile(userId);
}
}
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
index 47a89ec649787ffe68106425b58329e51db523d9..032402fb157638e70343e2ec441e91c556e5f3dd 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
@@ -16,13 +16,11 @@
package com.android.settings.applications.credentials;
-import android.annotation.Nullable;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
+import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.autofill.AutofillService;
@@ -30,17 +28,21 @@ import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
import android.view.autofill.AutofillManager;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.Utils;
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.widget.TwoTargetPreference;
import java.util.ArrayList;
import java.util.List;
-public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController
- implements Preference.OnPreferenceClickListener {
+public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController {
private static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE);
private static final String TAG = "DefaultCombinedPreferenceController";
@@ -78,72 +80,80 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
// Despite this method being called getSettingIntent this intent actually
// opens the primary picker. This is so that we can swap the cog and the left
// hand side presses to align the UX.
- return new Intent(mContext, CredentialsPickerActivity.class);
+ if (PrimaryProviderPreference.shouldUseNewSettingsUi()) {
+ // We need to return an empty intent here since the class we inherit
+ // from will throw an NPE if we return null and we don't want it to
+ // open anything since we added the buttons.
+ return new Intent();
+ }
+ return createIntentToOpenPicker();
}
@Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
-
- final String prefKey = getPreferenceKey();
- final Preference preference = screen.findPreference(prefKey);
- if (preference != null) {
- preference.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) this);
+ public void updateState(@NonNull Preference preference) {
+ final CombinedProviderInfo topProvider = getTopProvider();
+ if (topProvider != null && mContext != null) {
+ updatePreferenceForProvider(
+ preference,
+ topProvider.getAppName(mContext),
+ topProvider.getSettingsSubtitle(),
+ topProvider.getAppIcon(mContext, getUser()),
+ topProvider.getPackageName(),
+ topProvider.getSettingsActivity());
+ } else {
+ updatePreferenceForProvider(preference, null, null, null, null, null);
}
}
- @Override
- public boolean onPreferenceClick(Preference preference) {
- // Get the selected provider.
- final CombinedProviderInfo topProvider = getTopProvider();
- if (topProvider == null) {
- return false;
+ @VisibleForTesting
+ public void updatePreferenceForProvider(
+ Preference preference,
+ @Nullable CharSequence appName,
+ @Nullable String appSubtitle,
+ @Nullable Drawable appIcon,
+ @Nullable CharSequence packageName,
+ @Nullable CharSequence settingsActivity) {
+ if (appName == null) {
+ preference.setTitle(R.string.credman_app_list_preference_none);
+ } else {
+ preference.setTitle(appName);
}
- // If the top provider has a defined Credential Manager settings
- // provider then we should open that up.
- final String settingsActivity = topProvider.getSettingsActivity();
- if (!TextUtils.isEmpty(settingsActivity)) {
- final Intent intent =
- new Intent(Intent.ACTION_MAIN)
- .setComponent(
- new ComponentName(
- topProvider.getPackageName(), settingsActivity));
- startActivity(intent);
- return true;
+ if (appIcon == null) {
+ preference.setIcon(null);
+ } else {
+ preference.setIcon(Utils.getSafeIcon(appIcon));
}
- return false;
+ preference.setSummary(appSubtitle);
+
+ if (preference instanceof PrimaryProviderPreference) {
+ PrimaryProviderPreference primaryPref = (PrimaryProviderPreference) preference;
+ primaryPref.setIconSize(TwoTargetPreference.ICON_SIZE_MEDIUM);
+ primaryPref.setDelegate(
+ new PrimaryProviderPreference.Delegate() {
+ public void onOpenButtonClicked() {
+ CombinedProviderInfo.launchSettingsActivityIntent(
+ mContext, packageName, settingsActivity, getUser());
+ }
+
+ public void onChangeButtonClicked() {
+ startActivity(createIntentToOpenPicker());
+ }
+ });
+
+ // Hide the open button if there is no defined settings activity.
+ primaryPref.setOpenButtonVisible(!TextUtils.isEmpty(settingsActivity));
+ primaryPref.setButtonsCompactMode(appName != null);
+ }
}
private @Nullable CombinedProviderInfo getTopProvider() {
- List providers = getAllProviders(getUser());
- return CombinedProviderInfo.getTopProvider(providers);
+ return CombinedProviderInfo.getTopProvider(getAllProviders(getUser()));
}
@Override
protected DefaultAppInfo getDefaultAppInfo() {
- CombinedProviderInfo topProvider = getTopProvider();
- if (topProvider != null) {
- ServiceInfo brandingService = topProvider.getBrandingService();
- if (brandingService == null) {
- return new DefaultAppInfo(
- mContext,
- mPackageManager,
- getUser(),
- topProvider.getApplicationInfo(),
- topProvider.getSettingsSubtitle(),
- true);
- } else {
- return new DefaultAppInfo(
- mContext,
- mPackageManager,
- getUser(),
- brandingService,
- topProvider.getSettingsSubtitle(),
- true);
- }
- }
return null;
}
@@ -180,4 +190,11 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
protected int getUser() {
return UserHandle.myUserId();
}
+
+ /** Creates an intent to open the credential picker. */
+ private Intent createIntentToOpenPicker() {
+ final Context context =
+ mContext.createContextAsUser(UserHandle.of(getUser()), /* flags= */ 0);
+ return new Intent(context, CredentialsPickerActivity.class);
+ }
}
diff --git a/src/com/android/settings/applications/credentials/ImageUtils.java b/src/com/android/settings/applications/credentials/ImageUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7803a8eeb850b37e7be1b64930f9ed076fac9e6
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/ImageUtils.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2024 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.applications.credentials;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.util.DisplayMetrics;
+
+import androidx.annotation.NonNull;
+
+/** Handles resizing of images for CredMan settings. */
+public class ImageUtils {
+
+ /**
+ * Utility class to resize icons to match default icon size. Code is mostly borrowed from
+ * Launcher and ActivityPicker.
+ */
+ public static class IconResizer {
+ private final int mIconWidth;
+ private final int mIconHeight;
+
+ private final DisplayMetrics mMetrics;
+ private final Rect mOldBounds = new Rect();
+ private final Canvas mCanvas = new Canvas();
+
+ public IconResizer(int width, int height, DisplayMetrics metrics) {
+ mCanvas.setDrawFilter(
+ new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG));
+
+ mMetrics = metrics;
+ mIconWidth = width;
+ mIconHeight = height;
+ }
+
+ /**
+ * Returns a Drawable representing the thumbnail of the specified Drawable. The size of the
+ * thumbnail is defined by the dimension android.R.dimen.app_icon_size.
+ *
+ * This method is not thread-safe and should be invoked on the UI thread only.
+ *
+ * @param icon The icon to get a thumbnail of.
+ * @return A thumbnail for the specified icon or the icon itself if the thumbnail could not
+ * be created.
+ */
+ public Drawable createIconThumbnail(Drawable icon) {
+ int width = mIconWidth;
+ int height = mIconHeight;
+
+ if (icon == null) {
+ return new EmptyDrawable(width, height);
+ }
+
+ try {
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(width);
+ painter.setIntrinsicHeight(height);
+ } else if (icon instanceof BitmapDrawable) {
+ // Ensure the bitmap has a density.
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(mMetrics);
+ }
+ }
+ int iconWidth = icon.getIntrinsicWidth();
+ int iconHeight = icon.getIntrinsicHeight();
+
+ if (iconWidth > 0 && iconHeight > 0) {
+ if (width < iconWidth || height < iconHeight) {
+ final float ratio = (float) iconWidth / iconHeight;
+
+ if (iconWidth > iconHeight) {
+ height = (int) (width / ratio);
+ } else if (iconHeight > iconWidth) {
+ width = (int) (height * ratio);
+ }
+
+ final Bitmap.Config c =
+ icon.getOpacity() != PixelFormat.OPAQUE
+ ? Bitmap.Config.ARGB_8888
+ : Bitmap.Config.RGB_565;
+ final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
+ final Canvas canvas = mCanvas;
+ canvas.setBitmap(thumb);
+
+ // Copy the old bounds to restore them later
+ // If we were to do oldBounds = icon.getBounds(),
+ // the call to setBounds() that follows would
+ // change the same instance and we would lose the
+ // old bounds.
+ mOldBounds.set(icon.getBounds());
+ final int x = (mIconWidth - width) / 2;
+ final int y = (mIconHeight - height) / 2;
+ icon.setBounds(x, y, x + width, y + height);
+ icon.draw(canvas);
+ icon.setBounds(mOldBounds);
+
+ // Create the new resized drawable.
+ icon = createBitmapDrawable(thumb);
+ } else if (iconWidth < width && iconHeight < height) {
+ final Bitmap.Config c = Bitmap.Config.ARGB_8888;
+ final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
+ final Canvas canvas = mCanvas;
+ canvas.setBitmap(thumb);
+ mOldBounds.set(icon.getBounds());
+
+ // Set the bounds for the new icon.
+ final int x = (width - iconWidth) / 2;
+ final int y = (height - iconHeight) / 2;
+ icon.setBounds(x, y, x + iconWidth, y + iconHeight);
+ icon.draw(canvas);
+ icon.setBounds(mOldBounds);
+
+ // Create the new resized drawable.
+ icon = createBitmapDrawable(thumb);
+ }
+ }
+
+ } catch (Throwable t) {
+ icon = new EmptyDrawable(width, height);
+ }
+
+ return icon;
+ }
+
+ private BitmapDrawable createBitmapDrawable(Bitmap thumb) {
+ BitmapDrawable icon = new BitmapDrawable(thumb);
+ icon.setTargetDensity(mMetrics);
+ mCanvas.setBitmap(null);
+ return icon;
+ }
+ }
+
+ public static class EmptyDrawable extends Drawable {
+ private final int mWidth;
+ private final int mHeight;
+
+ EmptyDrawable(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public int getMinimumWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getMinimumHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {}
+
+ @Override
+ public void setAlpha(int alpha) {}
+
+ @Override
+ public void setColorFilter(@NonNull ColorFilter cf) {}
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/credentials/PrimaryProviderPreference.java b/src/com/android/settings/applications/credentials/PrimaryProviderPreference.java
new file mode 100644
index 0000000000000000000000000000000000000000..84459e057ab70dc7ce313fa7be43462a8cb5d6c3
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/PrimaryProviderPreference.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 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.applications.credentials;
+
+import android.content.Context;
+import android.credentials.flags.Flags;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.widget.GearPreference;
+
+/**
+ * This preference is shown at the top of the "passwords & accounts" screen and allows the user to
+ * pick their primary credential manager provider.
+ */
+public class PrimaryProviderPreference extends GearPreference {
+
+ public static boolean shouldUseNewSettingsUi() {
+ return Flags.newSettingsUi();
+ }
+
+ private @Nullable Button mChangeButton = null;
+ private @Nullable Button mOpenButton = null;
+ private @Nullable View mButtonFrameView = null;
+ private @Nullable View mGearView = null;
+ private @Nullable Delegate mDelegate = null;
+ private boolean mButtonsCompactMode = false;
+ private boolean mOpenButtonVisible = false;
+
+ /** Called to send messages back to the parent controller. */
+ public static interface Delegate {
+ void onOpenButtonClicked();
+
+ void onChangeButtonClicked();
+ }
+
+ public PrimaryProviderPreference(
+ @NonNull Context context,
+ @NonNull AttributeSet attrs,
+ int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initializeNewSettingsUi();
+ }
+
+ public PrimaryProviderPreference(
+ @NonNull Context context,
+ @NonNull AttributeSet attrs) {
+ super(context, attrs);
+ initializeNewSettingsUi();
+ }
+
+ private void initializeNewSettingsUi() {
+ if (!shouldUseNewSettingsUi()) {
+ return;
+ }
+
+ // Change the layout to the new settings ui.
+ setLayoutResource(R.layout.preference_credential_manager_with_buttons);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ if (shouldUseNewSettingsUi()) {
+ onBindViewHolderNewSettingsUi(holder);
+ } else {
+ onBindViewHolderOldSettingsUi(holder);
+ }
+ }
+
+ private void onBindViewHolderOldSettingsUi(PreferenceViewHolder holder) {
+ setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ public boolean onPreferenceClick(@NonNull Preference preference) {
+ if (mDelegate != null) {
+ mDelegate.onOpenButtonClicked();
+ return true;
+ }
+
+ return false;
+ }
+ });
+
+ // Setup the gear icon to handle opening the change provider scenario.
+ mGearView = holder.findViewById(R.id.settings_button);
+ mGearView.setVisibility(View.VISIBLE);
+ mGearView.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(@NonNull View v) {
+ if (mDelegate != null) {
+ mDelegate.onChangeButtonClicked();
+ }
+ }
+ });
+ }
+
+ private void onBindViewHolderNewSettingsUi(PreferenceViewHolder holder) {
+ mOpenButton = (Button) holder.findViewById(R.id.open_button);
+ mOpenButton.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(@NonNull View v) {
+ if (mDelegate != null) {
+ mDelegate.onOpenButtonClicked();
+ }
+ }
+ });
+ setVisibility(mOpenButton, mOpenButtonVisible);
+
+ mChangeButton = (Button) holder.findViewById(R.id.change_button);
+ mChangeButton.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(@NonNull View v) {
+ if (mDelegate != null) {
+ mDelegate.onChangeButtonClicked();
+ }
+ }
+ });
+
+ mButtonFrameView = holder.findViewById(R.id.credman_button_frame);
+ updateButtonFramePadding();
+ }
+
+ public void setOpenButtonVisible(boolean isVisible) {
+ if (mOpenButton != null) {
+ mOpenButton.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ setVisibility(mOpenButton, isVisible);
+ }
+
+ mOpenButtonVisible = isVisible;
+ }
+
+ private void updateButtonFramePadding() {
+ if (mButtonFrameView == null) {
+ return;
+ }
+
+ int paddingLeft = mButtonsCompactMode ?
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.credman_primary_provider_pref_left_padding) :
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.credman_primary_provider_pref_left_padding_compact);
+
+ mButtonFrameView.setPadding(
+ paddingLeft,
+ mButtonFrameView.getPaddingTop(),
+ mButtonFrameView.getPaddingRight(),
+ mButtonFrameView.getPaddingBottom());
+ }
+
+ public void setButtonsCompactMode(boolean isCompactMode) {
+ mButtonsCompactMode = isCompactMode;
+ updateButtonFramePadding();
+ }
+
+ public void setDelegate(@NonNull Delegate delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ protected boolean shouldHideSecondTarget() {
+ return shouldUseNewSettingsUi();
+ }
+
+ @VisibleForTesting
+ public @Nullable Button getOpenButton() {
+ return mOpenButton;
+ }
+
+ @VisibleForTesting
+ public @Nullable Button getChangeButton() {
+ return mChangeButton;
+ }
+
+ @VisibleForTesting
+ public @Nullable View getButtonFrameView() {
+ return mButtonFrameView;
+ }
+
+ @VisibleForTesting
+ public @Nullable View getGearView() {
+ return mGearView;
+ }
+
+ private static void setVisibility(View view, boolean isVisible) {
+ view.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java b/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
index b089dd8384a92d5209d86cbf0906793a9e22f53b..fda6b281142ae720df77dc6ddececcd4515044db 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
@@ -138,11 +138,15 @@ public abstract class DefaultAppPickerFragment extends RadioButtonPickerFragment
final Bundle bundle = getArguments();
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.setMessage(bundle.getCharSequence(EXTRA_MESSAGE))
- .setPositiveButton(android.R.string.ok, this)
+ .setPositiveButton(getPositiveButtonText(), this)
.setNegativeButton(android.R.string.cancel, mCancelListener);
return builder.create();
}
+ protected CharSequence getPositiveButtonText() {
+ return getContext().getString(android.R.string.ok);
+ }
+
@Override
public void onClick(DialogInterface dialog, int which) {
final Fragment fragment = getTargetFragment();
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
index 30c797340256071ee62dd7aa0f9a3d71d15e022a..c31fb9e547b2b9931dac05125432889795225985 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
@@ -18,7 +18,6 @@ package com.android.settings.applications.defaultapps;
import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -28,6 +27,7 @@ import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.R;
diff --git a/src/com/android/settings/applications/defaultapps/SettingIntentProvider.java b/src/com/android/settings/applications/defaultapps/SettingIntentProvider.java
index a6e3edf78e9e2650ec674d9f4fab8a301dc29e36..c43fbd5c40beda2e1ade06b206e76671becce2b0 100644
--- a/src/com/android/settings/applications/defaultapps/SettingIntentProvider.java
+++ b/src/com/android/settings/applications/defaultapps/SettingIntentProvider.java
@@ -16,9 +16,10 @@
package com.android.settings.applications.defaultapps;
-import android.annotation.Nullable;
import android.content.Intent;
+import androidx.annotation.Nullable;
+
/**
* Provides an "advanced setting" intent for this app info.
*/
diff --git a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
index 72f754379aa16d59d312f445a1133f0181cf96f0..676c35a3226869e8b7a48b5cd5d274091331af47 100644
--- a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
+++ b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
@@ -288,6 +288,7 @@ public class AppLaunchSettings extends AppInfoBase implements
.create();
if (dialog.getListView() != null) {
dialog.getListView().setTextDirection(View.TEXT_DIRECTION_LOCALE);
+ dialog.getListView().setEnabled(false);
} else {
Log.w(TAG, "createVerifiedLinksDialog: dialog.getListView() is null, please check it.");
}
diff --git a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
index 1d96688c1684f56037e7f962ae6ece5d9799e76f..cd3fd6bdb2cb0ea6a30cfb022f49e65e116eb903 100644
--- a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
+++ b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
@@ -231,7 +231,7 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
void updateAppCloneWidget(Context context, View.OnClickListener onClickListener,
AppEntry entry) {
if (mAddIcon != null) {
- if (!entry.isCloned) {
+ if (!entry.isClonedProfile()) {
mAddIcon.setBackground(context.getDrawable(R.drawable.ic_add_24dp));
} else {
mAddIcon.setBackground(context.getDrawable(R.drawable.ic_trash_can));
@@ -254,7 +254,7 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
String packageName = entry.info.packageName;
if (mWidgetContainer != null) {
- if (!entry.isCloned) {
+ if (!entry.isClonedProfile()) {
metricsFeatureProvider.action(context,
SettingsEnums.ACTION_CREATE_CLONE_APP);
mAddIcon.setVisibility(View.INVISIBLE);
@@ -285,7 +285,7 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
}
}.execute();
- } else if (entry.isCloned) {
+ } else if (entry.isClonedProfile()) {
metricsFeatureProvider.action(context,
SettingsEnums.ACTION_DELETE_CLONE_APP);
cloneBackend.uninstallClonedApp(packageName, /*allUsers*/ false,
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index e370f3eef64f900009ba2c1f3c3ba9fdd508663a..c2fabff680dd19dbbba9a7c3d62483195f00d8a6 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -295,6 +295,7 @@ public class ManageApplications extends InstrumentedFragment
private String mVolumeUuid;
private int mStorageType;
private boolean mIsWorkOnly;
+ private boolean mIsPrivateProfileOnly;
private int mWorkUserId;
private boolean mIsPersonalOnly;
private View mEmptyView;
@@ -378,6 +379,8 @@ public class ManageApplications extends InstrumentedFragment
== ProfileSelectFragment.ProfileType.PERSONAL;
mIsWorkOnly = args != null && args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
== ProfileSelectFragment.ProfileType.WORK;
+ mIsPrivateProfileOnly = args != null && args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
+ == ProfileSelectFragment.ProfileType.PRIVATE;
mWorkUserId = args != null ? args.getInt(EXTRA_WORK_ID) : UserHandle.myUserId();
if (mIsWorkOnly && mWorkUserId == UserHandle.myUserId()) {
mWorkUserId = Utils.getManagedProfileId(mUserManager, UserHandle.myUserId());
@@ -660,6 +663,10 @@ public class ManageApplications extends InstrumentedFragment
if (mIsWorkOnly) {
compositeFilter = new CompoundFilter(compositeFilter, ApplicationsState.FILTER_WORK);
}
+ if (mIsPrivateProfileOnly) {
+ compositeFilter =
+ new CompoundFilter(compositeFilter, ApplicationsState.FILTER_PRIVATE_PROFILE);
+ }
if (mIsPersonalOnly) {
compositeFilter = new CompoundFilter(compositeFilter,
ApplicationsState.FILTER_PERSONAL);
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
index 82e987e3f6fadc28a341bbf82f701a0a22e5a647..dca115b97c0d344e6f41248523352da1a92c01d9 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
+++ b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
@@ -59,6 +59,7 @@ import com.android.settings.applications.manageapplications.ManageApplications.L
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WIFI_ACCESS
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WRITE_SETTINGS
import com.android.settings.spa.app.AllAppListPageProvider
+import com.android.settings.spa.app.battery.BatteryOptimizationModeAppListPageProvider
import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
@@ -70,7 +71,6 @@ import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListPro
import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
import com.android.settings.spa.app.specialaccess.TurnScreenOnAppsAppListProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
-import com.android.settings.spa.app.storage.StorageAppListPageProvider
import com.android.settings.spa.notification.AppListNotificationsPageProvider
import com.android.settings.spa.system.AppLanguagesPageProvider
@@ -127,6 +127,7 @@ object ManageApplicationsUtil {
// TODO(b/292165031) enable once sorting is supported
//LIST_TYPE_STORAGE -> StorageAppListPageProvider.Apps.name
//LIST_TYPE_GAMES -> StorageAppListPageProvider.Games.name
+ LIST_TYPE_BATTERY_OPTIMIZATION -> BatteryOptimizationModeAppListPageProvider.name
else -> null
}
}
diff --git a/src/com/android/settings/applications/specialaccess/DefaultPaymentSettingsPreferenceController.java b/src/com/android/settings/applications/specialaccess/DefaultPaymentSettingsPreferenceController.java
index 763a50019426e8a252b7cebf2125c6ae98006b83..c66c97e2e591121c2d994fe96d89e3f5891913fe 100644
--- a/src/com/android/settings/applications/specialaccess/DefaultPaymentSettingsPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/DefaultPaymentSettingsPreferenceController.java
@@ -16,10 +16,14 @@
package com.android.settings.applications.specialaccess;
+import android.app.role.RoleManager;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
import android.os.UserManager;
+import android.permission.flags.Flags;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -61,6 +65,20 @@ public class DefaultPaymentSettingsPreferenceController extends BasePreferenceCo
mPaymentSettingsEnabler = new PaymentSettingsEnabler(mContext, preference);
}
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (Flags.walletRoleEnabled()
+ && mPreferenceKey.equals(preference.getKey())) {
+ RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+ if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
+ Intent intent = new Intent(CardEmulation.ACTION_CHANGE_DEFAULT);
+ mContext.startActivity(intent);
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void onResume() {
if (mPaymentSettingsEnabler != null) {
diff --git a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListItem.java b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListItem.java
index 370a4dfe12ff52f17535de638178f9f786dbf5dc..f7dff2e2d7bc95739582f9fba602255c1219c405 100644
--- a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListItem.java
+++ b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListItem.java
@@ -86,6 +86,14 @@ class DeviceAdminListItem implements Comparable {
return new UserHandle(getUserIdFromDeviceAdminInfo(mInfo));
}
+ public int getUid() {
+ return mInfo.getActivityInfo().applicationInfo.uid;
+ }
+
+ public String getPackageName() {
+ return mInfo.getPackageName();
+ }
+
public Intent getLaunchIntent(Context context) {
return new Intent(context, DeviceAdminAdd.class)
.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mInfo.getComponent());
diff --git a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
index 1184d8e41c55359986037e2180464ad3af75c11d..640f21dec0e54a5dea195b5d669d9556038d1e40 100644
--- a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
@@ -18,6 +18,7 @@ package com.android.settings.applications.specialaccess.deviceadmin;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import android.Manifest;
import android.app.AppGlobals;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DeviceAdminReceiver;
@@ -31,6 +32,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserProperties;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -45,12 +47,13 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
-import com.android.settingslib.widget.AppSwitchPreference;
import com.android.settingslib.widget.FooterPreference;
+import com.android.settingslib.widget.TwoTargetPreference;
import org.xmlpull.v1.XmlPullParserException;
@@ -154,12 +157,23 @@ public class DeviceAdminListPreferenceController extends BasePreferenceControlle
mAdmins.clear();
final List profiles = mUm.getUserProfiles();
for (UserHandle profile : profiles) {
+ if (shouldSkipProfile(profile)) {
+ continue;
+ }
final int profileId = profile.getIdentifier();
updateAvailableAdminsForProfile(profileId);
}
Collections.sort(mAdmins);
}
+ private boolean shouldSkipProfile(UserHandle profile) {
+ return android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
+ && mUm.isQuietModeEnabled(profile)
+ && mUm.getUserProperties(profile).getShowInQuietMode()
+ == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN;
+ }
+
private void refreshUI() {
if (mPreferenceGroup == null) {
return;
@@ -167,35 +181,35 @@ public class DeviceAdminListPreferenceController extends BasePreferenceControlle
if (mFooterPreference != null) {
mFooterPreference.setVisible(mAdmins.isEmpty());
}
- final Map preferenceCache = new ArrayMap<>();
+ final Map preferenceCache = new ArrayMap<>();
final Context prefContext = mPreferenceGroup.getContext();
final int childrenCount = mPreferenceGroup.getPreferenceCount();
for (int i = 0; i < childrenCount; i++) {
final Preference pref = mPreferenceGroup.getPreference(i);
- if (!(pref instanceof AppSwitchPreference)) {
+ if (!(pref instanceof RestrictedSwitchPreference switchPref)) {
continue;
}
- final AppSwitchPreference appSwitch = (AppSwitchPreference) pref;
- preferenceCache.put(appSwitch.getKey(), appSwitch);
+ preferenceCache.put(switchPref.getKey(), switchPref);
}
for (DeviceAdminListItem item : mAdmins) {
final String key = item.getKey();
- AppSwitchPreference pref = preferenceCache.remove(key);
+ RestrictedSwitchPreference pref = preferenceCache.remove(key);
if (pref == null) {
- pref = new AppSwitchPreference(prefContext);
+ pref = new RestrictedSwitchPreference(prefContext);
mPreferenceGroup.addPreference(pref);
}
bindPreference(item, pref);
}
- for (AppSwitchPreference unusedCacheItem : preferenceCache.values()) {
+ for (RestrictedSwitchPreference unusedCacheItem : preferenceCache.values()) {
mPreferenceGroup.removePreference(unusedCacheItem);
}
}
- private void bindPreference(DeviceAdminListItem item, AppSwitchPreference pref) {
+ private void bindPreference(DeviceAdminListItem item, RestrictedSwitchPreference pref) {
pref.setKey(item.getKey());
pref.setTitle(item.getName());
pref.setIcon(item.getIcon());
+ pref.setIconSize(TwoTargetPreference.ICON_SIZE_DEFAULT);
pref.setChecked(item.isActive());
pref.setSummary(item.getDescription());
pref.setEnabled(item.isEnabled());
@@ -207,6 +221,8 @@ public class DeviceAdminListPreferenceController extends BasePreferenceControlle
});
pref.setOnPreferenceChangeListener((preference, newValue) -> false);
pref.setSingleLineTitle(true);
+ pref.checkEcmRestrictionAndSetDisabled(Manifest.permission.BIND_DEVICE_ADMIN,
+ item.getPackageName());
}
/**
diff --git a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettings.java b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettings.java
index 8169072d868801a81ab37522b80bda61d3437a6f..432b42384abf0629ae3c2ed4d4a33e1b9cdc246b 100644
--- a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettings.java
+++ b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettings.java
@@ -17,7 +17,6 @@ package com.android.settings.applications.specialaccess.interactacrossprofiles;
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_WORK_AND_PERSONAL_APPS_TITLE;
-import android.annotation.Nullable;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -32,6 +31,7 @@ import android.util.IconDrawableFactory;
import android.util.Pair;
import android.view.View;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceScreen;
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
index fb78e3eb077542e276350669260bb36bd7a9865f..70a31b8e8f0bab5d2fbea3b232c2839565956b68 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
@@ -24,6 +24,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
@@ -42,7 +43,7 @@ public class ApprovalPreferenceController extends BasePreferenceController {
private NotificationManager mNm;
private PackageManager mPm;
// The appOp representing this preference
- private String mAppOpStr;
+ private String mSettingIdentifier;
public ApprovalPreferenceController(Context context, String key) {
super(context, key);
@@ -76,8 +77,9 @@ public class ApprovalPreferenceController extends BasePreferenceController {
/**
* Set the associated appOp for the Setting
*/
- public ApprovalPreferenceController setAppOpStr(String appOpStr) {
- mAppOpStr = appOpStr;
+ @NonNull
+ public ApprovalPreferenceController setSettingIdentifier(@NonNull String settingIdentifier) {
+ mSettingIdentifier = settingIdentifier;
return this;
}
@@ -118,14 +120,15 @@ public class ApprovalPreferenceController extends BasePreferenceController {
}
});
- if (android.security.Flags.extendEcmToAllSettings()) {
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ && android.security.Flags.extendEcmToAllSettings()) {
if (!isAllowedCn && !isEnabled) {
preference.setEnabled(false);
} else if (isEnabled) {
preference.setEnabled(true);
} else {
- preference.checkEcmRestrictionAndSetDisabled(mAppOpStr,
- mCn.getPackageName(), mPkgInfo.applicationInfo.uid);
+ preference.checkEcmRestrictionAndSetDisabled(mSettingIdentifier,
+ mCn.getPackageName());
}
} else {
preference.updateState(
@@ -139,7 +142,11 @@ public class ApprovalPreferenceController extends BasePreferenceController {
AsyncTask.execute(() -> {
if (!mNm.isNotificationPolicyAccessGrantedForPackage(
cn.getPackageName())) {
- mNm.removeAutomaticZenRules(cn.getPackageName());
+ if (android.app.Flags.modesApi()) {
+ mNm.removeAutomaticZenRules(cn.getPackageName(), /* fromUser= */ true);
+ } else {
+ mNm.removeAutomaticZenRules(cn.getPackageName());
+ }
}
});
}
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
index 89767ddb011cba74292706cd90d76dfdb1d9e2ad..e885d5c3d172f089e25c44a00c9b2645b15fada8 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
@@ -103,7 +103,7 @@ public class NotificationAccessDetails extends DashboardFragment {
.setCn(mComponentName)
.setNm(context.getSystemService(NotificationManager.class))
.setPm(mPm)
- .setAppOpStr(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS)
+ .setSettingIdentifier(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS)
.setParent(this);
use(HeaderPreferenceController.class)
.setFragment(this)
diff --git a/src/com/android/settings/applications/specialaccess/pictureinpicture/PictureInPictureSettings.java b/src/com/android/settings/applications/specialaccess/pictureinpicture/PictureInPictureSettings.java
index 3c90bf358c0add507a840da658d3d10ccc03706b..f19b68a6cae74f15dab8c82a99c518be2e3cfc8c 100644
--- a/src/com/android/settings/applications/specialaccess/pictureinpicture/PictureInPictureSettings.java
+++ b/src/com/android/settings/applications/specialaccess/pictureinpicture/PictureInPictureSettings.java
@@ -17,7 +17,6 @@ package com.android.settings.applications.specialaccess.pictureinpicture;
import static android.content.pm.PackageManager.GET_ACTIVITIES;
-import android.annotation.Nullable;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -32,6 +31,7 @@ import android.util.IconDrawableFactory;
import android.util.Pair;
import android.view.View;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
diff --git a/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java b/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java
index c186e07a327b7cdb7f7892fdf56f6246aa8cfbdc..2a5f1dbcf045bd2a3b7d852ea84c3662b2f51ad5 100644
--- a/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java
+++ b/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java
@@ -16,7 +16,6 @@
package com.android.settings.applications.specialaccess.premiumsms;
-import android.annotation.Nullable;
import android.app.Application;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -24,8 +23,8 @@ import android.os.Bundle;
import android.telephony.SmsManager;
import android.view.View;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import androidx.preference.DropDownPreference;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceScreen;
@@ -38,6 +37,7 @@ import com.android.settings.applications.AppStateSmsPremBridge.SmsState;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.EmptyTextSettings;
+import com.android.settingslib.RestrictedDropDownPreference;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.Callbacks;
@@ -52,6 +52,8 @@ import java.util.ArrayList;
public class PremiumSmsAccess extends EmptyTextSettings
implements Callback, Callbacks, OnPreferenceChangeListener {
+ private static final String ECM_RESTRICTION_KEY = "android:premium_sms_access";
+
private ApplicationsState mApplicationsState;
private AppStateSmsPremBridge mSmsBackend;
private Session mSession;
@@ -205,7 +207,7 @@ public class PremiumSmsAccess extends EmptyTextSettings
}
- private class PremiumSmsPreference extends DropDownPreference {
+ private class PremiumSmsPreference extends RestrictedDropDownPreference {
private final AppEntry mAppEntry;
public PremiumSmsPreference(AppEntry appEntry, Context context) {
@@ -224,6 +226,7 @@ public class PremiumSmsAccess extends EmptyTextSettings
});
setValue(String.valueOf(getCurrentValue()));
setSummary("%s");
+ this.checkEcmRestrictionAndSetDisabled(ECM_RESTRICTION_KEY, appEntry.info.packageName);
}
private int getCurrentValue() {
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
index b4a0c88d950eab9d899164ee2e2e8ec630c054d8..6f4137c224a948519656eaf8a1df9987963ae7f5 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
@@ -101,8 +101,12 @@ public class ZenAccessController extends BasePreferenceController {
}
public static void deleteRules(final Context context, final String pkg) {
- final NotificationManager mgr = context.getSystemService(NotificationManager.class);
- mgr.removeAutomaticZenRules(pkg);
+ final NotificationManager mgr = context.getSystemService(NotificationManager.class);
+ if (android.app.Flags.modesApi()) {
+ mgr.removeAutomaticZenRules(pkg, /* fromUser= */ true);
+ } else {
+ mgr.removeAutomaticZenRules(pkg);
+ }
}
@VisibleForTesting
diff --git a/src/com/android/settings/backup/OWNERS b/src/com/android/settings/backup/OWNERS
index 0f888113d7309156abcfed30e07be06c9b76079c..9b033ca5e990f19b37a1a2b1a68354ef4505d470 100644
--- a/src/com/android/settings/backup/OWNERS
+++ b/src/com/android/settings/backup/OWNERS
@@ -2,3 +2,5 @@
include platform/frameworks/base:/services/backup/OWNERS
+# Android Settings Core
+jiannan@google.com
diff --git a/src/com/android/settings/backup/SettingsBackupHelper.java b/src/com/android/settings/backup/SettingsBackupHelper.java
index 04935a7bf0b32cea7a8f28f8c6b5fb9503eb0857..0861af29d6c09bad6d0e845117d7dcd135ad1af4 100644
--- a/src/com/android/settings/backup/SettingsBackupHelper.java
+++ b/src/com/android/settings/backup/SettingsBackupHelper.java
@@ -19,25 +19,15 @@ package com.android.settings.backup;
import static com.android.settings.localepicker.LocaleNotificationDataManager.LOCALE_NOTIFICATION;
import android.app.backup.BackupAgentHelper;
-import android.app.backup.BackupDataInputStream;
-import android.app.backup.BackupDataOutput;
-import android.app.backup.BackupHelper;
import android.app.backup.SharedPreferencesBackupHelper;
-import android.os.ParcelFileDescriptor;
-import com.android.settings.fuelgauge.BatteryBackupHelper;
+import com.android.settings.flags.Flags;
import com.android.settings.onboarding.OnboardingFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.shortcut.CreateShortcutPreferenceController;
+import com.android.settingslib.datastore.BackupRestoreStorageManager;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import com.android.settings.flags.Flags;
-
-/**
- * Backup agent for Settings APK
- */
+/** Backup agent for Settings APK */
public class SettingsBackupHelper extends BackupAgentHelper {
private static final String PREF_LOCALE_NOTIFICATION = "localeNotificationSharedPref";
public static final String SOUND_BACKUP_HELPER = "SoundSettingsBackup";
@@ -45,8 +35,7 @@ public class SettingsBackupHelper extends BackupAgentHelper {
@Override
public void onCreate() {
super.onCreate();
- addHelper("no-op", new NoOpHelper());
- addHelper(BatteryBackupHelper.TAG, new BatteryBackupHelper(this));
+ BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
addHelper(PREF_LOCALE_NOTIFICATION,
new SharedPreferencesBackupHelper(this, LOCALE_NOTIFICATION));
if (Flags.enableSoundBackup()) {
@@ -62,48 +51,7 @@ public class SettingsBackupHelper extends BackupAgentHelper {
@Override
public void onRestoreFinished() {
super.onRestoreFinished();
+ BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
CreateShortcutPreferenceController.updateRestoredShortcuts(this);
}
-
- /**
- * Backup helper which does not do anything. Having at least one helper ensures that the
- * transport is not empty and onRestoreFinished is called eventually.
- */
- private static class NoOpHelper implements BackupHelper {
-
- private final int VERSION_CODE = 1;
-
- @Override
- public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) {
-
- try (FileOutputStream out = new FileOutputStream(newState.getFileDescriptor())) {
- if (getVersionCode(oldState) != VERSION_CODE) {
- data.writeEntityHeader("placeholder", 1);
- data.writeEntityData(new byte[1], 1);
- }
-
- // Write new version code
- out.write(VERSION_CODE);
- out.flush();
- } catch (IOException e) { }
- }
-
- @Override
- public void restoreEntity(BackupDataInputStream data) { }
-
- @Override
- public void writeNewStateDescription(ParcelFileDescriptor newState) { }
-
- private int getVersionCode(ParcelFileDescriptor state) {
- if (state == null) {
- return 0;
- }
- try (FileInputStream in = new FileInputStream(state.getFileDescriptor())) {
- return in.read();
- } catch (IOException e) {
- return 0;
- }
- }
- }
}
diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
index 40763e3934e21cedd0f0915bddda03b0cd879310..73e1af1bc8aa032ddc3df4deea439f8832830076 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
@@ -24,7 +24,6 @@ import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT
import static com.google.android.setupdesign.transition.TransitionHelper.TRANSITION_FADE_THROUGH;
-import android.annotation.NonNull;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
@@ -45,6 +44,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.util.FrameworkStatsLog;
diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java
index c9c8cff034c00f95af31be83a7ced1a278f188f5..335d0b9dd99edd8a984d439f5b41188cbcae925d 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollBase.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java
@@ -18,7 +18,6 @@ package com.android.settings.biometrics;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.res.ColorStateList;
@@ -32,6 +31,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
@@ -40,6 +40,7 @@ import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
import com.android.settings.core.InstrumentedActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settingslib.activityembedding.ActivityEmbeddingUtils;
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
import com.android.systemui.unfold.updates.FoldProvider;
@@ -173,6 +174,14 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
mPostureGuidanceIntent = FeatureFactory.getFeatureFactory()
.getFaceFeatureProvider().getPostureGuidanceIntent(getApplicationContext());
+
+ // Remove the existing split screen dialog.
+ BiometricsSplitScreenDialog dialog =
+ (BiometricsSplitScreenDialog) getSupportFragmentManager()
+ .findFragmentByTag(BiometricsSplitScreenDialog.class.getName());
+ if (dialog != null) {
+ getSupportFragmentManager().beginTransaction().remove(dialog).commit();
+ }
}
@Override
@@ -338,4 +347,8 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
final ColorStateList stateList = Utils.getColorAttr(this, android.R.attr.windowBackground);
return stateList != null ? stateList.getDefaultColor() : Color.TRANSPARENT;
}
+
+ protected boolean shouldShowSplitScreenDialog() {
+ return isInMultiWindowMode() && !ActivityEmbeddingUtils.isActivityEmbedded(this);
+ }
}
diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
index 44b1b3b17e563321ffca34a0504c2d9bb08ac132..1b9a70f5d465c3126fcbcab9af0b5d91efb1181a 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
@@ -154,6 +154,12 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (shouldShowSplitScreenDialog()) {
+ BiometricsSplitScreenDialog
+ .newInstance(getModality(), !WizardManagerHelper.isAnySetupWizard(getIntent()))
+ .show(getSupportFragmentManager(), BiometricsSplitScreenDialog.class.getName());
+ }
+
if (savedInstanceState != null) {
mConfirmingCredentials = savedInstanceState.getBoolean(KEY_CONFIRMING_CREDENTIALS);
mHasScrolledToBottom = savedInstanceState.getBoolean(KEY_SCROLLED_TO_BOTTOM);
@@ -293,6 +299,13 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
@Override
protected void onNextButtonClick(View view) {
+ // If it's not on suw, this method shouldn't be accessed.
+ if (shouldShowSplitScreenDialog() && WizardManagerHelper.isAnySetupWizard(getIntent())) {
+ BiometricsSplitScreenDialog.newInstance(getModality(), false /*destroyActivity*/)
+ .show(getSupportFragmentManager(), BiometricsSplitScreenDialog.class.getName());
+ return;
+ }
+
mNextClicked = true;
if (checkMaxEnrolled() == 0) {
// Lock thingy is already set up, launch directly to the next page
diff --git a/src/com/android/settings/biometrics/BiometricEnrollSidecar.java b/src/com/android/settings/biometrics/BiometricEnrollSidecar.java
index 369fa4b4311317cbad93f1f18c08d4d8a4ee6e97..78f6087218610f6e5f0d441e111d44103140cd09 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollSidecar.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollSidecar.java
@@ -16,7 +16,6 @@
package com.android.settings.biometrics;
-import android.annotation.Nullable;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -24,6 +23,8 @@ import android.os.CancellationSignal;
import android.os.Handler;
import android.os.UserHandle;
+import androidx.annotation.Nullable;
+
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java
index 53892bd9a1cb547c9258c72e8c7531773a5eae0e..3376d86c115a3e96ec2ed0dbbb0f03703575412f 100644
--- a/src/com/android/settings/biometrics/BiometricUtils.java
+++ b/src/com/android/settings/biometrics/BiometricUtils.java
@@ -25,6 +25,7 @@ import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -65,6 +66,7 @@ import java.lang.annotation.RetentionPolicy;
*/
public class BiometricUtils {
private static final String TAG = "BiometricUtils";
+ public static final String EXTRA_ENROLL_REASON = BiometricManager.EXTRA_ENROLL_REASON;
/** The character ' • ' to separate the setup choose options */
public static final String SEPARATOR = " \u2022 ";
diff --git a/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java b/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java
index 7051b70ee92eb0eee273ed75f579c9f5d5ad14c3..1e1a1423dfe8daffe35ff840d9313a04666d9037 100644
--- a/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java
@@ -16,11 +16,12 @@
package com.android.settings.biometrics;
-import android.annotation.Nullable;
import android.content.Intent;
import android.os.UserHandle;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.settings.password.ChooseLockSettingsHelper;
/**
@@ -96,6 +97,14 @@ public abstract class BiometricsEnrollEnrolling extends BiometricEnrollBase
}
public void startEnrollment() {
+ // If it's in multi window mode, dialog is shown, do not start enrollment.
+ if (shouldShowSplitScreenDialog()) {
+ return;
+ }
+ startEnrollmentInternal();
+ }
+
+ protected void startEnrollmentInternal() {
mSidecar = (BiometricEnrollSidecar) getSupportFragmentManager()
.findFragmentByTag(TAG_SIDECAR);
if (mSidecar == null) {
diff --git a/src/com/android/settings/biometrics/BiometricsSplitScreenDialog.java b/src/com/android/settings/biometrics/BiometricsSplitScreenDialog.java
index c1ecee8a8520014091901431965dcdd9e506d30c..79feb0b570e44bf2a761fafe21c262705f57429a 100644
--- a/src/com/android/settings/biometrics/BiometricsSplitScreenDialog.java
+++ b/src/com/android/settings/biometrics/BiometricsSplitScreenDialog.java
@@ -18,6 +18,8 @@ package com.android.settings.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_SKIP;
+
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.DialogInterface;
@@ -34,23 +36,33 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
*/
public class BiometricsSplitScreenDialog extends InstrumentedDialogFragment {
private static final String KEY_BIOMETRICS_MODALITY = "biometrics_modality";
+ private static final String KEU_DESTROY_ACTIVITY = "destroy_activity";
@BiometricAuthenticator.Modality
private int mBiometricsModality;
+ private boolean mDestroyActivity;
- /** Returns the new instance of the class */
+ /**
+ * Returns the new instance of the class
+ * @param biometricsModality Biometric modality.
+ * @param destroyActivity Whether to destroy the activity
+ * @return the current {@link BiometricsSplitScreenDialog}
+ */
public static BiometricsSplitScreenDialog newInstance(
- @BiometricAuthenticator.Modality int biometricsModality) {
+ @BiometricAuthenticator.Modality int biometricsModality, boolean destroyActivity) {
final BiometricsSplitScreenDialog dialog = new BiometricsSplitScreenDialog();
final Bundle args = new Bundle();
args.putInt(KEY_BIOMETRICS_MODALITY, biometricsModality);
+ args.putBoolean(KEU_DESTROY_ACTIVITY, destroyActivity);
dialog.setArguments(args);
+ dialog.setCancelable(false);
return dialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mBiometricsModality = getArguments().getInt(KEY_BIOMETRICS_MODALITY);
+ mDestroyActivity = getArguments().getBoolean(KEU_DESTROY_ACTIVITY);
int titleId;
int messageId;
switch (mBiometricsModality) {
@@ -65,9 +77,16 @@ public class BiometricsSplitScreenDialog extends InstrumentedDialogFragment {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(titleId)
.setMessage(messageId)
+ .setCancelable(false)
.setPositiveButton(
R.string.biometric_settings_add_biometrics_in_split_mode_ok,
- (DialogInterface.OnClickListener) (dialog, which) -> dialog.dismiss());
+ (DialogInterface.OnClickListener) (dialog, which) -> {
+ dialog.dismiss();
+ if (mDestroyActivity) {
+ getActivity().setResult(RESULT_SKIP);
+ getActivity().finish();
+ }
+ });
return builder.create();
}
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index d8d34844948f4624a827c83715b34d56938cae89..b17478881fe006178d060281c79553567d5868a6 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -16,8 +16,6 @@
package com.android.settings.biometrics.combination;
import static android.app.Activity.RESULT_OK;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static com.android.settings.password.ChooseLockPattern.RESULT_FINISHED;
@@ -48,12 +46,10 @@ import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricStatusPreferenceController;
import com.android.settings.biometrics.BiometricUtils;
-import com.android.settings.biometrics.BiometricsSplitScreenDialog;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
-import com.android.settingslib.activityembedding.ActivityEmbeddingUtils;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.transition.SettingsTransitionHelper;
@@ -167,18 +163,6 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
// since FingerprintSettings and FaceSettings revoke the challenge when finishing.
if (getFacePreferenceKey().equals(key)) {
mDoNotFinishActivity = true;
-
- // If it's split mode and there is no enrolled face, show the dialog. (if there is
- // enrolled face, FaceSettingsEnrollButtonPreferenceController#onClick will handle
- // the dialog)
- if (getActivity().isInMultiWindowMode() && !ActivityEmbeddingUtils.isActivityEmbedded(
- getActivity()) && !mFaceManager.hasEnrolledTemplates(mUserId)) {
- BiometricsSplitScreenDialog.newInstance(TYPE_FACE).show(
- getActivity().getSupportFragmentManager(),
- BiometricsSplitScreenDialog.class.getName());
- return true;
- }
-
mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
final Activity activity = getActivity();
if (activity == null || activity.isFinishing()) {
@@ -209,18 +193,6 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
return true;
} else if (getFingerprintPreferenceKey().equals(key)) {
mDoNotFinishActivity = true;
-
- // If it's split mode and there is no enrolled fingerprint, show the dialog. (if
- // there is enrolled fingerprint, FingerprintSettingsFragment#onPreferenceTreeClick
- // will handle the dialog)
- if (getActivity().isInMultiWindowMode() && !ActivityEmbeddingUtils.isActivityEmbedded(
- getActivity()) && !mFingerprintManager.hasEnrolledFingerprints(mUserId)) {
- BiometricsSplitScreenDialog.newInstance(TYPE_FINGERPRINT).show(
- getActivity().getSupportFragmentManager(),
- BiometricsSplitScreenDialog.class.getName());
- return true;
- }
-
mFingerprintManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
final Activity activity = getActivity();
if (activity == null || activity.isFinishing()) {
diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java
index 8cc6bc46d40a2202bfb462e5adc11c5bd3d58d0f..2cd239e2fcf1836292628aa1da30a94f01dedba7 100644
--- a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java
+++ b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java
@@ -159,4 +159,12 @@ public class CombinedBiometricStatusUtils {
public String getProfileSettingsClassName() {
return Settings.CombinedBiometricProfileSettingsActivity.class.getName();
}
+
+ /**
+ * Returns the class name of the Settings page corresponding to combined biometric settings for
+ * Private profile.
+ */
+ public String getPrivateProfileSettingsClassName() {
+ return Settings.PrivateSpaceBiometricSettingsActivity.class.getName();
+ }
}
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
index 62e9757b365619757f74e8017948d38695ac348f..6862bc921d790b91320037e606934e6f8030b958 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
@@ -265,6 +265,8 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
}
intent.putExtra(EXTRA_KEY_REQUIRE_DIVERSITY, !mSwitchDiversity.isChecked());
+ intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON,
+ getIntent().getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1));
if (!mSwitchDiversity.isChecked() && mAccessibilityEnabled) {
FaceEnrollAccessibilityDialog dialog = FaceEnrollAccessibilityDialog.newInstance();
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
index 472410bdcf36687c7594e936e450662dfd803684..da3689a6b95671d9b1a3313e1059d0d0f383ad1c 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
@@ -16,6 +16,8 @@
package com.android.settings.biometrics.face;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.hardware.face.FaceManager;
@@ -33,6 +35,7 @@ import com.android.settings.biometrics.BiometricEnrollSidecar;
import com.android.settings.biometrics.BiometricErrorDialog;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.BiometricsEnrollEnrolling;
+import com.android.settings.biometrics.BiometricsSplitScreenDialog;
import com.android.settings.slices.CustomSliceRegistry;
import com.google.android.setupcompat.template.FooterBarMixin;
@@ -88,6 +91,10 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (shouldShowSplitScreenDialog()) {
+ BiometricsSplitScreenDialog.newInstance(TYPE_FACE, true /*destroyActivity*/)
+ .show(getSupportFragmentManager(), BiometricsSplitScreenDialog.class.getName());
+ }
setContentView(R.layout.face_enroll_enrolling);
setHeaderText(R.string.security_settings_face_enroll_repeat_title);
mErrorText = findViewById(R.id.error_text);
@@ -134,7 +141,7 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling {
}
@Override
- public void startEnrollment() {
+ protected void startEnrollmentInternal() {
super.startEnrollment();
mPreviewFragment = (FaceEnrollPreviewFragment) getSupportFragmentManager()
.findFragmentByTag(TAG_FACE_PREVIEW);
@@ -158,7 +165,7 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling {
disabledFeatures[i] = mDisabledFeatures.get(i);
}
- return new FaceEnrollSidecar(disabledFeatures);
+ return new FaceEnrollSidecar(disabledFeatures, getIntent());
}
@Override
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
index eb50e0874c013316c07a8b2040ed76260b7ed787..d776b9af29af29c3771c28527620cdefb5ea633c 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
@@ -484,6 +484,9 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
protected Intent getEnrollingIntent() {
Intent intent = new Intent(this, FaceEnrollEducation.class);
WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
+ intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON,
+ getIntent().getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1));
+
return intent;
}
@@ -560,7 +563,7 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
@StringRes
protected int getAgreeButtonTextRes() {
- return R.string.security_settings_fingerprint_enroll_introduction_agree;
+ return R.string.security_settings_face_enroll_introduction_agree;
}
@Override
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollSidecar.java b/src/com/android/settings/biometrics/face/FaceEnrollSidecar.java
index de713dd3386795f2f85e5cf38cb2f13e591af45d..b7ba1b52508cfece78199409c2adb66ca2cb8535 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollSidecar.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollSidecar.java
@@ -18,6 +18,7 @@ package com.android.settings.biometrics.face;
import android.app.Activity;
import android.app.settings.SettingsEnums;
+import android.content.Intent;
import android.hardware.face.FaceManager;
import com.android.settings.biometrics.BiometricEnrollSidecar;
@@ -33,8 +34,11 @@ public class FaceEnrollSidecar extends BiometricEnrollSidecar {
private FaceUpdater mFaceUpdater;
- public FaceEnrollSidecar(int[] disabledFeatures) {
+ private Intent mIntent;
+
+ public FaceEnrollSidecar(int[] disabledFeatures, Intent intent) {
mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
+ mIntent = intent;
}
@Override
@@ -47,7 +51,7 @@ public class FaceEnrollSidecar extends BiometricEnrollSidecar {
public void startEnrollment() {
super.startEnrollment();
mFaceUpdater.enroll(mUserId, mToken, mEnrollmentCancel,
- mEnrollmentCallback, mDisabledFeatures);
+ mEnrollmentCallback, mDisabledFeatures, mIntent);
}
private FaceManager.EnrollmentCallback mEnrollmentCallback
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index bebb5c70cab2da7460262a83ed8c5f675fddb64f..197aca03e0bfc925d9bddc135ac4e60f54cc6048 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -18,7 +18,6 @@ package com.android.settings.biometrics.face;
import static android.app.Activity.RESULT_OK;
import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.ENROLL_REQUEST;
@@ -43,12 +42,10 @@ import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricUtils;
-import com.android.settings.biometrics.BiometricsSplitScreenDialog;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.activityembedding.ActivityEmbeddingUtils;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.LayoutPreference;
@@ -104,26 +101,8 @@ public class FaceSettings extends DashboardFragment {
mEnrollButton.setVisible(true);
};
- private final FaceSettingsEnrollButtonPreferenceController.Listener mEnrollListener =
- new FaceSettingsEnrollButtonPreferenceController.Listener() {
- @Override
- public boolean onShowSplitScreenDialog() {
- if (getActivity().isInMultiWindowMode()
- && !ActivityEmbeddingUtils.isActivityEmbedded(getActivity())) {
- // If it's in split mode, show the error dialog.
- BiometricsSplitScreenDialog.newInstance(TYPE_FACE).show(
- getActivity().getSupportFragmentManager(),
- BiometricsSplitScreenDialog.class.getName());
- return true;
- }
- return false;
- }
-
- @Override
- public void onStartEnrolling(Intent intent) {
- FaceSettings.this.startActivityForResult(intent, ENROLL_REQUEST);
- }
- };
+ private final FaceSettingsEnrollButtonPreferenceController.Listener mEnrollListener = intent ->
+ startActivityForResult(intent, ENROLL_REQUEST);
/**
* @param context
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java
index 50e424837eda7facbb8837295675e77b998c952d..e5f7c4ff60f97f312a5dd31d4236178f3310cfeb 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsEnrollButtonPreferenceController.java
@@ -75,11 +75,6 @@ public class FaceSettingsEnrollButtonPreferenceController extends BasePreference
@Override
public void onClick(View v) {
- // If it's in multi window mode, do not start the introduction intent.
- if (mListener != null && mListener.onShowSplitScreenDialog()) {
- return;
- }
-
mIsClicked = true;
final Intent intent = new Intent();
intent.setClassName(SETTINGS_PACKAGE_NAME, FaceEnrollIntroduction.class.getName());
@@ -120,12 +115,6 @@ public class FaceSettingsEnrollButtonPreferenceController extends BasePreference
* Interface for registering callbacks related to the face enroll preference button.
*/
public interface Listener {
- /**
- * Called to check whether to show dialog in split screen mode
- * @return Whether split screen warning dialog shown.
- */
- boolean onShowSplitScreenDialog();
-
/**
* Called when the user has indicated an intent to begin enrolling a new face.
* @param intent The Intent that should be used to launch face enrollment.
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
index 797364b18a81d2b374150bddbc7ed130eacd0086..ae5b62bcbd824cea4c3ca052581cd5ac871a5744 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
@@ -29,7 +29,9 @@ import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
+import android.window.OnBackInvokedCallback;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -57,9 +59,14 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
private static final String TAG = "FaceSettings/Remove";
static final String KEY = "security_settings_face_delete_faces_container";
- public static class ConfirmRemoveDialog extends InstrumentedDialogFragment {
+ public static class ConfirmRemoveDialog extends InstrumentedDialogFragment
+ implements OnBackInvokedCallback {
private static final String KEY_IS_CONVENIENCE = "is_convenience";
private DialogInterface.OnClickListener mOnClickListener;
+ @Nullable
+ private AlertDialog mDialog = null;
+ @Nullable
+ private Preference mFaceUnlockPreference = null;
/** Returns the new instance of the class */
public static ConfirmRemoveDialog newInstance(boolean isConvenience) {
@@ -99,14 +106,41 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
.setMessage(dialogMessageRes)
.setPositiveButton(R.string.delete, mOnClickListener)
.setNegativeButton(R.string.cancel, mOnClickListener);
- AlertDialog dialog = builder.create();
- dialog.setCanceledOnTouchOutside(false);
- return dialog;
+ mDialog = builder.create();
+ mDialog.setCanceledOnTouchOutside(false);
+ mDialog.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(0, this);
+ return mDialog;
}
public void setOnClickListener(DialogInterface.OnClickListener listener) {
mOnClickListener = listener;
}
+
+ public void setPreference(@Nullable Preference preference) {
+ mFaceUnlockPreference = preference;
+ }
+
+ public void unregisterOnBackInvokedCallback() {
+ if (mDialog != null) {
+ mDialog.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(this);
+ }
+ }
+
+ @Override
+ public void onBackInvoked() {
+ if (mDialog != null) {
+ mDialog.cancel();
+ }
+ unregisterOnBackInvokedCallback();
+
+ if (mFaceUnlockPreference != null) {
+ final Button removeButton = ((LayoutPreference) mFaceUnlockPreference)
+ .findViewById(R.id.security_settings_face_settings_remove_button);
+ if (removeButton != null) {
+ removeButton.setEnabled(true);
+ }
+ }
+ }
}
interface Listener {
@@ -171,6 +205,13 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
mButton.setEnabled(true);
mRemoving = false;
}
+
+ final ConfirmRemoveDialog removeDialog =
+ (ConfirmRemoveDialog) mActivity.getSupportFragmentManager()
+ .findFragmentByTag(ConfirmRemoveDialog.class.getName());
+ if (removeDialog != null) {
+ removeDialog.unregisterOnBackInvokedCallback();
+ }
}
};
@@ -210,6 +251,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
(ConfirmRemoveDialog) mActivity.getSupportFragmentManager()
.findFragmentByTag(ConfirmRemoveDialog.class.getName());
if (removeDialog != null) {
+ removeDialog.setPreference(mPreference);
mRemoving = true;
removeDialog.setOnClickListener(mOnConfirmDialogClickListener);
}
diff --git a/src/com/android/settings/biometrics/face/FaceUpdater.java b/src/com/android/settings/biometrics/face/FaceUpdater.java
index 57c11953d6abb5346439fa34951538c670b6b6c5..ddb68129df12461a92654f082deb5db158690ab3 100644
--- a/src/com/android/settings/biometrics/face/FaceUpdater.java
+++ b/src/com/android/settings/biometrics/face/FaceUpdater.java
@@ -17,8 +17,10 @@
package com.android.settings.biometrics.face;
import android.content.Context;
+import android.content.Intent;
import android.hardware.face.Face;
import android.hardware.face.FaceEnrollCell;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.FaceManager;
import android.os.CancellationSignal;
import android.view.Surface;
@@ -26,6 +28,7 @@ import android.view.Surface;
import androidx.annotation.Nullable;
import com.android.settings.Utils;
+import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.safetycenter.BiometricsSafetySource;
/**
@@ -49,19 +52,19 @@ public class FaceUpdater {
/** Wrapper around the {@link FaceManager#enroll} method. */
public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
- FaceManager.EnrollmentCallback callback, int[] disabledFeatures) {
+ FaceManager.EnrollmentCallback callback, int[] disabledFeatures, Intent intent) {
this.enroll(userId, hardwareAuthToken, cancel,
new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures,
- null, false);
+ null, false, intent);
}
/** Wrapper around the {@link FaceManager#enroll} method. */
public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
FaceManager.EnrollmentCallback callback, int[] disabledFeatures,
- @Nullable Surface previewSurface, boolean debugConsent) {
+ @Nullable Surface previewSurface, boolean debugConsent, Intent intent) {
mFaceManager.enroll(userId, hardwareAuthToken, cancel,
new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures,
- previewSurface, debugConsent);
+ previewSurface, debugConsent, toFaceEnrollOptions(intent));
}
/** Wrapper around the {@link FaceManager#remove} method. */
@@ -135,4 +138,14 @@ public class FaceUpdater {
BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed
}
}
+
+ private FaceEnrollOptions toFaceEnrollOptions(Intent intent) {
+ final int reason = intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1);
+ final FaceEnrollOptions.Builder builder = new FaceEnrollOptions.Builder();
+ builder.setEnrollReason(FaceEnrollOptions.ENROLL_REASON_UNKNOWN);
+ if (reason != -1) {
+ builder.setEnrollReason(reason);
+ }
+ return builder.build();
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index 6c0afd1585bcc9231a2f49f6b411d6d63de26c3d..f7134fba6e5b8d415fb825290e51bc69cce49742 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -16,13 +16,12 @@
package com.android.settings.biometrics.fingerprint;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.text.Layout.HYPHENATION_FREQUENCY_NONE;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.RawRes;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
@@ -53,7 +52,6 @@ import android.view.Surface;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ProgressBar;
@@ -61,6 +59,8 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.android.internal.annotations.VisibleForTesting;
@@ -68,6 +68,7 @@ import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollSidecar;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.BiometricsEnrollEnrolling;
+import com.android.settings.biometrics.BiometricsSplitScreenDialog;
import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.flags.Flags;
@@ -191,7 +192,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
private boolean mHaveShownSfpsRightEdgeLottie;
private boolean mShouldShowLottie;
- private ObjectAnimator mHelpAnimation;
+ private Animator mHelpAnimation;
private OrientationEventListener mOrientationEventListener;
private int mPreviousRotation = 0;
@@ -224,7 +225,10 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+ if (shouldShowSplitScreenDialog()) {
+ BiometricsSplitScreenDialog.newInstance(TYPE_FINGERPRINT, true /*destroyActivity*/)
+ .show(getSupportFragmentManager(), BiometricsSplitScreenDialog.class.getName());
+ }
if (savedInstanceState != null) {
restoreSavedState(savedInstanceState);
}
@@ -341,20 +345,14 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
}
private void setHelpAnimation() {
- final float translationX = 40;
- final int duration = 550;
final RelativeLayout progressLottieLayout = findViewById(R.id.progress_lottie);
- mHelpAnimation = ObjectAnimator.ofFloat(progressLottieLayout,
- "translationX" /* propertyName */,
- 0, translationX, -1 * translationX, translationX, 0f);
- mHelpAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
- mHelpAnimation.setDuration(duration);
- mHelpAnimation.setAutoCancel(false);
+ mHelpAnimation = mSfpsEnrollmentFeature.getHelpAnimator(progressLottieLayout);
}
+
@Override
protected BiometricEnrollSidecar getSidecar() {
final FingerprintEnrollSidecar sidecar = new FingerprintEnrollSidecar(this,
- FingerprintManager.ENROLL_ENROLL);
+ FingerprintManager.ENROLL_ENROLL, getIntent());
return sidecar;
}
@@ -703,6 +701,9 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
view.setComposition(composition);
view.setVisibility(View.VISIBLE);
view.playAnimation();
+ if (mCanAssumeSfps) {
+ mSfpsEnrollmentFeature.handleOnEnrollmentLottieComposition(view);
+ }
}
@EnrollStage
@@ -1235,5 +1236,10 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
public float getEnrollStageThreshold(@NonNull Context context, int index) {
throw new IllegalStateException(exceptionStr);
}
+
+ @Override
+ public Animator getHelpAnimator(@NonNull View target) {
+ throw new IllegalStateException(exceptionStr);
+ }
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index 9f3688e8d41891d8c7bd7d173e7997d2916075ee..9d4f54f03dd9b663d3f4b6166fb14a6de90e9216 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -78,7 +78,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
private boolean mIsFolded;
private boolean mIsReverseDefaultRotation;
@Nullable
- private UdfpsEnrollCalibrator mCalibrator;
+ protected UdfpsEnrollCalibrator mCalibrator;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -307,6 +307,8 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
@Override
protected Intent getFingerprintEnrollingIntent() {
final Intent ret = super.getFingerprintEnrollingIntent();
+ ret.putExtra(BiometricUtils.EXTRA_ENROLL_REASON,
+ getIntent().getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1));
if (Flags.udfpsEnrollCalibration()) {
if (mCalibrator != null) {
ret.putExtras(mCalibrator.getExtrasForNextIntent(true));
@@ -364,7 +366,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
FingerprintEnrollEnrolling.TAG_SIDECAR);
if (mSidecar == null) {
mSidecar = new FingerprintEnrollSidecar(this,
- FingerprintManager.ENROLL_FIND_SENSOR);
+ FingerprintManager.ENROLL_FIND_SENSOR, getIntent());
getSupportFragmentManager().beginTransaction()
.add(mSidecar, FingerprintEnrollEnrolling.TAG_SIDECAR)
.commitAllowingStateLoss();
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
index 722f213ce10500a41472d808b5188e7ad53f6270..9c89f24ef876a9796ca33558ab30c96f308872e0 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
@@ -23,8 +23,10 @@ import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.VisibleForTesting;
@@ -32,6 +34,8 @@ import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricUtils;
+import com.android.settings.biometrics.fingerprint.feature.SfpsRestToUnlockFeature;
+import com.android.settings.overlay.FeatureFactory;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
@@ -56,6 +60,8 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
private boolean mIsAddAnotherOrFinish;
+ private SfpsRestToUnlockFeature mSfpsRestToUnlockFeature;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -64,14 +70,20 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
mFingerprintManager.getSensorPropertiesInternal();
mCanAssumeSfps = props != null && props.size() == 1 && props.get(0).isAnySidefpsType();
if (mCanAssumeSfps) {
+ mSfpsRestToUnlockFeature = FeatureFactory.getFeatureFactory()
+ .getFingerprintFeatureProvider().getSfpsRestToUnlockFeature(this);
setContentView(R.layout.sfps_enroll_finish);
+ setUpRestToUnlockLayout();
} else {
setContentView(R.layout.fingerprint_enroll_finish);
}
setHeaderText(R.string.security_settings_fingerprint_enroll_finish_title);
setDescriptionText(R.string.security_settings_fingerprint_enroll_finish_v2_message);
- if (mCanAssumeSfps) {
- setDescriptionForSfps();
+ final String sfpsDescription = mSfpsRestToUnlockFeature != null
+ ? mSfpsRestToUnlockFeature.getDescriptionForSfps(this)
+ : null;
+ if (mCanAssumeSfps && !TextUtils.isEmpty(sfpsDescription)) {
+ setDescriptionForSfps(sfpsDescription);
}
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
@@ -93,7 +105,7 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
);
}
- private void setDescriptionForSfps() {
+ private void setDescriptionForSfps(String sfpsDescription) {
final FingerprintManager fpm = Utils.getFingerprintManagerOrNull(this);
if (fpm != null) {
final List props =
@@ -101,12 +113,19 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
final int maxEnrollments = props.get(0).maxEnrollmentsPerUser;
final int enrolled = fpm.getEnrolledFingerprints(mUserId).size();
if (enrolled < maxEnrollments) {
- setDescriptionText(R.string
- .security_settings_fingerprint_enroll_finish_v2_add_fingerprint_message);
+ setDescriptionText(sfpsDescription);
}
}
}
+ private void setUpRestToUnlockLayout() {
+ final ViewGroup contentFrame = findViewById(R.id.sfps_enrollment_finish_content_frame);
+ final View restToUnlockLayout = mSfpsRestToUnlockFeature.getRestToUnlockLayout(this);
+ if (restToUnlockLayout == null) return;
+ contentFrame.removeAllViews();
+ contentFrame.addView(restToUnlockLayout);
+ }
+
@Override
public void onBackPressed() {
updateFingerprintSuggestionEnableState();
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
index 91751adebb6a6d7c4f1fe569ac8245a5e35aa715..17944958079c20f4f8690908e4cc94f8aad9aa60 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
@@ -60,7 +60,6 @@ import com.google.android.setupdesign.util.DeviceHelper;
import java.util.List;
public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
-
private static final String TAG = "FingerprintIntro";
@VisibleForTesting
@@ -71,7 +70,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
private DevicePolicyManager mDevicePolicyManager;
private boolean mCanAssumeUdfps;
@Nullable
- private UdfpsEnrollCalibrator mCalibrator;
+ protected UdfpsEnrollCalibrator mCalibrator;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -393,6 +392,8 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
intent.putExtras(mCalibrator.getExtrasForNextIntent(false));
}
}
+ intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON,
+ getIntent().getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1));
return intent;
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java
index 493302bd13c8fd2c31d6ecf3beae8d2f68fa8e53..52d7d2be7a3679aff181b5d80181c67fd5349936 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java
@@ -21,6 +21,8 @@ import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.content.Intent;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.os.SystemClock;
import android.util.Log;
@@ -28,6 +30,9 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollSidecar;
+import com.android.settings.biometrics.BiometricUtils;
+
+import com.google.android.setupcompat.util.WizardManagerHelper;
/**
* Sidecar fragment to handle the state around fingerprint enrollment.
@@ -39,15 +44,18 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar {
private @FingerprintManager.EnrollReason int mEnrollReason;
private final MessageDisplayController mMessageDisplayController;
private final boolean mMessageDisplayControllerFlag;
+ private final Intent mIntent;
/**
* Create a new FingerprintEnrollSidecar object.
- * @param context associated context
+ *
+ * @param context associated context
* @param enrollReason reason for enrollment
*/
public FingerprintEnrollSidecar(Context context,
- @FingerprintManager.EnrollReason int enrollReason) {
+ @FingerprintManager.EnrollReason int enrollReason, Intent intent) {
mEnrollReason = enrollReason;
+ mIntent = intent;
int helpMinimumDisplayTime = context.getResources().getInteger(
R.integer.enrollment_help_minimum_time_display);
@@ -85,14 +93,21 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar {
return;
}
+ if (mIntent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) == -1) {
+ final boolean isSuw = WizardManagerHelper.isAnySetupWizard(mIntent);
+ mIntent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON,
+ isSuw ? FingerprintEnrollOptions.ENROLL_REASON_SUW :
+ FingerprintEnrollOptions.ENROLL_REASON_SETTINGS);
+ }
+
if (mEnrollReason == ENROLL_ENROLL && mMessageDisplayControllerFlag) {
//API calls need to be processed for {@link FingerprintEnrollEnrolling}
mFingerprintUpdater.enroll(mToken, mEnrollmentCancel, mUserId,
- mMessageDisplayController, mEnrollReason);
+ mMessageDisplayController, mEnrollReason, mIntent);
} else {
//No processing required for {@link FingerprintEnrollFindSensor}
mFingerprintUpdater.enroll(mToken, mEnrollmentCancel, mUserId, mEnrollmentCallback,
- mEnrollReason);
+ mEnrollReason, mIntent);
}
}
@@ -100,7 +115,8 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar {
mEnrollReason = enrollReason;
}
- @VisibleForTesting FingerprintManager.EnrollmentCallback mEnrollmentCallback
+ @VisibleForTesting
+ FingerprintManager.EnrollmentCallback mEnrollmentCallback
= new FingerprintManager.EnrollmentCallback() {
@Override
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
index e7702207ad176626cd0c81407ee74e9da1191119..c1e34a579a8f3344d16c87326bc4fc912494a01b 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
@@ -24,6 +24,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
+import com.android.settings.biometrics.fingerprint.feature.SfpsRestToUnlockFeature;
public interface FingerprintFeatureProvider {
/**
@@ -44,4 +45,11 @@ public interface FingerprintFeatureProvider {
@Nullable Bundle activitySavedInstanceState, @Nullable Intent activityIntent) {
return null;
}
+
+ /**
+ * Gets the feature implementation of SFPS rest to unlock.
+ * @param context context
+ * @return the feature implementation
+ */
+ SfpsRestToUnlockFeature getSfpsRestToUnlockFeature(@NonNull Context context);
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
index 9745ca3fd7e53ce970d1fb2e6a373fe969ad2ee1..8a8df984e5282c7d12652bac7d8704c2d0ff0b68 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
@@ -16,16 +16,24 @@
package com.android.settings.biometrics.fingerprint;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeatureImpl;
+import com.android.settings.biometrics.fingerprint.feature.SfpsRestToUnlockFeature;
+import com.android.settings.biometrics.fingerprint.feature.SfpsRestToUnlockFeatureImpl;
public class FingerprintFeatureProviderImpl implements FingerprintFeatureProvider {
@Nullable
private SfpsEnrollmentFeature mSfpsEnrollmentFeatureImpl = null;
+ @Nullable
+ private SfpsRestToUnlockFeature mSfpsRestToUnlockFeature = null;
+
@Override
public SfpsEnrollmentFeature getSfpsEnrollmentFeature() {
if (mSfpsEnrollmentFeatureImpl == null) {
@@ -33,4 +41,12 @@ public class FingerprintFeatureProviderImpl implements FingerprintFeatureProvide
}
return mSfpsEnrollmentFeatureImpl;
}
+
+ @Override
+ public SfpsRestToUnlockFeature getSfpsRestToUnlockFeature(@NonNull Context context) {
+ if (mSfpsRestToUnlockFeature == null) {
+ mSfpsRestToUnlockFeature = new SfpsRestToUnlockFeatureImpl();
+ }
+ return mSfpsRestToUnlockFeature;
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java b/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java
index 4d7e2d24427ccb4f83666bba609127c39a2bb319..08e86c3befa2145101c1331062c21a99a605e9db 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java
@@ -20,7 +20,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.ColorInt;
-import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -30,6 +29,8 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
+import androidx.annotation.Nullable;
+
import com.android.settings.R;
import com.android.settings.Utils;
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java b/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java
index 134462d10c1353e6b1e9ac0d0e458717f4e939d7..73eccdc4564af345ab9b25a5704e53d5b81d2151 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java
@@ -16,13 +16,14 @@
package com.android.settings.biometrics.fingerprint;
-import android.annotation.Nullable;
import android.app.settings.SettingsEnums;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.settings.core.InstrumentedFragment;
import java.util.LinkedList;
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index a3e84421e1ad9415956358e014c6f5d22fc6d546..6b9d397e0a1964195865afa80715ce4df10b9fe5 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -20,7 +20,6 @@ package com.android.settings.biometrics.fingerprint;
import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED_EXPLANATION;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE;
import static android.app.admin.DevicePolicyResources.UNDEFINED;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
@@ -68,13 +67,13 @@ 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.biometrics.BiometricsSplitScreenDialog;
import com.android.settings.biometrics.GatekeeperPasswordProvider;
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -83,7 +82,6 @@ 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.activityembedding.ActivityEmbeddingUtils;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.transition.SettingsTransitionHelper;
@@ -668,6 +666,19 @@ public class FingerprintSettings extends SubSettings {
if (mRequireScreenOnToAuthPreferenceController != null) {
setupFingerprintUnlockCategoryPreferences();
}
+ final Preference restToUnlockPreference = FeatureFactory.getFeatureFactory()
+ .getFingerprintFeatureProvider()
+ .getSfpsRestToUnlockFeature(getContext())
+ .getRestToUnlockPreference(getContext());
+ if (restToUnlockPreference != null) {
+ // Use custom featured preference if any.
+ mRequireScreenOnToAuthPreference.setTitle(restToUnlockPreference.getTitle());
+ mRequireScreenOnToAuthPreference.setSummary(restToUnlockPreference.getSummary());
+ mRequireScreenOnToAuthPreference.setChecked(
+ ((TwoStatePreference) restToUnlockPreference).isChecked());
+ mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener(
+ restToUnlockPreference.getOnPreferenceChangeListener());
+ }
if (mFingerprintUnlockCategoryPreferenceController != null) {
updateFingerprintUnlockCategoryVisibility();
}
@@ -815,17 +826,6 @@ public class FingerprintSettings extends SubSettings {
public boolean onPreferenceTreeClick(Preference pref) {
final String key = pref.getKey();
if (KEY_FINGERPRINT_ADD.equals(key)) {
- // If it's in split mode, show the error dialog and don't need to show adding
- // fingerprint intent.
- final boolean isActivityEmbedded = ActivityEmbeddingUtils.isActivityEmbedded(
- getActivity());
- if (getActivity().isInMultiWindowMode() && !isActivityEmbedded) {
- BiometricsSplitScreenDialog.newInstance(TYPE_FINGERPRINT).show(
- getActivity().getSupportFragmentManager(),
- BiometricsSplitScreenDialog.class.getName());
- return true;
- }
-
mIsEnrolling = true;
Intent intent = new Intent();
if (FeatureFlagUtils.isEnabled(getContext(),
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java b/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java
index 306b1a3e136faff0412a4c723c64a52e5a0f6b9d..ea9abf1bb5cd5ba9bfcb8b331b69796cd3bb63f9 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java
@@ -17,13 +17,16 @@
package com.android.settings.biometrics.fingerprint;
import android.content.Context;
+import android.content.Intent;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.os.CancellationSignal;
import androidx.annotation.Nullable;
import com.android.settings.Utils;
+import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.safetycenter.BiometricsSafetySource;
/**
@@ -48,9 +51,10 @@ public class FingerprintUpdater {
/** Wrapper around the {@link FingerprintManager#enroll} method. */
public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId,
FingerprintManager.EnrollmentCallback callback,
- @FingerprintManager.EnrollReason int enrollReason) {
+ @FingerprintManager.EnrollReason int enrollReason, Intent intent) {
mFingerprintManager.enroll(hardwareAuthToken, cancel, userId,
- new NotifyingEnrollmentCallback(mContext, callback), enrollReason);
+ new NotifyingEnrollmentCallback(mContext, callback), enrollReason,
+ toFingerprintEnrollOptions(intent));
}
/** Wrapper around the {@link FingerprintManager#remove} method. */
@@ -138,4 +142,14 @@ public class FingerprintUpdater {
BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed
}
}
+
+ private FingerprintEnrollOptions toFingerprintEnrollOptions(Intent intent) {
+ final int reason = intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1);
+ final FingerprintEnrollOptions.Builder builder = new FingerprintEnrollOptions.Builder();
+ builder.setEnrollReason(FingerprintEnrollOptions.ENROLL_REASON_UNKNOWN);
+ if (reason != -1) {
+ builder.setEnrollReason(reason);
+ }
+ return builder.build();
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint/MessageDisplayController.java b/src/com/android/settings/biometrics/fingerprint/MessageDisplayController.java
index 11f3ee39062bc3f9e74117e65d563a8c50051051..4dc75b9d3756cc6b39b09c61ae6bae98abd9ea5a 100644
--- a/src/com/android/settings/biometrics/fingerprint/MessageDisplayController.java
+++ b/src/com/android/settings/biometrics/fingerprint/MessageDisplayController.java
@@ -16,11 +16,12 @@
package com.android.settings.biometrics.fingerprint;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.time.Clock;
import java.util.ArrayDeque;
import java.util.Deque;
diff --git a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java
index a71bb6540218e4857f4b008f738755ec912ba61b..6590530cecf77a3efac42efabd918f06773e07b4 100644
--- a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java
@@ -33,6 +33,7 @@ import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.flags.Flags;
import com.android.settings.password.ChooseLockSettingsHelper;
public class SetupFingerprintEnrollFindSensor extends FingerprintEnrollFindSensor {
@@ -48,6 +49,11 @@ public class SetupFingerprintEnrollFindSensor extends FingerprintEnrollFindSenso
}
BiometricUtils.copyMultiBiometricExtras(getIntent(), intent);
SetupWizardUtils.copySetupExtras(getIntent(), intent);
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ intent.putExtras(mCalibrator.getExtrasForNextIntent(true));
+ }
+ }
return intent;
}
diff --git a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java
index eb6868790090bfb67262f375e5c9cd5a0e53d51c..0ee9ad380b5b77f69a3f27e539879eb4caec7bfa 100644
--- a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java
@@ -25,6 +25,7 @@ import android.view.View;
import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricUtils;
+import com.android.settings.flags.Flags;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupSkipDialog;
@@ -46,6 +47,11 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu
BiometricUtils.getGatekeeperPasswordHandle(getIntent()));
}
SetupWizardUtils.copySetupExtras(getIntent(), intent);
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ intent.putExtras(mCalibrator.getExtrasForNextIntent(false));
+ }
+ }
return intent;
}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
index d17fa24fede148368991552a2df83ab460f64d36..c798dff4be73bcd89eef04ce9cecf70764f78ddc 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
@@ -138,7 +138,8 @@ public class UdfpsEnrollEnrollingView extends GlifLayout {
displayInfo.getNaturalWidth(),
displayInfo.getNaturalHeight(),
scaleFactor,
- displayInfo.rotation);
+ displayInfo.rotation,
+ udfpsProps.sensorType);
mUdfpsEnrollView.setOverlayParams(params);
mUdfpsEnrollView.setEnrollHelper(udfpsEnrollHelper);
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
index d3bc97764de4a68a2d3ecd2af8fe2e0e6d851a9e..8d1113ee289e4c29c26c2ff3db507ad78d828cf1 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
@@ -16,8 +16,6 @@
package com.android.settings.biometrics.fingerprint;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PointF;
import android.hardware.fingerprint.FingerprintManager;
@@ -29,6 +27,9 @@ import android.util.Log;
import android.util.TypedValue;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.settings.core.InstrumentedFragment;
import java.util.ArrayList;
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java
index 2d392ffbe6debe1b1476c0ad442c5116855d4cf9..4a2a243d2d8d99f8f307c055624d429272755917 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java
@@ -19,6 +19,7 @@ package com.android.settings.biometrics.fingerprint;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.hardware.fingerprint.FingerprintSensorProperties;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
@@ -202,7 +203,9 @@ public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Li
}
private void onFingerDown() {
- mFingerprintDrawable.setShouldSkipDraw(true);
+ if (mOverlayParams.getSensorType() == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ mFingerprintDrawable.setShouldSkipDraw(true);
+ }
mFingerprintDrawable.invalidateSelf();
}
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
index a1a18e5652e479ce88fcdcda9d01807fc7730fe1..f99d394dceb0293c9da91ee046e082bf45373991 100644
--- a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
@@ -16,12 +16,16 @@
package com.android.settings.biometrics.fingerprint.feature;
+import android.animation.Animator;
import android.content.Context;
+import android.view.View;
import androidx.annotation.NonNull;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
+import com.airbnb.lottie.LottieAnimationView;
+
import java.util.function.Function;
import java.util.function.Supplier;
@@ -76,4 +80,17 @@ public interface SfpsEnrollmentFeature {
* @return threshold
*/
float getEnrollStageThreshold(@NonNull Context context, int index);
+
+ /**
+ * Gets the help animator used when get help message.
+ * @param target the target view to animate
+ * @return animator
+ */
+ Animator getHelpAnimator(@NonNull View target);
+
+ /**
+ * Handles extra stuffs on lottie composition.
+ * @param lottieView the view related to the lottie
+ */
+ default void handleOnEnrollmentLottieComposition(LottieAnimationView lottieView) {}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
index 5a975373c0cab70edb0434668e4222f417373ed1..60ced6e95f1b0c2f72971bac6f4608779a9a350e 100644
--- a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
@@ -23,17 +23,24 @@ import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrol
import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_RIGHT_EDGE;
import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.STAGE_UNKNOWN;
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import java.util.function.Function;
public class SfpsEnrollmentFeatureImpl implements SfpsEnrollmentFeature {
+ @VisibleForTesting
+ public static final int HELP_ANIMATOR_DURATION = 550;
@Nullable
private FingerprintManager mFingerprintManager = null;
@@ -88,4 +95,16 @@ public class SfpsEnrollmentFeatureImpl implements SfpsEnrollmentFeature {
}
return mFingerprintManager.getEnrollStageThreshold(index);
}
+
+ @Override
+ public Animator getHelpAnimator(@NonNull View target) {
+ final float translationX = 40;
+ final ObjectAnimator help = ObjectAnimator.ofFloat(target,
+ "translationX" /* propertyName */,
+ 0, translationX, -1 * translationX, translationX, 0f);
+ help.setInterpolator(new AccelerateInterpolator());
+ help.setDuration(HELP_ANIMATOR_DURATION);
+ help.setAutoCancel(false);
+ return help;
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsRestToUnlockFeature.kt b/src/com/android/settings/biometrics/fingerprint/feature/SfpsRestToUnlockFeature.kt
new file mode 100644
index 0000000000000000000000000000000000000000..840926c013a8451a3d53f66e875575d921565fa1
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsRestToUnlockFeature.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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.feature
+
+import android.content.Context
+import android.view.View
+import androidx.preference.Preference
+
+/**
+ * Defines the feature provided by rest to unlock.
+ */
+interface SfpsRestToUnlockFeature {
+ /**
+ * Gets the content view hierarchy for SFPS rest to unlock feature which is used by
+ * [com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish].
+ * @param context the context of
+ * [com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish].
+ */
+ fun getRestToUnlockLayout(context: Context) : View? = null
+
+ /**
+ * Gets the SFPS rest to unlock preference which is used in
+ * [com.android.settings.biometrics.fingerprint.FingerprintSettings].
+ * @param context the context of
+ * [com.android.settings.biometrics.fingerprint.FingerprintSettings].
+ */
+ fun getRestToUnlockPreference(context: Context) : Preference? = null
+
+ /**
+ * Gets the specific description used in
+ * [com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish] for SFPS devices.
+ * @return the description text for SFPS devices.
+ */
+ fun getDescriptionForSfps(context: Context) : String
+}
\ No newline at end of file
diff --git a/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsRestToUnlockFeatureImpl.kt
similarity index 55%
rename from src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java
rename to src/com/android/settings/biometrics/fingerprint/feature/SfpsRestToUnlockFeatureImpl.kt
index f8364158d5809e67223112a23f52ef9377f89404..de78a2a32fa0c03aa7bc1a7943826184832c20ff 100644
--- a/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsRestToUnlockFeatureImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -14,18 +14,15 @@
* limitations under the License.
*/
-package com.android.settings.network.telephony;
+package com.android.settings.biometrics.fingerprint.feature
-import android.content.Context;
+import android.content.Context
-import com.android.settings.widget.PreferenceCategoryController;
-
-/**
- * Preference controller for "Calling" category
- */
-public class CallingPreferenceCategoryController extends PreferenceCategoryController {
-
- public CallingPreferenceCategoryController(Context context, String key) {
- super(context, key);
+class SfpsRestToUnlockFeatureImpl : SfpsRestToUnlockFeature {
+ override fun getDescriptionForSfps(context: Context) : String {
+ return context.getString(
+ com.android.settings.R
+ .string.security_settings_fingerprint_enroll_finish_v2_add_fingerprint_message
+ )
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/OWNERS b/src/com/android/settings/biometrics/fingerprint2/OWNERS
index c58a06d5fa02353cd30ed0cbe7c20d783f7ea2e4..f5fd453ff2646e548bbbbefbd066a97cba254158 100644
--- a/src/com/android/settings/biometrics/fingerprint2/OWNERS
+++ b/src/com/android/settings/biometrics/fingerprint2/OWNERS
@@ -1,3 +1,4 @@
# Owners for Biometric Fingerprint
joshmccloskey@google.com
-jbolinger@google.com
\ No newline at end of file
+jbolinger@google.com
+spdonghao@google.com
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt b/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
index 58ef50978344113a29e8b75459baaf5e8b1dfe55..0ef1d2568085fd4662bb1456c48c652e88a5cf77 100644
--- a/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
@@ -20,8 +20,8 @@ import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERR
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS
import android.hardware.fingerprint.FingerprintManager
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
object Util {
fun EnrollReason.toOriginalReason(): Int {
@@ -71,6 +71,4 @@ object Util {
this == FINGERPRINT_ERROR_CANCELED,
)
}
-
}
-
diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..000a4774a78b34825f794f3d61960058d289cd04
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 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.fingerprint2.data.repository
+
+import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import com.android.systemui.biometrics.shared.model.FingerprintSensor
+import com.android.systemui.biometrics.shared.model.toFingerprintSensor
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Provides the [FingerprintSensor]
+ *
+ * TODO(b/313493336): Move this to systemui
+ */
+interface FingerprintSensorRepository {
+ /** Get the [FingerprintSensor] */
+ val fingerprintSensor: Flow
+}
+
+class FingerprintSensorRepositoryImpl(
+ fingerprintManager: FingerprintManager,
+ backgroundDispatcher: CoroutineDispatcher,
+ activityScope: CoroutineScope,
+) : FingerprintSensorRepository {
+
+ override val fingerprintSensor: Flow =
+ callbackFlow {
+ val callback =
+ object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ override fun onAllAuthenticatorsRegistered(
+ sensors: List
+ ) {
+ if (sensors.isEmpty()) {
+ trySend(DEFAULT_PROPS)
+ } else {
+ trySend(sensors[0].toFingerprintSensor())
+ }
+ }
+ }
+ withContext(backgroundDispatcher) {
+ fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
+ }
+ awaitClose {}
+ }
+ .stateIn(activityScope, started = SharingStarted.Eagerly, initialValue = DEFAULT_PROPS)
+
+ companion object {
+ private const val TAG = "FingerprintSensorRepoImpl"
+
+ private val DEFAULT_PROPS =
+ FingerprintSensorPropertiesInternal(
+ -1 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 0 /* maxEnrollmentsPerUser */,
+ listOf(),
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* halControlsIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ listOf(SensorLocationInternal.DEFAULT),
+ )
+ .toFingerprintSensor()
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/AccessibilityViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AccessibilityInteractor.kt
similarity index 57%
rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/AccessibilityViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/domain/interactor/AccessibilityInteractor.kt
index a86ad5d99425c04eee4fb3864e6adb50101bba5c..e7692372a31f301af5162b5b1a1b146dfca8fd3d 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/AccessibilityViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AccessibilityInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.view.accessibility.AccessibilityManager
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.LifecycleCoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -27,30 +25,28 @@ import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.stateIn
/** Represents all of the information on accessibility state. */
-class AccessibilityViewModel(accessibilityManager: AccessibilityManager) : ViewModel() {
+interface AccessibilityInteractor {
+ /** A flow that contains whether or not accessibility is enabled */
+ val isAccessibilityEnabled: Flow
+}
+
+class AccessibilityInteractorImpl(
+ accessibilityManager: AccessibilityManager,
+ activityScope: LifecycleCoroutineScope
+) : AccessibilityInteractor {
/** A flow that contains whether or not accessibility is enabled */
- val isAccessibilityEnabled: Flow =
+ override val isAccessibilityEnabled: Flow =
callbackFlow {
val listener =
- AccessibilityManager.AccessibilityStateChangeListener { enabled -> trySend(enabled) }
+ AccessibilityManager.AccessibilityStateChangeListener { enabled -> trySend(enabled) }
accessibilityManager.addAccessibilityStateChangeListener(listener)
// This clause will be called when no one is listening to the flow
awaitClose { accessibilityManager.removeAccessibilityStateChangeListener(listener) }
- }
+ }
.stateIn(
- viewModelScope, // This is going to tied to the view model scope
- SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener
+ activityScope, // This is going to tied to the activity scope
+ SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener
false
)
-
- class AccessibilityViewModelFactory(private val accessibilityManager: AccessibilityManager) :
- ViewModelProvider.Factory {
- @Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
- return AccessibilityViewModel(accessibilityManager) as T
- }
- }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
index 984d04cb44ea46b330290ae5bf41aaacd6e7725a..900f7cfb734778f9873bb093dd934b8bd6c304b0 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
@@ -18,24 +18,29 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.content.Context
import android.content.Intent
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricFingerprintConstants
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
import android.os.CancellationSignal
import android.util.Log
import com.android.settings.biometrics.GatekeeperPasswordProvider
+import com.android.settings.biometrics.BiometricUtils
import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
-import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
-import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
-import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
-import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
+import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
import com.android.settings.password.ChooseLockSettingsHelper
import com.android.systemui.biometrics.shared.model.toFingerprintSensor
+import com.google.android.setupcompat.util.WizardManagerHelper
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CancellableContinuation
@@ -57,9 +62,11 @@ class FingerprintManagerInteractorImpl(
applicationContext: Context,
private val backgroundDispatcher: CoroutineDispatcher,
private val fingerprintManager: FingerprintManager,
+ fingerprintSensorRepository: FingerprintSensorRepository,
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
- private val pressToAuthProvider: PressToAuthProvider,
+ private val pressToAuthInteractor: PressToAuthInteractor,
private val fingerprintFlow: FingerprintFlow,
+ private val intent: Intent,
) : FingerprintManagerInteractor {
private val maxFingerprints =
@@ -100,13 +107,7 @@ class FingerprintManagerInteractorImpl(
)
}
- override val sensorPropertiesInternal = flow {
- val sensorPropertiesInternal = fingerprintManager.sensorPropertiesInternal
- emit(
- if (sensorPropertiesInternal.isEmpty()) null
- else sensorPropertiesInternal.first().toFingerprintSensor()
- )
- }
+ override val sensorPropertiesInternal = fingerprintSensorRepository.fingerprintSensor
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
@@ -136,8 +137,7 @@ class FingerprintManagerInteractorImpl(
totalSteps = remaining + 1
}
- trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure {
- error ->
+ trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure { error ->
Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
}
@@ -148,13 +148,16 @@ class FingerprintManagerInteractorImpl(
}
override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
- trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString()))
- .onFailure { error -> Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") }
+ trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString())).onFailure { error
+ ->
+ Log.d(TAG, "onEnrollmentHelp failed to send, due to $error")
+ }
}
override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
- trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard))
- .onFailure { error -> Log.d(TAG, "onEnrollmentError failed to send, due to $error") }
+ trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard)).onFailure { error ->
+ Log.d(TAG, "onEnrollmentError failed to send, due to $error")
+ }
Log.d(TAG, "onEnrollmentError($errMsgId)")
streamEnded = true
enrollRequestOutstanding.update { false }
@@ -162,12 +165,21 @@ class FingerprintManagerInteractorImpl(
}
val cancellationSignal = CancellationSignal()
+
+ if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) {
+ val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent)
+ intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON,
+ if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW else
+ FingerprintEnrollOptions.ENROLL_REASON_SETTINGS)
+ }
+
fingerprintManager.enroll(
hardwareAuthToken,
cancellationSignal,
applicationContext.userId,
enrollmentCallback,
enrollReason.toOriginalReason(),
+ toFingerprintEnrollOptions(intent)
)
awaitClose {
// If the stream has not been ended, and the user has stopped collecting the flow
@@ -185,14 +197,14 @@ class FingerprintManagerInteractorImpl(
override fun onRemovalError(
fp: android.hardware.fingerprint.Fingerprint,
errMsgId: Int,
- errString: CharSequence
+ errString: CharSequence,
) {
it.resume(false)
}
override fun onRemovalSucceeded(
fp: android.hardware.fingerprint.Fingerprint?,
- remaining: Int
+ remaining: Int,
) {
it.resume(true)
}
@@ -200,7 +212,7 @@ class FingerprintManagerInteractorImpl(
fingerprintManager.remove(
android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
applicationContext.userId,
- callback
+ callback,
)
}
@@ -214,10 +226,6 @@ class FingerprintManagerInteractorImpl(
it.resume(fingerprintManager.isPowerbuttonFps)
}
- override suspend fun pressToAuthEnabled(): Boolean = suspendCancellableCoroutine {
- it.resume(pressToAuthProvider.isEnabled)
- }
-
override suspend fun authenticate(): FingerprintAuthAttemptModel =
suspendCancellableCoroutine { c: CancellableContinuation ->
val authenticationCallback =
@@ -249,7 +257,18 @@ class FingerprintManagerInteractorImpl(
cancellationSignal,
authenticationCallback,
null,
- applicationContext.userId
+ applicationContext.userId,
)
}
+
+ private fun toFingerprintEnrollOptions(intent: Intent): FingerprintEnrollOptions {
+ val reason: Int =
+ intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1)
+ val builder: FingerprintEnrollOptions.Builder = FingerprintEnrollOptions.Builder()
+ builder.setEnrollReason(FingerprintEnrollOptions.ENROLL_REASON_UNKNOWN)
+ if (reason != -1) {
+ builder.setEnrollReason(reason)
+ }
+ return builder.build()
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FoldStateInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FoldStateInteractor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0224aa2ef9321705a1cb01460d2c7d166536cd82
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FoldStateInteractor.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 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.fingerprint2.domain.interactor
+
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.unfold.updates.FoldProvider
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+
+interface FoldStateInteractor {
+ /** A flow that contains the fold state info */
+ val isFolded: Flow
+
+ /**
+ * Indicates a configuration change has occurred, and the repo
+ * should update the [isFolded] flow.
+ */
+ fun onConfigurationChange(newConfig: Configuration)
+}
+
+/**
+ * Interactor which handles fold state
+ */
+class FoldStateInteractorImpl(context: Context) : FoldStateInteractor {
+ private val screenSizeFoldProvider = ScreenSizeFoldProvider(context)
+ override val isFolded: Flow = callbackFlow {
+ val foldStateListener = FoldProvider.FoldCallback { isFolded -> trySend(isFolded) }
+ screenSizeFoldProvider.registerCallback(foldStateListener, context.mainExecutor)
+ awaitClose { screenSizeFoldProvider.unregisterCallback(foldStateListener) }
+ }
+
+ /**
+ * This function is called by the root activity, indicating an orientation event has occurred.
+ * When this happens, the [ScreenSizeFoldProvider] is notified and it will re-compute if the
+ * device is folded or not, and notify the [FoldProvider.FoldCallback]
+ */
+ override fun onConfigurationChange(newConfig: Configuration) {
+ screenSizeFoldProvider.onConfigurationChange(newConfig)
+ }
+
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/OrientationStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt
similarity index 54%
rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/OrientationStateViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt
index 2e5f7341743574e159d164169cf90d058541b50e..968203fff80032618f6e24a5a4da38b6386191ad 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/OrientationStateViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -14,25 +14,37 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.content.Context
import android.view.OrientationEventListener
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewModelScope
import com.android.internal.R
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.stateIn
-/** Represents all of the information on orientation state and rotation state. */
-class OrientationStateViewModel(private val context: Context) : ViewModel() {
+/**
+ * Interactor which provides information about orientation
+ */
+interface OrientationInteractor {
+ /** A flow that contains the information about the orientation changing */
+ val orientation: Flow
+ /** A flow that contains the rotation info */
+ val rotation: Flow
+ /**
+ * A Helper function that computes rotation if device is in
+ * [R.bool.config_reverseDefaultConfigRotation]
+ */
+ fun getRotationFromDefault(rotation: Int): Int
+}
- /** A flow that contains the orientation info */
- val orientation: Flow = callbackFlow {
+class OrientationInteractorImpl(private val context: Context, activityScope: CoroutineScope) :
+ OrientationInteractor {
+
+ override val orientation: Flow = callbackFlow {
val orientationEventListener =
object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
@@ -43,25 +55,24 @@ class OrientationStateViewModel(private val context: Context) : ViewModel() {
awaitClose { orientationEventListener.disable() }
}
- /** A flow that contains the rotation info */
- val rotation: Flow =
+ override val rotation: Flow =
callbackFlow {
- val orientationEventListener =
- object : OrientationEventListener(context) {
- override fun onOrientationChanged(orientation: Int) {
- trySend(getRotationFromDefault(context.display!!.rotation))
- }
+ val orientationEventListener =
+ object : OrientationEventListener(context) {
+ override fun onOrientationChanged(orientation: Int) {
+ trySend(getRotationFromDefault(context.display!!.rotation))
}
- orientationEventListener.enable()
- awaitClose { orientationEventListener.disable() }
- }
+ }
+ orientationEventListener.enable()
+ awaitClose { orientationEventListener.disable() }
+ }
.stateIn(
- viewModelScope, // This is going to tied to the view model scope
+ activityScope, // This is tied to the activity scope
SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener
- context.display!!.rotation
+ context.display!!.rotation,
)
- fun getRotationFromDefault(rotation: Int): Int {
+ override fun getRotationFromDefault(rotation: Int): Int {
val isReverseDefaultRotation =
context.resources.getBoolean(R.bool.config_reverseDefaultRotation)
return if (isReverseDefaultRotation) {
@@ -70,13 +81,4 @@ class OrientationStateViewModel(private val context: Context) : ViewModel() {
rotation
}
}
-
- class OrientationViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
- @Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
- return OrientationStateViewModel(context) as T
- }
- }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/PressToAuthInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/PressToAuthInteractor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ab7b5de3f518a2887d222f183d5d8a96fb7ceec1
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/PressToAuthInteractor.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 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.fingerprint2.domain.interactor
+
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings
+import android.util.Log
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+
+/** Interface that indicates if press to auth is on or off. */
+interface PressToAuthInteractor {
+ /** Indicates true if the PressToAuth feature is enabled, false otherwise. */
+ val isEnabled: Flow
+}
+
+/** Indicates whether or not the press to auth feature is enabled. */
+class PressToAuthInteractorImpl(
+ private val context: Context,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : PressToAuthInteractor {
+
+ /**
+ * A flow that contains the status of the press to auth feature.
+ */
+ override val isEnabled: Flow =
+
+ callbackFlow {
+ val callback =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ Log.d(TAG, "SFPS_PERFORMANT_AUTH_ENABLED#onchange")
+ trySend(
+ getPressToAuth(),
+ )
+ }
+ }
+
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED),
+ false,
+ callback,
+ context.userId
+ )
+ trySend(getPressToAuth())
+ awaitClose {
+ context.contentResolver.unregisterContentObserver(callback)
+ }
+ }.flowOn(backgroundDispatcher)
+
+
+ /**
+ * Returns true if press to auth is enabled
+ */
+ private fun getPressToAuth(): Boolean {
+ var toReturn: Int =
+ Settings.Secure.getIntForUser(
+ context.contentResolver,
+ Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+ -1,
+ context.userId,
+ )
+ if (toReturn == -1) {
+ toReturn =
+ if (
+ context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
+ ) {
+ 1
+ } else {
+ 0
+ }
+ Settings.Secure.putIntForUser(
+ context.contentResolver,
+ Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+ toReturn,
+ context.userId,
+ )
+ }
+ return toReturn == 1
+
+ }
+
+ companion object {
+ const val TAG = "PressToAuthInteractor"
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/Android.bp b/src/com/android/settings/biometrics/fingerprint2/lib/Android.bp
similarity index 64%
rename from src/com/android/settings/biometrics/fingerprint2/shared/Android.bp
rename to src/com/android/settings/biometrics/fingerprint2/lib/Android.bp
index 145f3d682ea586eb17f9a6641dace49aeb201957..9f753b2d5d74a765a4d19fb72548e49f5729ae8e 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/Android.bp
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/Android.bp
@@ -2,13 +2,17 @@
// unit/robo/screenshot etc.
//
// This library shouldn't have many dependencies.
+package {
+ default_team: "trendy_team_android_settings_app",
+}
+
android_library {
name: "FingerprintManagerInteractor",
srcs: [
- "**/*.kt"
+ "**/*.kt",
],
static_libs: [
- "BiometricsSharedLib",
- "kotlinx-coroutines-android",
+ "BiometricsSharedLib",
+ "kotlinx-coroutines-android",
],
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/AndroidManifest.xml b/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml
similarity index 91%
rename from src/com/android/settings/biometrics/fingerprint2/shared/AndroidManifest.xml
rename to src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml
index e2c97fc1fac1e4b9fa52fc71ae2095d37e171bbd..250f0af83ebb6048523a1d3c6c0d51f6af195c27 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/AndroidManifest.xml
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml
@@ -14,5 +14,5 @@
~ limitations under the License.
-->
+ package="com.android.settings.biometrics.fingerprint2.lib">
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt
similarity index 79%
rename from src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
rename to src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt
index 94afa49da99a1daf4444c9ff33679ca5b9b21e5f..c0e1b4aeaf4ef37fa48aca127744a5aa593afea2 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.shared.domain.interactor
+package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
-import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.systemui.biometrics.shared.model.FingerprintSensor
import kotlinx.coroutines.flow.Flow
@@ -56,12 +56,12 @@ interface FingerprintManagerInteractor {
/**
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
- * enrollment. Returning the [FingerEnrollState] that represents this fingerprint
- * enrollment state.
+ * enrollment. Returning the [FingerEnrollState] that represents this fingerprint enrollment
+ * state.
*/
suspend fun enroll(
- hardwareAuthToken: ByteArray?,
- enrollReason: EnrollReason,
+ hardwareAuthToken: ByteArray?,
+ enrollReason: EnrollReason,
): Flow
/**
@@ -75,8 +75,4 @@ interface FingerprintManagerInteractor {
/** Indicates if the device has side fingerprint */
suspend fun hasSideFps(): Boolean
-
- /** Indicates if the press to auth feature has been enabled */
- suspend fun pressToAuthEnabled(): Boolean
-
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReason.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/EnrollReason.kt
similarity index 93%
rename from src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReason.kt
rename to src/com/android/settings/biometrics/fingerprint2/lib/model/EnrollReason.kt
index 47a0af0de9749020d839b5542d5af2639d4bb0bc..3cc6497014e3bec3d2a0ea4850e12d2ff13ecdef 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReason.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/EnrollReason.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.shared.model
+package com.android.settings.biometrics.fingerprint2.lib.model
/** The reason for enrollment */
enum class EnrollReason {
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt
similarity index 81%
rename from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
rename to src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt
index 4766d599814c0ff7101c4826d1e9f8dd231d975d..683397fce029c72e82ea541a4e67f017b0941876 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.shared.model
+package com.android.settings.biometrics.fingerprint2.lib.model
import android.annotation.StringRes
@@ -28,16 +28,12 @@ sealed class FingerEnrollState {
*
* Progress is obtained by (totalStepsRequired - remainingSteps) / totalStepsRequired
*/
- data class EnrollProgress(
- val remainingSteps: Int,
- val totalStepsRequired: Int,
- ) : FingerEnrollState()
+ data class EnrollProgress(val remainingSteps: Int, val totalStepsRequired: Int) :
+ FingerEnrollState()
/** Represents that recoverable error has been encountered during enrollment. */
- data class EnrollHelp(
- @StringRes val helpMsgId: Int,
- val helpString: String,
- ) : FingerEnrollState()
+ data class EnrollHelp(@StringRes val helpMsgId: Int, val helpString: String) :
+ FingerEnrollState()
/** Represents that an unrecoverable error has been encountered and the operation is complete. */
data class EnrollError(
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerprintAuthAttemptModel.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerprintAuthAttemptModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..45259e9d915c21124065168397de725a2a7f4ca3
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerprintAuthAttemptModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 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.fingerprint2.lib.model
+
+/** Information indicating whether an auth was successful or not */
+sealed class FingerprintAuthAttemptModel {
+ /** Indicates a successful auth attempt has occurred for [fingerId] */
+ data class Success(val fingerId: Int) : FingerprintAuthAttemptModel()
+
+ /** Indicates a failed auth attempt has occurred. */
+ data class Error(val error: Int, val message: String) : FingerprintAuthAttemptModel()
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerprintData.kt
similarity index 67%
rename from src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
rename to src/com/android/settings/biometrics/fingerprint2/lib/model/FingerprintData.kt
index e776b9ab3e3f9aff04dd8a52b07c000c4843503f..62aa2c7ec37ce8dc645ad94198d9d62a2abe5520 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerprintData.kt
@@ -14,14 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.shared.data.repository
+package com.android.settings.biometrics.fingerprint2.lib.model
-/**
- * Interface that indicates if press to auth is on or off.
- */
-interface PressToAuthProvider {
- /**
- * Indicates true if the PressToAuth feature is enabled, false otherwise.
- */
- val isEnabled: Boolean
-}
\ No newline at end of file
+/** Basic information about an enrolled fingerprint */
+data class FingerprintData(val name: String, val fingerId: Int, val deviceId: Long)
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerprintFlow.kt
similarity index 83%
rename from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt
rename to src/com/android/settings/biometrics/fingerprint2/lib/model/FingerprintFlow.kt
index 93c75770d63d36fd4bad01409f5d03afa1c37f8f..40d7a03882c0a159603d5e1618ea2aa23263e00b 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerprintFlow.kt
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.shared.model
+package com.android.settings.biometrics.fingerprint2.lib.model
/**
- * The [FingerprintFlow] for fingerprint enrollment indicates information on how the flow should behave.
+ * The [FingerprintFlow] for fingerprint enrollment indicates information on how the flow should
+ * behave.
*/
sealed class FingerprintFlow
@@ -32,3 +33,6 @@ data object Unicorn : FingerprintFlow()
/** Flow to specify settings type */
data object Settings : FingerprintFlow()
+
+/** Indicates that the fast enroll experience should occur */
+data object FastEnroll : FingerprintFlow()
diff --git a/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt b/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt
deleted file mode 100644
index 38c5335ab0e390561bc87d942470da1607bec6d6..0000000000000000000000000000000000000000
--- a/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2023 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.fingerprint2.repository
-
-import android.content.Context
-import android.provider.Settings
-import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
-
-class PressToAuthProviderImpl(val context: Context) : PressToAuthProvider {
- override val isEnabled: Boolean
- get() {
- var toReturn: Int =
- Settings.Secure.getIntForUser(
- context.contentResolver,
- Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
- -1,
- context.userId,
- )
- if (toReturn == -1) {
- toReturn =
- if (
- context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
- ) {
- 1
- } else {
- 0
- }
- Settings.Secure.putIntForUser(
- context.contentResolver,
- Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
- toReturn,
- context.userId
- )
- }
- return (toReturn == 1)
- }
-}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
index de2a1eef3c123adcd25ce8b61f62cf718df2d4f5..d26b812a68679684c6ba119b6bdfc0bc601d6eac 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
@@ -21,7 +21,6 @@ import android.content.Intent
import android.content.res.Configuration
import android.hardware.fingerprint.FingerprintManager
import android.os.Bundle
-import android.provider.Settings
import android.util.Log
import android.view.accessibility.AccessibilityManager
import androidx.activity.result.contract.ActivityResultContracts
@@ -37,33 +36,42 @@ import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
import com.android.settings.biometrics.GatekeeperPasswordProvider
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
-import com.android.settings.biometrics.fingerprint2.shared.model.Default
-import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
-import com.android.settings.biometrics.fingerprint2.repository.PressToAuthProviderImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
+import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
+import com.android.settings.biometrics.fingerprint2.lib.model.Default
+import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollConfirmationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollIntroViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintFlowViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.ConfirmDeviceCredential
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Confirmation
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Init
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Introduction
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.TransitionStep
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FoldStateViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.LaunchConfirmDeviceCredential
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.OrientationStateViewModel
+import com.android.settings.flags.Flags
import com.android.settings.password.ChooseLockGeneric
import com.android.settings.password.ChooseLockSettingsHelper
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
@@ -71,7 +79,6 @@ import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.util.WizardManagerHelper
import com.google.android.setupdesign.util.ThemeHelper
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@@ -83,14 +90,16 @@ private const val TAG = "FingerprintEnrollmentV2Activity"
*/
class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel
- private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
+ private lateinit var navigationViewModel: FingerprintNavigationViewModel
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
- private lateinit var accessibilityViewModel: AccessibilityViewModel
- private lateinit var foldStateViewModel: FoldStateViewModel
- private lateinit var orientationStateViewModel: OrientationStateViewModel
+ private lateinit var foldStateInteractor: FoldStateInteractor
+ private lateinit var orientationInteractor: OrientationInteractor
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
private lateinit var backgroundViewModel: BackgroundViewModel
+ private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
+ private lateinit var fingerprintEnrollConfirmationViewModel:
+ FingerprintEnrollConfirmationViewModel
private val coroutineDispatcher = Dispatchers.Default
/** Result listener for ChooseLock activity flow. */
@@ -119,23 +128,46 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
super.onResume()
backgroundViewModel.inForeground()
}
+
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
- foldStateViewModel.onConfigurationChange(newConfig)
+ foldStateInteractor.onConfigurationChange(newConfig)
}
private fun onConfirmDevice(resultCode: Int, data: Intent?) {
val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK
val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long?
+
lifecycleScope.launch {
+ val confirmDeviceResult =
+ if (wasSuccessful) {
+ FingerprintAction.CONFIRM_DEVICE_SUCCESS
+ } else {
+ FingerprintAction.CONFIRM_DEVICE_FAIL
+ }
gatekeeperViewModel.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
+ navigationViewModel.update(
+ confirmDeviceResult,
+ ConfirmDeviceCredential::class,
+ "$TAG#onConfirmDevice",
+ )
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ // TODO(b/299573056): Show split screen dialog when it's in multi window mode.
setContentView(R.layout.fingerprint_v2_enroll_main)
+ if (!Flags.fingerprintV2Enrollment()) {
+ check(false) {
+ "fingerprint enrollment v2 is not enabled, " +
+ "please run adb shell device_config put " +
+ "biometrics_framework com.android.settings.flags.fingerprint_v2_enrollment true"
+ }
+ finish()
+ }
+
setTheme(SetupWizardUtils.getTheme(applicationContext, intent))
ThemeHelper.trySetDynamicColor(applicationContext)
@@ -155,57 +187,82 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
ViewModelProvider(this, BackgroundViewModel.BackgroundViewModelFactory())[
BackgroundViewModel::class.java]
+ fingerprintFlowViewModel =
+ ViewModelProvider(this, FingerprintFlowViewModel.FingerprintFlowViewModelFactory(enrollType))[
+ FingerprintFlowViewModel::class.java]
- val interactor =
+ val fingerprintSensorRepo =
+ FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
+ val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher)
+
+ val fingerprintManagerInteractor =
FingerprintManagerInteractorImpl(
context,
backgroundDispatcher,
fingerprintManager,
+ fingerprintSensorRepo,
GatekeeperPasswordProvider(LockPatternUtils(context)),
- PressToAuthProviderImpl(context),
+ pressToAuthInteractor,
enrollType,
+ getIntent(),
)
var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
val gatekeeperInfo = FingerprintGatekeeperViewModel.toGateKeeperInfo(challenge, token)
+ val hasConfirmedDeviceCredential = gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo
+
+ val accessibilityInteractor =
+ AccessibilityInteractorImpl(
+ getSystemService(AccessibilityManager::class.java)!!,
+ lifecycleScope,
+ )
+
+ navigationViewModel =
+ ViewModelProvider(
+ this,
+ FingerprintNavigationViewModel.FingerprintNavigationViewModelFactory(
+ Init,
+ hasConfirmedDeviceCredential,
+ fingerprintFlowViewModel,
+ fingerprintManagerInteractor,
+ ),
+ )[FingerprintNavigationViewModel::class.java]
+ // Initialize FingerprintEnrollIntroViewModel
+ ViewModelProvider(
+ this,
+ FingerprintEnrollIntroViewModel.FingerprintEnrollIntoViewModelFactory(
+ navigationViewModel,
+ fingerprintFlowViewModel,
+ fingerprintManagerInteractor,
+ ),
+ )[FingerprintEnrollIntroViewModel::class.java]
+
gatekeeperViewModel =
ViewModelProvider(
this,
FingerprintGatekeeperViewModel.FingerprintGatekeeperViewModelFactory(
gatekeeperInfo,
- interactor,
- )
+ fingerprintManagerInteractor,
+ ),
)[FingerprintGatekeeperViewModel::class.java]
- navigationViewModel =
- ViewModelProvider(
- this,
- FingerprintEnrollNavigationViewModel.FingerprintEnrollNavigationViewModelFactory(
- backgroundDispatcher,
- interactor,
- gatekeeperViewModel,
- gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo,
- enrollType,
- )
- )[FingerprintEnrollNavigationViewModel::class.java]
-
// Initialize FoldStateViewModel
- foldStateViewModel =
- ViewModelProvider(this, FoldStateViewModel.FoldStateViewModelFactory(context))[
- FoldStateViewModel::class.java]
- foldStateViewModel.onConfigurationChange(resources.configuration)
+ foldStateInteractor = FoldStateInteractorImpl(context)
+ foldStateInteractor.onConfigurationChange(resources.configuration)
+
+ orientationInteractor = OrientationInteractorImpl(context, lifecycleScope)
// Initialize FingerprintViewModel
fingerprintEnrollViewModel =
ViewModelProvider(
this,
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
- interactor,
+ fingerprintManagerInteractor,
gatekeeperViewModel,
navigationViewModel,
- )
+ ),
)[FingerprintEnrollViewModel::class.java]
// Initialize scroll view model
@@ -213,28 +270,14 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
FingerprintScrollViewModel::class.java]
- // Initialize AccessibilityViewModel
- accessibilityViewModel =
- ViewModelProvider(
- this,
- AccessibilityViewModel.AccessibilityViewModelFactory(
- getSystemService(AccessibilityManager::class.java)!!
- )
- )[AccessibilityViewModel::class.java]
-
- // Initialize OrientationViewModel
- orientationStateViewModel =
- ViewModelProvider(this, OrientationStateViewModel.OrientationViewModelFactory(context))[
- OrientationStateViewModel::class.java]
-
// Initialize FingerprintEnrollEnrollingViewModel
fingerprintEnrollEnrollingViewModel =
ViewModelProvider(
this,
FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingViewModelFactory(
fingerprintEnrollViewModel,
- backgroundViewModel
- )
+ backgroundViewModel,
+ ),
)[FingerprintEnrollEnrollingViewModel::class.java]
// Initialize FingerprintEnrollFindSensorViewModel
@@ -245,38 +288,66 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
fingerprintEnrollViewModel,
gatekeeperViewModel,
backgroundViewModel,
- accessibilityViewModel,
- foldStateViewModel,
- orientationStateViewModel
- )
+ accessibilityInteractor,
+ foldStateInteractor,
+ orientationInteractor,
+ fingerprintFlowViewModel,
+ fingerprintManagerInteractor,
+ ),
)[FingerprintEnrollFindSensorViewModel::class.java]
// Initialize RFPS View Model
ViewModelProvider(
this,
- RFPSViewModel.RFPSViewModelFactory(fingerprintEnrollEnrollingViewModel)
+ RFPSViewModel.RFPSViewModelFactory(
+ fingerprintEnrollEnrollingViewModel,
+ navigationViewModel,
+ orientationInteractor,
+ ),
)[RFPSViewModel::class.java]
+ fingerprintEnrollConfirmationViewModel =
+ ViewModelProvider(
+ this,
+ FingerprintEnrollConfirmationViewModel.FingerprintEnrollConfirmationViewModelFactory(
+ navigationViewModel,
+ fingerprintManagerInteractor,
+ ),
+ )[FingerprintEnrollConfirmationViewModel::class.java]
+
lifecycleScope.launch {
- navigationViewModel.navigationViewModel
- .filterNotNull()
- .combine(fingerprintEnrollViewModel.sensorType) { nav, sensorType -> Pair(nav, sensorType) }
- .collect { (nav, sensorType) ->
- Log.d(TAG, "navigationStep $nav")
- fingerprintEnrollViewModel.sensorTypeCached = sensorType
- val isForward = nav.forward
- val currStep = nav.currStep
- val theClass: Class? =
- when (currStep) {
- Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class
- Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class
+ navigationViewModel.currentStep.collect { step ->
+ if (step is Init) {
+ Log.d(TAG, "FingerprintNav.init($step)")
+ navigationViewModel.update(FingerprintAction.ACTIVITY_CREATED, Init::class, "$TAG#init")
+ }
+ }
+ }
+
+ lifecycleScope.launch {
+ navigationViewModel.navigateTo.filterNotNull().collect { step ->
+ Log.d(TAG, "navigateTo: $step")
+ if (step is ConfirmDeviceCredential) {
+ launchConfirmOrChooseLock(userId)
+ navigationViewModel.update(
+ FingerprintAction.TRANSITION_FINISHED,
+ TransitionStep::class,
+ "$TAG#launchConfirmOrChooseLock",
+ )
+ } else {
+ val theClass: Fragment? =
+ when (step) {
+ Confirmation -> FingerprintEnrollConfirmationV2Fragment()
+ is Education -> {
+ FingerprintEnrollFindSensorV2Fragment(step.sensor.sensorType)
+ }
is Enrollment -> {
- when (sensorType) {
- FingerprintSensorType.REAR -> RFPSEnrollFragment::class.java as Class
- else -> FingerprintEnrollEnrollingV2Fragment::class.java as Class
+ when (step.sensor.sensorType) {
+ FingerprintSensorType.REAR -> RFPSEnrollFragment()
+ else -> FingerprintEnrollEnrollingV2Fragment()
}
}
- Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class
+ Introduction -> FingerprintEnrollIntroV2Fragment()
else -> null
}
@@ -290,19 +361,31 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, theClass, null)
.commit()
- } else {
-
- if (currStep is Finish) {
- if (currStep.resultCode != null) {
- finishActivity(currStep.resultCode)
- } else {
- finish()
- }
- } else if (currStep == LaunchConfirmDeviceCredential) {
- launchConfirmOrChooseLock(userId)
- }
+ navigationViewModel.update(
+ FingerprintAction.TRANSITION_FINISHED,
+ TransitionStep::class,
+ "$TAG#fragmentManager.add($theClass)",
+ )
}
}
+ }
+ }
+
+ lifecycleScope.launch {
+ navigationViewModel.shouldFinish.filterNotNull().collect {
+ Log.d(TAG, "FingerprintSettingsNav.finishing($it)")
+ if (it.result != null) {
+ finishActivity(it.result as Int)
+ } else {
+ finish()
+ }
+ }
+ }
+
+ lifecycleScope.launch {
+ navigationViewModel.currentScreen.filterNotNull().collect { screen ->
+ Log.d(TAG, "FingerprintSettingsNav.currentScreen($screen)")
+ }
}
val fromSettingsSummary =
@@ -312,7 +395,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
) {
overridePendingTransition(
com.google.android.setupdesign.R.anim.sud_slide_next_in,
- com.google.android.setupdesign.R.anim.sud_slide_next_out
+ com.google.android.setupdesign.R.anim.sud_slide_next_out,
)
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollConfirmationV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollConfirmationV2Fragment.kt
index b12491f3afd924079f41104a29a23d6b6470e093..d8c1c93f9104b4cbf6f298718494aca1dd9854a2 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollConfirmationV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollConfirmationV2Fragment.kt
@@ -17,9 +17,21 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollConfirmationViewModel
+import com.google.android.setupcompat.template.FooterBarMixin
+import com.google.android.setupcompat.template.FooterButton
+import com.google.android.setupdesign.GlifLayout
+import kotlinx.coroutines.launch
/**
* A fragment to indicate that fingerprint enrollment has been completed.
@@ -27,13 +39,71 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Fing
* This page will display basic information about what a fingerprint can be used for and acts as the
* final step of enrollment.
*/
-class FingerprintEnrollConfirmationV2Fragment : Fragment() {
+class FingerprintEnrollConfirmationV2Fragment() :
+ Fragment(R.layout.fingerprint_enroll_finish_base) {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- if (savedInstanceState == null) {
- val navigationViewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
+ companion object {
+ const val TAG = "FingerprintEnrollConfirmationV2Fragment"
+ }
+
+ /** Used for testing purposes */
+ private var factory: ViewModelProvider.Factory? = null
+
+ @VisibleForTesting
+ constructor(theFactory: ViewModelProvider.Factory) : this() {
+ factory = theFactory
+ }
+
+ private val viewModelProvider: ViewModelProvider by lazy {
+ if (factory != null) {
+ ViewModelProvider(requireActivity(), factory!!)
+ } else {
+ ViewModelProvider(requireActivity())
+ }
+ }
+
+ private val viewModel: FingerprintEnrollConfirmationViewModel by lazy {
+ viewModelProvider[FingerprintEnrollConfirmationViewModel::class.java]
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View? =
+ super.onCreateView(inflater, container, savedInstanceState).also { theView ->
+ val mainView = theView!! as GlifLayout
+
+ mainView.setHeaderText(R.string.security_settings_fingerprint_enroll_finish_title)
+ mainView.setDescriptionText(R.string.security_settings_fingerprint_enroll_finish_v2_message)
+
+ val mixin = mainView.getMixin(FooterBarMixin::class.java)
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ viewModel.isAddAnotherButtonVisible.collect {
+ mixin.secondaryButton =
+ FooterButton.Builder(requireContext())
+ .setText(R.string.fingerprint_enroll_button_add)
+ .setListener { viewModel.onAddAnotherButtonClicked() }
+ .setButtonType(FooterButton.ButtonType.SKIP)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
+ .build()
+ }
+ }
+ }
+
+ mixin.setPrimaryButton(
+ FooterButton.Builder(requireContext())
+ .setText(R.string.security_settings_fingerprint_enroll_done)
+ .setListener(this::onNextButtonClick)
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+ .build()
+ )
}
+
+ @Suppress("UNUSED_PARAMETER")
+ private fun onNextButtonClick(view: View?) {
+ viewModel.onNextButtonClicked()
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
index 0140d57a2e127d720cd086505d8f1add4f2899a5..be30346d30354a32feba052859c1fe3185bbab09 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
@@ -18,18 +18,12 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
/** A fragment that is responsible for enrolling a users fingerprint. */
class FingerprintEnrollEnrollingV2Fragment : Fragment(R.layout.fingerprint_enroll_enrolling) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- if (savedInstanceState == null) {
- val navigationViewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
- }
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
index bfd426435a3682696af79ef33c69efe0c6c0c307..2b1ff9bf085b5f4795a2fae145658fa66b19aa20 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
@@ -22,6 +22,7 @@ import android.view.LayoutInflater
import android.view.Surface
import android.view.View
import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@@ -30,7 +31,6 @@ import com.android.settings.R
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
@@ -50,24 +50,40 @@ private const val TAG = "FingerprintEnrollFindSensorV2Fragment"
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
* will work.
*/
-class FingerprintEnrollFindSensorV2Fragment : Fragment() {
+class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorType) : Fragment() {
+ /** Used for testing purposes */
+ private var factory: ViewModelProvider.Factory? = null
+
+ @VisibleForTesting
+ constructor(
+ sensorType: FingerprintSensorType,
+ theFactory: ViewModelProvider.Factory,
+ ) : this(sensorType) {
+ factory = theFactory
+ }
+
+ private val viewModelProvider: ViewModelProvider by lazy {
+ if (factory != null) {
+ ViewModelProvider(requireActivity(), factory!!)
+ } else {
+ ViewModelProvider(requireActivity())
+ }
+ }
+
// This is only for non-udfps or non-sfps sensor. For udfps and sfps, we show lottie.
private var animation: FingerprintFindSensorAnimation? = null
private var contentLayoutId: Int = -1
private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
- ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
+ viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java]
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
- val sensorType =
- ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java].sensorTypeCached
-
contentLayoutId =
when (sensorType) {
FingerprintSensorType.UDFPS_OPTICAL,
@@ -76,47 +92,43 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
else -> R.layout.fingerprint_v2_enroll_find_sensor
}
- return inflater.inflate(contentLayoutId, container, false).also { it ->
- val view = it!! as GlifLayout
-
- // Set up header and description
- lifecycleScope.launch { viewModel.sensorType.collect { setTexts(it, view) } }
+ val view = inflater.inflate(contentLayoutId, container, false)!! as GlifLayout
+ setTexts(sensorType, view)
- // Set up footer bar
- val footerBarMixin = view.getMixin(FooterBarMixin::class.java)
- setupSecondaryButton(footerBarMixin)
- lifecycleScope.launch {
- viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
- }
+ // Set up footer bar
+ val footerBarMixin = view.getMixin(FooterBarMixin::class.java)
+ setupSecondaryButton(footerBarMixin)
+ lifecycleScope.launch {
+ viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
+ }
- // Set up lottie or animation
- lifecycleScope.launch {
- viewModel.sfpsLottieInfo.collect { (isFolded, rotation) ->
- setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation))
- }
+ // Set up lottie or animation
+ lifecycleScope.launch {
+ viewModel.sfpsLottieInfo.collect { (isFolded, rotation) ->
+ setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation))
}
- lifecycleScope.launch {
- viewModel.udfpsLottieInfo.collect { isAccessibilityEnabled ->
- val lottieAnimation =
- if (isAccessibilityEnabled) R.raw.udfps_edu_a11y_lottie else R.raw.udfps_edu_lottie
- setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() }
- }
+ }
+ lifecycleScope.launch {
+ viewModel.udfpsLottieInfo.collect { isAccessibilityEnabled ->
+ val lottieAnimation =
+ if (isAccessibilityEnabled) R.raw.udfps_edu_a11y_lottie else R.raw.udfps_edu_lottie
+ setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() }
}
- lifecycleScope.launch {
- viewModel.showRfpsAnimation.collect {
- animation =
- view.findViewById(R.id.fingerprint_sensor_location_animation)
- animation!!.startAnimation()
- }
+ }
+ lifecycleScope.launch {
+ viewModel.showRfpsAnimation.collect {
+ animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
+ animation!!.startAnimation()
}
+ }
- lifecycleScope.launch {
- viewModel.showErrorDialog.collect { (errMsgId, isSetup) ->
- // TODO: Covert error dialog kotlin as well
- FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup)
- }
+ lifecycleScope.launch {
+ viewModel.showErrorDialog.collect { (errMsgId, isSetup) ->
+ // TODO: Covert error dialog kotlin as well
+ FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup)
}
}
+ return view
}
override fun onDestroy() {
@@ -128,14 +140,7 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
footerBarMixin.secondaryButton =
FooterButton.Builder(requireActivity())
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
- .setListener {
- run {
- // TODO: Show the dialog for suw
- Log.d(TAG, "onSkipClicked")
- // TODO: Finish activity in the root activity instead.
- requireActivity().finish()
- }
- }
+ .setListener { viewModel.secondaryButtonClicked() }
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
@@ -146,10 +151,8 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
FooterButton.Builder(requireActivity())
.setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
.setListener {
- run {
- Log.d(TAG, "onStartButtonClick")
- viewModel.proceedToEnrolling()
- }
+ Log.d(TAG, "onStartButtonClick")
+ viewModel.proceedToEnrolling()
}
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
@@ -159,7 +162,7 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
private fun setupLottie(
view: View,
lottieAnimation: Int,
- lottieClickListener: View.OnClickListener? = null
+ lottieClickListener: View.OnClickListener? = null,
) {
val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
illustrationLottie?.setAnimation(lottieAnimation)
@@ -168,7 +171,7 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
illustrationLottie?.visibility = View.VISIBLE
}
- private fun setTexts(sensorType: FingerprintSensorType, view: GlifLayout) {
+ private fun setTexts(sensorType: FingerprintSensorType?, view: GlifLayout) {
when (sensorType) {
FingerprintSensorType.UDFPS_OPTICAL,
FingerprintSensorType.UDFPS_ULTRASONIC -> {
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
index 32d201d8c3d0d67e74a0b5a65c4d3a9213171687..53d0ddf1b0ea0916fdef3a33aeec4155c0ee4c4a 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
@@ -24,7 +24,6 @@ import android.graphics.PorterDuffColorFilter
import android.os.Bundle
import android.text.Html
import android.text.method.LinkMovementMethod
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -36,9 +35,8 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.Unicorn
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
+import com.android.settings.biometrics.fingerprint2.lib.model.Unicorn
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollIntroViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -48,6 +46,7 @@ import com.google.android.setupdesign.GlifLayout
import com.google.android.setupdesign.template.RequireScrollMixin
import com.google.android.setupdesign.util.DynamicColorPalette
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
private const val TAG = "FingerprintEnrollmentIntroV2Fragment"
@@ -96,16 +95,14 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
private lateinit var footerBarMixin: FooterBarMixin
private lateinit var textModel: TextModel
- // Note that the ViewModels cannot be requested before the onCreate call
- private val navigationViewModel: FingerprintEnrollNavigationViewModel by lazy {
- viewModelProvider[FingerprintEnrollNavigationViewModel::class.java]
- }
- private val fingerprintViewModel: FingerprintEnrollViewModel by lazy {
- viewModelProvider[FingerprintEnrollViewModel::class.java]
+ private val viewModel: FingerprintEnrollIntroViewModel by lazy {
+ viewModelProvider[FingerprintEnrollIntroViewModel::class.java]
}
+
private val fingerprintScrollViewModel: FingerprintScrollViewModel by lazy {
viewModelProvider[FingerprintScrollViewModel::class.java]
}
+
private val gateKeeperViewModel: FingerprintGatekeeperViewModel by lazy {
viewModelProvider[FingerprintGatekeeperViewModel::class.java]
}
@@ -113,17 +110,16 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? =
super.onCreateView(inflater, container, savedInstanceState).also { theView ->
val view = theView!!
viewLifecycleOwner.lifecycleScope.launch {
- combine(
- navigationViewModel.fingerprintFlow,
- fingerprintViewModel.sensorType,
- ) { enrollType, sensorType ->
- Pair(enrollType, sensorType)
+ combine(viewModel.fingerprintFlow, viewModel.sensor.filterNotNull()) {
+ enrollType,
+ sensorType ->
+ Pair(enrollType, sensorType.sensorType)
}
.collect { (enrollType, sensorType) ->
textModel =
@@ -147,7 +143,7 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
R.id.icon_trash_can,
R.id.icon_info,
R.id.icon_shield,
- R.id.icon_link
+ R.id.icon_link,
)
.forEach { icon ->
view.requireViewById(icon).drawable.colorFilter = colorFilter
@@ -186,31 +182,24 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
return view
}
- /**
- * TODO (b/305269201): This link isn't displaying for screenshot tests.
- */
+ /** TODO (b/305269201): This link isn't displaying for screenshot tests. */
private fun setFooterLink(view: View) {
val footerLink: TextView = view.requireViewById(R.id.footer_learn_more)
footerLink.movementMethod = LinkMovementMethod.getInstance()
footerLink.text =
Html.fromHtml(
getString(R.string.security_settings_fingerprint_v2_enroll_introduction_message_learn_more),
- Html.FROM_HTML_MODE_LEGACY
+ Html.FROM_HTML_MODE_LEGACY,
)
}
- private fun setupFooterBarAndScrollView(
- view: View,
- ) {
+ private fun setupFooterBarAndScrollView(view: View) {
val scrollView: ScrollView =
view.requireViewById(com.google.android.setupdesign.R.id.sud_scroll_view)
scrollView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
// Next button responsible for starting the next fragment.
val onNextButtonClick: View.OnClickListener =
- View.OnClickListener {
- Log.d(TAG, "OnNextClicked")
- navigationViewModel.nextStep()
- }
+ View.OnClickListener { viewModel.primaryButtonClicked() }
val layout: GlifLayout = view.findViewById(R.id.setup_wizard_layout)!!
footerBarMixin = layout.getMixin(FooterBarMixin::class.java)
@@ -225,11 +214,11 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
footerBarMixin.setSecondaryButton(
FooterButton.Builder(requireContext())
.setText(textModel.negativeButton)
- .setListener({ Log.d(TAG, "prevClicked") })
+ .setListener { viewModel.onSecondaryButtonClicked() }
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build(),
- true /* usePrimaryStyle */
+ true, /* usePrimaryStyle */
)
val primaryButton = footerBarMixin.primaryButton
@@ -242,7 +231,7 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
requireContext(),
primaryButton,
R.string.security_settings_face_enroll_introduction_more,
- onNextButtonClick
+ onNextButtonClick,
)
requireScrollMixin.setOnRequireScrollStateChangedListener { scrollNeeded: Boolean ->
@@ -257,7 +246,7 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
if (consented) {
primaryButton.setText(
requireContext(),
- R.string.security_settings_fingerprint_enroll_introduction_agree
+ R.string.security_settings_fingerprint_enroll_introduction_agree,
)
secondaryButton.visibility = View.VISIBLE
} else {
@@ -309,7 +298,7 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
private fun getIconColorFilter(): PorterDuffColorFilter {
return PorterDuffColorFilter(
DynamicColorPalette.getColor(context, DynamicColorPalette.ColorType.ACCENT),
- PorterDuff.Mode.SRC_IN
+ PorterDuff.Mode.SRC_IN,
)
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt
index d8c2f5a2a03707d057fc8fa8385e2bc95fd67110..a9cd16fef1e5ba56018a3c491dc0ecfdef447b34 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt
@@ -26,33 +26,51 @@ import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.view.animation.Interpolator
import android.widget.TextView
+import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.FingerprintErrorDialog
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.IconTouchDialog
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.OrientationStateViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
import com.google.android.setupdesign.GlifLayout
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
-private const val TAG = "RFPSEnrollFragment"
-
/** This fragment is responsible for taking care of rear fingerprint enrollment. */
-class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrolling) {
+class RFPSEnrollFragment() : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrolling) {
+
+ /** Used for testing purposes */
+ private var factory: ViewModelProvider.Factory? = null
+
+ @VisibleForTesting
+ constructor(theFactory: ViewModelProvider.Factory) : this() {
+ factory = theFactory
+ }
+
+ private val viewModelProvider: ViewModelProvider by lazy {
+ if (factory != null) {
+ ViewModelProvider(requireActivity(), factory!!)
+ } else {
+ ViewModelProvider(requireActivity())
+ }
+ }
private lateinit var linearOutSlowInInterpolator: Interpolator
private lateinit var fastOutLinearInInterpolator: Interpolator
@@ -60,25 +78,18 @@ class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrollin
private lateinit var progressBar: RFPSProgressBar
private val iconTouchViewModel: RFPSIconTouchViewModel by lazy {
- ViewModelProvider(requireActivity())[RFPSIconTouchViewModel::class.java]
+ viewModelProvider[RFPSIconTouchViewModel::class.java]
}
- private val orientationViewModel: OrientationStateViewModel by lazy {
- ViewModelProvider(requireActivity())[OrientationStateViewModel::class.java]
- }
-
- private val rfpsViewModel: RFPSViewModel by lazy {
- ViewModelProvider(requireActivity())[RFPSViewModel::class.java]
- }
+ private val rfpsViewModel: RFPSViewModel by lazy { viewModelProvider[RFPSViewModel::class.java] }
private val backgroundViewModel: BackgroundViewModel by lazy {
- ViewModelProvider(requireActivity())[BackgroundViewModel::class.java]
+ viewModelProvider[BackgroundViewModel::class.java]
}
-
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)!!
val fragment = this
@@ -99,7 +110,7 @@ class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrollin
footerBarMixin.secondaryButton =
FooterButton.Builder(context)
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
- .setListener { Log.e(TAG, "skip enrollment!") }
+ .setListener { rfpsViewModel.negativeButtonClicked() }
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
@@ -112,9 +123,8 @@ class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrollin
true
}
- // On any orientation event, dismiss dialogs.
viewLifecycleOwner.lifecycleScope.launch {
- orientationViewModel.orientation.collect { dismissDialogs() }
+ rfpsViewModel.shouldDismissDialog.collect { dismissDialogs() }
}
// Signal we are ready for enrollment.
@@ -124,6 +134,8 @@ class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrollin
repeatOnLifecycle(Lifecycle.State.RESUMED) {
// Icon animation update
viewLifecycleOwner.lifecycleScope.launch {
+ // TODO(b/324427704): Fix this delay
+ delay(100)
rfpsViewModel.shouldAnimateIcon.collect { animate ->
progressBar.updateIconAnimation(animate)
}
@@ -150,7 +162,7 @@ class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrollin
viewLifecycleOwner.lifecycleScope.launch {
backgroundViewModel.background
.filter { inBackground -> inBackground }
- .collect { rfpsViewModel.stopEnrollment() }
+ .collect { rfpsViewModel.didGoToBackground() }
}
viewLifecycleOwner.lifecycleScope.launch {
@@ -171,7 +183,6 @@ class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrollin
.setDuration(200)
.setInterpolator(linearOutSlowInInterpolator)
.start()
-
}
}
@@ -207,6 +218,12 @@ class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrollin
dismissDialogs()
}
}
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.didCompleteEnrollment
+ .filter { it }
+ .collect { rfpsViewModel.finishedSuccessfully() }
+ }
return view
}
@@ -215,29 +232,19 @@ class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrollin
viewLifecycleOwner.lifecycleScope.launch {
try {
val shouldRestartEnrollment = FingerprintErrorDialog.showInstance(error, fragment)
+ rfpsViewModel.userClickedStopEnrollDialog()
} catch (exception: Exception) {
Log.e(TAG, "Exception occurred $exception")
}
- onEnrollmentFailed()
}
}
- private fun onEnrollmentFailed() {
- rfpsViewModel.stopEnrollment()
- }
-
private fun handleEnrollProgress(progress: FingerEnrollState.EnrollProgress) {
progressBar.updateProgress(
progress.remainingSteps.toFloat() / progress.totalStepsRequired.toFloat()
)
-
- if (progress.remainingSteps == 0) {
- performNextStepSuccess()
- }
}
- private fun performNextStepSuccess() {}
-
private fun dismissDialogs() {
val transaction = parentFragmentManager.beginTransaction()
for (frag in parentFragmentManager.fragments) {
@@ -249,4 +256,9 @@ class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrollin
}
transaction.commitAllowingStateLoss()
}
+
+ companion object {
+ private const val TAG = "RFPSEnrollFragment"
+ private val navStep = FingerprintNavigationStep.Enrollment::class
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt
index c16e65cc9f2182fc7e631f40b1fcbb710179ef8f..cbcb1d4bbadf7471be8c6d34e2a7fc97dca08cac 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt
@@ -38,9 +38,9 @@ class RFPSIconTouchViewModel : ViewModel() {
private val _touches: MutableStateFlow = MutableStateFlow(0)
/**
- * Whether or not the UI should be showing the dialog. By making this SharingStarted.Eagerly
- * the first event 0 % 3 == 0 will fire as soon as this view model is created, so it should
- * be ignored and work as intended.
+ * Whether or not the UI should be showing the dialog. By making this SharingStarted.Eagerly the
+ * first event 0 % 3 == 0 will fire as soon as this view model is created, so it should be ignored
+ * and work as intended.
*/
val shouldShowDialog: Flow =
_touches
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
index 58d604e4407521d782126fa7bbe1cba75cfcf8a0..6ee57099ea2d326de5edef5c4ddff44ab7dca9af 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
@@ -19,13 +19,18 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrol
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.transform
@@ -34,16 +39,20 @@ import kotlinx.coroutines.flow.update
/** View Model used by the rear fingerprint enrollment fragment. */
class RFPSViewModel(
private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
+ private val navigationViewModel: FingerprintNavigationViewModel,
+ orientationInteractor: OrientationInteractor,
) : ViewModel() {
- /** Value to indicate if the text view is visible or not **/
private val _textViewIsVisible = MutableStateFlow(false)
+ /** Value to indicate if the text view is visible or not */
val textViewIsVisible: Flow = _textViewIsVisible.asStateFlow()
+ private var _shouldAnimateIcon: Flow =
+ fingerprintEnrollViewModel.enrollFlowShouldBeRunning
/** Indicates if the icon should be animating or not */
- val shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning
+ val shouldAnimateIcon = _shouldAnimateIcon
- private val enrollFlow: Flow = fingerprintEnrollViewModel.enrollFLow
+ private var enrollFlow: Flow = fingerprintEnrollViewModel.enrollFLow
/**
* Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
@@ -52,7 +61,7 @@ class RFPSViewModel(
val progress: Flow =
enrollFlow
.filterIsInstance()
- .shareIn(viewModelScope, SharingStarted.Eagerly, 1)
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 0)
/** Clear help message on enroll progress */
val clearHelpMessage: Flow = progress.map { it != null }
@@ -61,9 +70,8 @@ class RFPSViewModel(
val helpMessage: Flow =
enrollFlow
.filterIsInstance()
- .shareIn(viewModelScope, SharingStarted.Eagerly, 0).transform {
- _textViewIsVisible.update { true }
- }
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 0)
+ .transform { _textViewIsVisible.update { true } }
/**
* The error message should only be shown once, for scenarios like screen rotations, we don't want
@@ -74,6 +82,12 @@ class RFPSViewModel(
.filterIsInstance()
.shareIn(viewModelScope, SharingStarted.Eagerly, 0)
+ /** Indicates that enrollment was completed. */
+ val didCompleteEnrollment: Flow = progress.filterNotNull().map { it.remainingSteps == 0 }
+
+ /** Indicates if the fragment should dismiss a dialog if one was shown. */
+ val shouldDismissDialog = orientationInteractor.orientation.map { true }
+
/** Indicates if the consumer is ready for enrollment */
fun readyForEnrollment() {
fingerprintEnrollViewModel.canEnroll()
@@ -84,19 +98,72 @@ class RFPSViewModel(
fingerprintEnrollViewModel.stopEnroll()
}
+ /** Set the visibility of the text view */
fun setVisibility(isVisible: Boolean) {
_textViewIsVisible.update { isVisible }
}
+ /** Indicates that the user is done with trying to enroll a fingerprint */
+ fun userClickedStopEnrollDialog() {
+ navigationViewModel.update(
+ FingerprintAction.USER_CLICKED_FINISH,
+ navStep,
+ "${TAG}#userClickedStopEnrollingDialog",
+ )
+ }
+
+ /** Indicates that the application went to the background. */
+ fun didGoToBackground() {
+ navigationViewModel.update(
+ FingerprintAction.DID_GO_TO_BACKGROUND,
+ navStep,
+ "${TAG}#didGoToBackground",
+ )
+ stopEnrollment()
+ }
+
+ /** Indicates the negative button has been clicked */
+ fun negativeButtonClicked() {
+ doReset()
+ navigationViewModel.update(
+ FingerprintAction.NEGATIVE_BUTTON_PRESSED,
+ navStep,
+ "${TAG}negativeButtonClicked",
+ )
+ }
+
+ /** Indicates that an enrollment was completed */
+ fun finishedSuccessfully() {
+ doReset()
+ navigationViewModel.update(FingerprintAction.NEXT, navStep, "${TAG}#progressFinished")
+ }
+
+ private fun doReset() {
+ _textViewIsVisible.update { false }
+ _shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning
+ /** Indicates if the icon should be animating or not */
+ enrollFlow = fingerprintEnrollViewModel.enrollFLow
+ }
+
class RFPSViewModelFactory(
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
+ private val navigationViewModel: FingerprintNavigationViewModel,
+ private val orientationInteractor: OrientationInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
- return RFPSViewModel(fingerprintEnrollEnrollingViewModel) as T
+ override fun create(modelClass: Class): T {
+ return RFPSViewModel(
+ fingerprintEnrollEnrollingViewModel,
+ navigationViewModel,
+ orientationInteractor,
+ )
+ as T
}
}
+
+ companion object {
+ private val navStep = Enrollment::class
+ private const val TAG = "RFPSViewModel"
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt
index b9c628ed41264c7130a128890c9d7138d8a92222..9c0040b8ded93af8b2cdf8b3994f0d92a2d930f1 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt
@@ -24,7 +24,7 @@ import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -86,39 +86,28 @@ class FingerprintErrorDialog : InstrumentedDialogFragment() {
private const val KEY_TITLE = "fingerprint_title"
private const val KEY_SHOULD_TRY_AGAIN = "should_try_again"
- suspend fun showInstance(
- error: FingerEnrollState.EnrollError,
- fragment: Fragment,
- ) = suspendCancellableCoroutine { continuation ->
- val dialog = FingerprintErrorDialog()
- dialog.onTryAgain = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
+ suspend fun showInstance(error: FingerEnrollState.EnrollError, fragment: Fragment) =
+ suspendCancellableCoroutine { continuation ->
+ val dialog = FingerprintErrorDialog()
+ dialog.onTryAgain = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
- dialog.onContinue = DialogInterface.OnClickListener { _, _ -> continuation.resume(false) }
+ dialog.onContinue = DialogInterface.OnClickListener { _, _ -> continuation.resume(false) }
- dialog.onCancelListener =
- DialogInterface.OnCancelListener {
- Log.d(TAG, "onCancelListener clicked $dialog")
- continuation.resume(null)
- }
+ dialog.onCancelListener =
+ DialogInterface.OnCancelListener {
+ Log.d(TAG, "onCancelListener clicked $dialog")
+ continuation.resume(null)
+ }
- continuation.invokeOnCancellation { Log.d(TAG, "invokeOnCancellation $dialog") }
+ continuation.invokeOnCancellation { Log.d(TAG, "invokeOnCancellation $dialog") }
- val bundle = Bundle()
- bundle.putInt(
- KEY_TITLE,
- error.errTitle,
- )
- bundle.putInt(
- KEY_MESSAGE,
- error.errString,
- )
- bundle.putBoolean(
- KEY_SHOULD_TRY_AGAIN,
- error.shouldRetryEnrollment,
- )
- dialog.arguments = bundle
- Log.d(TAG, "showing dialog $dialog")
- dialog.show(fragment.parentFragmentManager, FingerprintErrorDialog::class.java.toString())
- }
+ val bundle = Bundle()
+ bundle.putInt(KEY_TITLE, error.errTitle)
+ bundle.putInt(KEY_MESSAGE, error.errString)
+ bundle.putBoolean(KEY_SHOULD_TRY_AGAIN, error.shouldRetryEnrollment)
+ dialog.arguments = bundle
+ Log.d(TAG, "showing dialog $dialog")
+ dialog.show(fragment.parentFragmentManager, FingerprintErrorDialog::class.java.toString())
+ }
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt
index fe6268107d320e1b99d72fec5fba1696ffcbbf36..5a6fc149b3e1744d7bd261bcf6188948884c3628 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt
@@ -24,14 +24,14 @@ import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.util.AttributeSet
+import android.util.Log
import android.view.animation.AnimationUtils
import android.view.animation.Interpolator
import com.android.settings.R
import com.android.settings.widget.RingProgressBar
/** Progress bar for rear fingerprint enrollment. */
-class RFPSProgressBar(context: Context, attributeSet: AttributeSet) :
- RingProgressBar(context, attributeSet) {
+class RFPSProgressBar : RingProgressBar {
private val fastOutSlowInInterpolator: Interpolator
@@ -42,9 +42,9 @@ class RFPSProgressBar(context: Context, attributeSet: AttributeSet) :
private var progressAnimation: ObjectAnimator? = null
- private var shouldAnimateInternal: Boolean = true
+ private var shouldAnimateInternal: Boolean = false
- init {
+ constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
val fingerprintDrawable = background as LayerDrawable
iconAnimationDrawable =
fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation)
@@ -52,10 +52,8 @@ class RFPSProgressBar(context: Context, attributeSet: AttributeSet) :
iconBackgroundBlinksDrawable =
fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background)
as AnimatedVectorDrawable
-
fastOutSlowInInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in)
-
iconAnimationDrawable.registerAnimationCallback(
object : Animatable2.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
@@ -66,7 +64,6 @@ class RFPSProgressBar(context: Context, attributeSet: AttributeSet) :
}
}
)
- animateIconAnimationInternal()
progressBackgroundTintMode = PorterDuff.Mode.SRC
@@ -85,8 +82,8 @@ class RFPSProgressBar(context: Context, attributeSet: AttributeSet) :
}
shouldAnimateInternal = shouldAnimate
- }
+ }
/** This function should only be called when actual progress has been made. */
fun updateProgress(percentComplete: Float) {
val progress = maxProgress - (percentComplete.coerceIn(0.0f, 100.0f) * maxProgress).toInt()
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d9b31d7f6220ff1ac7f4782f83c8bc1e0e84036b
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.fingerprint2.ui.enrollment.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Models the UI state for [FingerprintEnrollConfirmationV2Fragment]
+ */
+class FingerprintEnrollConfirmationViewModel(
+ private val navigationViewModel: FingerprintNavigationViewModel,
+ fingerprintInteractor: FingerprintManagerInteractor,
+) : ViewModel() {
+
+ /**
+ * Indicates if the add another button is possible. This should only be true when the user is able
+ * to enroll more fingerprints.
+ */
+ val isAddAnotherButtonVisible: Flow = fingerprintInteractor.canEnrollFingerprints
+
+ /**
+ * Indicates that the user has clicked the next button and is done with fingerprint enrollment.
+ */
+ fun onNextButtonClicked() {
+ navigationViewModel.update(FingerprintAction.NEXT, navStep, "onNextButtonClicked")
+ }
+
+ /**
+ * Indicates that the user has clicked the add another button and will be sent to the enrollment
+ * screen.
+ */
+ fun onAddAnotherButtonClicked() {
+ navigationViewModel.update(FingerprintAction.ADD_ANOTHER, navStep, "onAddAnotherButtonClicked")
+ }
+
+ class FingerprintEnrollConfirmationViewModelFactory(
+ private val navigationViewModel: FingerprintNavigationViewModel,
+ private val fingerprintInteractor: FingerprintManagerInteractor,
+ ) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return FingerprintEnrollConfirmationViewModel(navigationViewModel, fingerprintInteractor) as T
+ }
+ }
+
+ companion object {
+ private const val TAG = "FingerprintEnrollConfirmationViewModel"
+ private val navStep = FingerprintNavigationStep.Confirmation::class
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
index 7ab315e7201f341f5368671f295423c244798be7..63182bb6bb90ccb3fb12ad40576f14a899443479 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
@@ -25,8 +25,8 @@ import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update
/**
- * This class is a wrapper around the [FingerprintEnrollViewModel] and decides when
- * the user should or should not be enrolling.
+ * This class is a wrapper around the [FingerprintEnrollViewModel] and decides when the user should
+ * or should not be enrolling.
*/
class FingerprintEnrollEnrollingViewModel(
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
@@ -72,12 +72,10 @@ class FingerprintEnrollEnrollingViewModel(
class FingerprintEnrollEnrollingViewModelFactory(
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
- private val backgroundViewModel: BackgroundViewModel
+ private val backgroundViewModel: BackgroundViewModel,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
+ override fun create(modelClass: Class): T {
return FingerprintEnrollEnrollingViewModel(fingerprintEnrollViewModel, backgroundViewModel)
as T
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
index 7722a46fc7c615675800b754785768dc1c10284c..3bf806b7eaf30a42074d926ceba0390d648e245f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
@@ -19,38 +19,41 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
-import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
+import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
+import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/** Models the UI state for [FingerprintEnrollFindSensorV2Fragment]. */
class FingerprintEnrollFindSensorViewModel(
- private val navigationViewModel: FingerprintEnrollNavigationViewModel,
+ private val navigationViewModel: FingerprintNavigationViewModel,
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
backgroundViewModel: BackgroundViewModel,
- accessibilityViewModel: AccessibilityViewModel,
- foldStateViewModel: FoldStateViewModel,
- orientationStateViewModel: OrientationStateViewModel
+ accessibilityInteractor: AccessibilityInteractor,
+ foldStateInteractor: FoldStateInteractor,
+ orientationInteractor: OrientationInteractor,
+ fingerprintFlowViewModel: FingerprintFlowViewModel,
+ fingerprintManagerInteractor: FingerprintManagerInteractor,
) : ViewModel() {
+
/** Represents the stream of sensor type. */
val sensorType: Flow =
- fingerprintEnrollViewModel.sensorType.shareIn(
- viewModelScope,
- SharingStarted.WhileSubscribed(),
- 1
- )
+ fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
private val _isUdfps: Flow =
sensorType.map {
it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC
@@ -66,8 +69,8 @@ class FingerprintEnrollFindSensorViewModel(
val sfpsLottieInfo: Flow> =
combineTransform(
_showSfpsLottie,
- foldStateViewModel.isFolded,
- orientationStateViewModel.rotation,
+ foldStateInteractor.isFolded,
+ orientationInteractor.rotation,
) { _, isFolded, rotation ->
emit(Pair(isFolded, rotation))
}
@@ -75,7 +78,7 @@ class FingerprintEnrollFindSensorViewModel(
private val _showUdfpsLottie = _isUdfps.filter { it }
/** Represents the stream of showing udfps lottie and whether accessibility is enabled. */
val udfpsLottieInfo: Flow =
- _showUdfpsLottie.combine(accessibilityViewModel.isAccessibilityEnabled) {
+ _showUdfpsLottie.combine(accessibilityInteractor.isAccessibilityEnabled) {
_,
isAccessibilityEnabled ->
isAccessibilityEnabled
@@ -100,13 +103,13 @@ class FingerprintEnrollFindSensorViewModel(
// Start or end enroll flow
viewModelScope.launch {
combine(
- fingerprintEnrollViewModel.sensorType,
+ sensorType,
gatekeeperViewModel.hasValidGatekeeperInfo,
gatekeeperViewModel.gatekeeperInfo,
- navigationViewModel.navigationViewModel
- ) { sensorType, hasValidGatekeeperInfo, gatekeeperInfo, navigationViewModel ->
+ navigationViewModel.currentScreen,
+ ) { sensorType, hasValidGatekeeperInfo, gatekeeperInfo, currStep ->
val shouldStartEnroll =
- navigationViewModel.currStep == Education &&
+ currStep is Education &&
sensorType != FingerprintSensorType.UDFPS_OPTICAL &&
sensorType != FingerprintSensorType.UDFPS_ULTRASONIC &&
hasValidGatekeeperInfo
@@ -128,22 +131,19 @@ class FingerprintEnrollFindSensorViewModel(
// Only collect the flow when we should be running.
if (it) {
combine(
- navigationViewModel.fingerprintFlow,
fingerprintEnrollViewModel.educationEnrollFlow.filterNotNull(),
- ) { enrollType, educationFlow ->
- Pair(enrollType, educationFlow)
+ fingerprintFlowViewModel.fingerprintFlow,
+ ) { educationFlow, type ->
+ Pair(educationFlow, type)
}
- .collect { (enrollType, educationFlow) ->
+ .collect { (educationFlow, type) ->
when (educationFlow) {
- // TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding
- // to
- // Enrolling page. Otherwise Enrolling page will receive the EnrollError.
is FingerEnrollState.EnrollProgress -> proceedToEnrolling()
is FingerEnrollState.EnrollError -> {
if (educationFlow.isCancelled) {
proceedToEnrolling()
} else {
- _showErrorDialog.update { Pair(educationFlow.errString, enrollType == SetupWizard) }
+ _showErrorDialog.update { Pair(educationFlow.errString, type == SetupWizard) }
}
}
is FingerEnrollState.EnrollHelp -> {}
@@ -169,17 +169,29 @@ class FingerprintEnrollFindSensorViewModel(
/** Proceed to EnrollEnrolling page. */
fun proceedToEnrolling() {
- navigationViewModel.nextStep()
+ stopEducation()
+ navigationViewModel.update(FingerprintAction.NEXT, navStep, "$TAG#proceedToEnrolling")
+ }
+
+ /** Indicates the secondary button has been clicked */
+ fun secondaryButtonClicked() {
+ navigationViewModel.update(
+ FingerprintAction.NEGATIVE_BUTTON_PRESSED,
+ navStep,
+ "${TAG}#secondaryButtonClicked",
+ )
}
class FingerprintEnrollFindSensorViewModelFactory(
- private val navigationViewModel: FingerprintEnrollNavigationViewModel,
+ private val navigationViewModel: FingerprintNavigationViewModel,
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
private val backgroundViewModel: BackgroundViewModel,
- private val accessibilityViewModel: AccessibilityViewModel,
- private val foldStateViewModel: FoldStateViewModel,
- private val orientationStateViewModel: OrientationStateViewModel
+ private val accessibilityInteractor: AccessibilityInteractor,
+ private val foldStateInteractor: FoldStateInteractor,
+ private val orientationInteractor: OrientationInteractor,
+ private val fingerprintFlowViewModel: FingerprintFlowViewModel,
+ private val fingerprintManagerInteractor: FingerprintManagerInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun create(modelClass: Class): T {
@@ -188,11 +200,18 @@ class FingerprintEnrollFindSensorViewModel(
fingerprintEnrollViewModel,
gatekeeperViewModel,
backgroundViewModel,
- accessibilityViewModel,
- foldStateViewModel,
- orientationStateViewModel
+ accessibilityInteractor,
+ foldStateInteractor,
+ orientationInteractor,
+ fingerprintFlowViewModel,
+ fingerprintManagerInteractor,
)
as T
}
}
+
+ companion object {
+ private const val TAG = "FingerprintEnrollFindSensorViewModel"
+ private val navStep = Education::class
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3bf003c0c901b0281012b2baefbb2a000aa5a94c
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.fingerprint2.ui.enrollment.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Introduction
+import com.android.systemui.biometrics.shared.model.FingerprintSensor
+import kotlinx.coroutines.flow.Flow
+
+/** A view model for fingerprint enroll introduction. */
+class FingerprintEnrollIntroViewModel(
+ val navigationViewModel: FingerprintNavigationViewModel,
+ private val fingerprintFlowViewModel: FingerprintFlowViewModel,
+ private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+) : ViewModel() {
+
+ /** Represents a stream of [FingerprintSensor] */
+ val sensor: Flow = fingerprintManagerInteractor.sensorPropertiesInternal
+
+ /** Represents a stream of [FingerprintFlow] */
+ val fingerprintFlow: Flow = fingerprintFlowViewModel.fingerprintFlow
+
+ /** Indicates the primary button has been clicked */
+ fun primaryButtonClicked() {
+ navigationViewModel.update(FingerprintAction.NEXT, navStep, "${TAG}#onNextClicked")
+ }
+
+ /** Indicates the secondary button has been clicked */
+ fun onSecondaryButtonClicked() {
+ navigationViewModel.update(
+ FingerprintAction.NEGATIVE_BUTTON_PRESSED,
+ navStep,
+ "${TAG}#negativeButtonClicked",
+ )
+ }
+
+ class FingerprintEnrollIntoViewModelFactory(
+ val navigationViewModel: FingerprintNavigationViewModel,
+ val fingerprintFlowViewModel: FingerprintFlowViewModel,
+ val fingerprintManagerInteractor: FingerprintManagerInteractor,
+ ) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return FingerprintEnrollIntroViewModel(
+ navigationViewModel,
+ fingerprintFlowViewModel,
+ fingerprintManagerInteractor,
+ )
+ as T
+ }
+ }
+
+ companion object {
+ val navStep = Introduction::class
+ private const val TAG = "FingerprintEnrollIntroViewModel"
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
index c7a1071c6a65c506f4ea9faeccf39f621c53a73c..c27808db43487c29b27a93fd738337e9e2938dae 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
@@ -18,9 +18,11 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
-import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -34,7 +36,7 @@ import kotlinx.coroutines.flow.transformLatest
class FingerprintEnrollViewModel(
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
gatekeeperViewModel: FingerprintGatekeeperViewModel,
- navigationViewModel: FingerprintEnrollNavigationViewModel,
+ val navigationViewModel: FingerprintNavigationViewModel,
) : ViewModel() {
/**
@@ -45,8 +47,8 @@ class FingerprintEnrollViewModel(
*/
var sensorTypeCached: FingerprintSensorType? = null
private var _enrollReason: Flow =
- navigationViewModel.navigationViewModel.map {
- when (it.currStep) {
+ navigationViewModel.currentScreen.map {
+ when (it) {
is Enrollment -> EnrollReason.EnrollEnrolling
is Education -> EnrollReason.FindSensor
else -> null
@@ -54,7 +56,7 @@ class FingerprintEnrollViewModel(
}
/** Represents the stream of [FingerprintSensorType] */
- val sensorType: Flow =
+ val sensorType: Flow =
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
/**
@@ -64,8 +66,7 @@ class FingerprintEnrollViewModel(
* This flow should be the only flow which calls enroll().
*/
val _enrollFlow: Flow =
- combine(gatekeeperViewModel.gatekeeperInfo, _enrollReason) { hardwareAuthToken, enrollReason,
- ->
+ combine(gatekeeperViewModel.gatekeeperInfo, _enrollReason) { hardwareAuthToken, enrollReason ->
Pair(hardwareAuthToken, enrollReason)
}
.transformLatest {
@@ -110,18 +111,11 @@ class FingerprintEnrollViewModel(
class FingerprintEnrollViewModelFactory(
val interactor: FingerprintManagerInteractor,
val gatekeeperViewModel: FingerprintGatekeeperViewModel,
- val navigationViewModel: FingerprintEnrollNavigationViewModel,
+ val navigationViewModel: FingerprintNavigationViewModel,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
- return FingerprintEnrollViewModel(
- interactor,
- gatekeeperViewModel,
- navigationViewModel,
- )
- as T
+ override fun create(modelClass: Class): T {
+ return FingerprintEnrollViewModel(interactor, gatekeeperViewModel, navigationViewModel) as T
}
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
deleted file mode 100644
index 2e5dce0939ee91ca2beef5c5db7b4f8a2eee77b4..0000000000000000000000000000000000000000
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2023 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.fingerprint2.ui.enrollment.viewmodel
-
-import android.util.Log
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.shareIn
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-
-private const val TAG = "FingerprintEnrollNavigationViewModel"
-
-/**
- * This class is responsible for sending a [NavigationStep] which indicates where the user is in the
- * Fingerprint Enrollment flow
- */
-class FingerprintEnrollNavigationViewModel(
- private val dispatcher: CoroutineDispatcher,
- private val fingerprintManagerInteractor: FingerprintManagerInteractor,
- private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
- private val firstStep: NextStepViewModel,
- private val navState: NavState,
- private val theFingerprintFlow: FingerprintFlow,
-) : ViewModel() {
-
- private class InternalNavigationStep(
- lastStep: NextStepViewModel,
- nextStep: NextStepViewModel,
- forward: Boolean,
- var canNavigate: Boolean,
- ) : NavigationStep(lastStep, nextStep, forward)
-
- private var _fingerprintFlow = MutableStateFlow(theFingerprintFlow)
-
- /** A flow that indicates the [FingerprintFlow] */
- val fingerprintFlow: Flow = _fingerprintFlow.asStateFlow()
-
- private val _navigationStep =
- MutableStateFlow(
- InternalNavigationStep(PlaceHolderState, firstStep, forward = false, canNavigate = true)
- )
-
- init {
- viewModelScope.launch {
- gatekeeperViewModel.credentialConfirmed.filterNotNull().collect {
- if (_navigationStep.value.currStep is LaunchConfirmDeviceCredential) {
- if (it) nextStep() else finish()
- }
- }
- }
- }
-
- /**
- * A flow that contains the [NavigationStep] used to indicate where in the enrollment process the
- * user is.
- */
- val navigationViewModel: Flow = _navigationStep.asStateFlow()
-
- /** This action indicates that the UI should actually update the navigation to the given step. */
- val navigationAction: Flow =
- _navigationStep.shareIn(viewModelScope, SharingStarted.Lazily, 0)
-
- /** Used to start the next step of Fingerprint Enrollment. */
- fun nextStep() {
- viewModelScope.launch {
- val currStep = _navigationStep.value.currStep
- val nextStep = currStep.next(navState)
- Log.d(TAG, "nextStep(${currStep} -> $nextStep)")
- _navigationStep.update {
- InternalNavigationStep(currStep, nextStep, forward = true, canNavigate = false)
- }
- }
- }
-
- /** Go back a step of fingerprint enrollment. */
- fun prevStep() {
- viewModelScope.launch {
- val currStep = _navigationStep.value.currStep
- val nextStep = currStep.prev(navState)
- _navigationStep.update {
- InternalNavigationStep(currStep, nextStep, forward = false, canNavigate = false)
- }
- }
- }
-
- private fun finish() {
- _navigationStep.update {
- InternalNavigationStep(Finish(null), Finish(null), forward = false, canNavigate = false)
- }
- }
-
- class FingerprintEnrollNavigationViewModelFactory(
- private val backgroundDispatcher: CoroutineDispatcher,
- private val fingerprintManagerInteractor: FingerprintManagerInteractor,
- private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
- private val canSkipConfirm: Boolean,
- private val fingerprintFlow: FingerprintFlow,
- ) : ViewModelProvider.Factory {
-
- @Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
-
- val navState = NavState(canSkipConfirm)
- return FingerprintEnrollNavigationViewModel(
- backgroundDispatcher,
- fingerprintManagerInteractor,
- fingerprintGatekeeperViewModel,
- Start.next(navState),
- navState,
- fingerprintFlow,
- )
- as T
- }
- }
-}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintFlowViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintFlowViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f07652419d08ba1892f6c486b22c691df6a273e8
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintFlowViewModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.fingerprint2.ui.enrollment.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.shareIn
+
+class FingerprintFlowViewModel(private val fingerprintFlowType: FingerprintFlow) : ViewModel() {
+ val fingerprintFlow: Flow =
+ flowOf(fingerprintFlowType).shareIn(viewModelScope, SharingStarted.Eagerly, 1)
+
+ class FingerprintFlowViewModelFactory(val flowType: FingerprintFlow) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return FingerprintFlowViewModel(flowType) as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
index db93d022266f2a7cfffc2b0f10ecc8f976f9e194..322be6af1002c75ca1c2391fb65026725595171d 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
@@ -21,7 +21,7 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -29,11 +29,11 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-private const val TAG = "FingerprintGatekeeperViewModel"
-
sealed interface GatekeeperInfo {
object Invalid : GatekeeperInfo
+
object Timeout : GatekeeperInfo
+
data class GatekeeperPasswordInfo(val token: ByteArray?, val passwordHandle: Long?) :
GatekeeperInfo
}
@@ -97,7 +97,19 @@ class FingerprintGatekeeperViewModel(
}
}
+ class FingerprintGatekeeperViewModelFactory(
+ private val gatekeeperInfo: GatekeeperInfo?,
+ private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+ ) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return FingerprintGatekeeperViewModel(gatekeeperInfo, fingerprintManagerInteractor) as T
+ }
+ }
+
companion object {
+ private const val TAG = "FingerprintGatekeeperViewModel"
/**
* A function that checks if the challenge and token are valid, in which case a
* [GatekeeperInfo.GatekeeperPasswordInfo] is provided, else [GatekeeperInfo.Invalid]
@@ -110,17 +122,4 @@ class FingerprintGatekeeperViewModel(
return GatekeeperInfo.GatekeeperPasswordInfo(token, challenge)
}
}
-
- class FingerprintGatekeeperViewModelFactory(
- private val gatekeeperInfo: GatekeeperInfo?,
- private val fingerprintManagerInteractor: FingerprintManagerInteractor,
- ) : ViewModelProvider.Factory {
-
- @Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
- return FingerprintGatekeeperViewModel(gatekeeperInfo, fingerprintManagerInteractor) as T
- }
- }
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationStep.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationStep.kt
new file mode 100644
index 0000000000000000000000000000000000000000..76b4895f2bb7af23ed57b6e386266dceab365d5d
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationStep.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 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.fingerprint2.ui.enrollment.viewmodel
+
+import com.android.settings.biometrics.fingerprint2.lib.model.FastEnroll
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
+import com.android.systemui.biometrics.shared.model.FingerprintSensor
+
+/**
+ * A [FingerprintAction] event notifies the current [FingerprintNavigationStep] that an event
+ * occurred. Depending on the type of [FingerprintAction] and the current
+ * [FingerprintNavigationStep], the navstep will potentially produce a new
+ * [FingerprintNavigationStep] indicating either 1). Control flow has changed 2). The activity has
+ * finished 3). A transition is required
+ */
+enum class FingerprintAction {
+ NEXT,
+ PREV,
+ CONFIRM_DEVICE_SUCCESS,
+ CONFIRM_DEVICE_FAIL,
+ TRANSITION_FINISHED,
+ DID_GO_TO_BACKGROUND,
+ ACTIVITY_CREATED,
+ NEGATIVE_BUTTON_PRESSED,
+ USER_CLICKED_FINISH,
+ ADD_ANOTHER,
+}
+
+/** State that can be used to help a [FingerprintNavigationStep] determine the next step to take. */
+data class NavigationState(
+ val flowType: FingerprintFlow,
+ val hasConfirmedDeviceCredential: Boolean,
+ val fingerprintSensor: FingerprintSensor?,
+)
+
+/**
+ * A generic interface for operating on (state, action) -> state? which will produce either another
+ * FingerprintNavStep if something is required, or nothing.
+ *
+ * Note during the lifetime of the Activity, their should only be one [FingerprintNavigationStep] at
+ * a time.
+ */
+sealed interface FingerprintNavigationStep {
+ fun update(state: NavigationState, action: FingerprintAction): FingerprintNavigationStep?
+
+ /**
+ * This indicates that a transition should occur from one screen to another. This class should
+ * contain all necessary info about the transition.
+ *
+ * A transition step will cause a screen to change ownership from the current screen to the
+ * [nextUiStep], after the transition has been completed and a
+ * [FingerprintAction.TRANSITION_FINISHED] has been sent, the [nextUiStep] will be given control.
+ */
+ class TransitionStep(val nextUiStep: UiStep) : FingerprintNavigationStep {
+ override fun update(
+ state: NavigationState,
+ action: FingerprintAction,
+ ): FingerprintNavigationStep? {
+ return when (action) {
+ FingerprintAction.TRANSITION_FINISHED -> nextUiStep
+ else -> null
+ }
+ }
+
+ override fun toString(): String = "TransitionStep(nextUiStep=$nextUiStep)"
+ }
+
+ /** Indicates we should finish the enrolling activity */
+ data class Finish(val result: Int?) : FingerprintNavigationStep {
+ override fun update(
+ state: NavigationState,
+ action: FingerprintAction,
+ ): FingerprintNavigationStep? = null
+ }
+
+ /** UiSteps should have a 1 to 1 mapping between each screen of FingerprintEnrollment */
+ sealed class UiStep : FingerprintNavigationStep
+
+ /** This is the landing page for enrollment, where no content is shown. */
+ data object Init : UiStep() {
+ override fun update(
+ state: NavigationState,
+ action: FingerprintAction,
+ ): FingerprintNavigationStep? {
+ return when (action) {
+ FingerprintAction.ACTIVITY_CREATED -> {
+ if (!state.hasConfirmedDeviceCredential) {
+ TransitionStep(ConfirmDeviceCredential)
+ } else if (state.flowType is FastEnroll) {
+ TransitionStep(Enrollment(state.fingerprintSensor!!))
+ } else {
+ TransitionStep(Introduction)
+ }
+ }
+ else -> null
+ }
+ }
+ }
+
+ /** Indicates the ConfirmDeviceCredential activity is being presented to the user */
+ data object ConfirmDeviceCredential : UiStep() {
+ override fun update(
+ state: NavigationState,
+ action: FingerprintAction,
+ ): FingerprintNavigationStep? {
+ return when (action) {
+ FingerprintAction.CONFIRM_DEVICE_SUCCESS -> TransitionStep(Introduction)
+ FingerprintAction.CONFIRM_DEVICE_FAIL -> Finish(null)
+ else -> null
+ }
+ }
+ }
+
+ /** Indicates the FingerprintIntroduction screen is being presented to the user */
+ data object Introduction : UiStep() {
+ override fun update(
+ state: NavigationState,
+ action: FingerprintAction,
+ ): FingerprintNavigationStep? {
+ return when (action) {
+ FingerprintAction.NEXT -> TransitionStep(Education(state.fingerprintSensor!!))
+ FingerprintAction.NEGATIVE_BUTTON_PRESSED,
+ FingerprintAction.PREV -> Finish(null)
+ else -> null
+ }
+ }
+ }
+
+ /** Indicates the FingerprintEducation screen is being presented to the user */
+ data class Education(val sensor: FingerprintSensor) : UiStep() {
+ override fun update(
+ state: NavigationState,
+ action: FingerprintAction,
+ ): FingerprintNavigationStep? {
+ return when (action) {
+ FingerprintAction.NEXT -> TransitionStep(Enrollment(state.fingerprintSensor!!))
+ FingerprintAction.NEGATIVE_BUTTON_PRESSED,
+ FingerprintAction.PREV -> TransitionStep(Introduction)
+ else -> null
+ }
+ }
+ }
+
+ /** Indicates the Enrollment screen is being presented to the user */
+ data class Enrollment(val sensor: FingerprintSensor) : UiStep() {
+ override fun update(
+ state: NavigationState,
+ action: FingerprintAction,
+ ): FingerprintNavigationStep? {
+ return when (action) {
+ FingerprintAction.NEXT -> TransitionStep(Confirmation)
+ FingerprintAction.NEGATIVE_BUTTON_PRESSED,
+ FingerprintAction.USER_CLICKED_FINISH,
+ FingerprintAction.DID_GO_TO_BACKGROUND -> Finish(null)
+ else -> null
+ }
+ }
+ }
+
+ /** Indicates the Confirmation screen is being presented to the user */
+ data object Confirmation : UiStep() {
+ override fun update(
+ state: NavigationState,
+ action: FingerprintAction,
+ ): FingerprintNavigationStep? {
+ return when (action) {
+ FingerprintAction.NEXT -> Finish(null)
+ FingerprintAction.PREV -> TransitionStep(Education(state.fingerprintSensor!!))
+ FingerprintAction.ADD_ANOTHER -> TransitionStep(Enrollment(state.fingerprintSensor!!))
+ else -> null
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..131f5bb1e96e64a1ff6fc3298835afbc7cad3504
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 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.fingerprint2.ui.enrollment.viewmodel
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Finish
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.TransitionStep
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.UiStep
+import java.lang.NullPointerException
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+/**
+ * This class is essentially a wrapper around [FingerprintNavigationStep] that will be used by
+ * fragments/viewmodels that want to consume these events. It should provide no additional
+ * functionality beyond what is available in [FingerprintNavigationStep].
+ */
+class FingerprintNavigationViewModel(
+ step: UiStep,
+ hasConfirmedDeviceCredential: Boolean,
+ flowViewModel: FingerprintFlowViewModel,
+ fingerprintManagerInteractor: FingerprintManagerInteractor,
+) : ViewModel() {
+
+ private var _navStateInternal: MutableStateFlow = MutableStateFlow(null)
+
+ init {
+ viewModelScope.launch {
+ flowViewModel.fingerprintFlow
+ .combineTransform(fingerprintManagerInteractor.sensorPropertiesInternal) { flow, props ->
+ if (props?.sensorId != -1) {
+ emit(NavigationState(flow, hasConfirmedDeviceCredential, props))
+ }
+ }
+ .collect { navState -> _navStateInternal.update { navState } }
+ }
+ }
+
+ private var _currentStep = MutableStateFlow(step)
+
+ private var _navigateTo: MutableStateFlow = MutableStateFlow(null)
+ val navigateTo: Flow = _navigateTo.asStateFlow()
+
+ /**
+ * This indicates a navigation event should occur. Navigation depends on navStateInternal being
+ * present.
+ */
+ val currentStep: Flow =
+ _currentStep.filterNotNull().combineTransform(_navStateInternal.filterNotNull()) { navigation, _
+ ->
+ emit(navigation)
+ }
+
+ private var _finishState = MutableStateFlow(null)
+
+ /** This indicates the activity should finish. */
+ val shouldFinish: Flow = _finishState.asStateFlow()
+
+ private var _currentScreen = MutableStateFlow(null)
+
+ /** This indicates what screen should currently be presenting to the user. */
+ val currentScreen: Flow = _currentScreen.asStateFlow()
+
+ /** See [updateInternal] for more details */
+ fun update(action: FingerprintAction, caller: KClass<*>, debugStr: String) {
+ Log.d(TAG, "$caller.update($action) $debugStr")
+ val currentStep = _currentStep.value
+ val isUiStep = currentStep is UiStep && caller is UiStep
+ if (currentStep == null) {
+ throw NullPointerException("current step is null")
+ }
+ if (isUiStep && currentStep::class != caller) {
+ throw IllegalAccessError(
+ "Error $currentStep != $caller, $caller should not be sending any events at this time"
+ )
+ }
+ val navState = _navStateInternal.value
+ if (navState == null) {
+ throw NullPointerException("nav state is null")
+ }
+ val nextStep = currentStep.update(navState, action) ?: return
+ Log.d(TAG, "nextStep=$nextStep")
+ // Whenever an state update occurs, everything should be cleared.
+ _currentStep.update { nextStep }
+ _finishState.update { null }
+ _currentScreen.update { null }
+
+ when (nextStep) {
+ is TransitionStep -> {
+ _navigateTo.update { nextStep.nextUiStep }
+ }
+ is Finish -> {
+ _finishState.update { nextStep }
+ }
+ is UiStep -> {
+ _currentScreen.update { nextStep }
+ }
+ }
+ }
+
+ class FingerprintNavigationViewModelFactory(
+ private val step: UiStep,
+ private val hasConfirmedDeviceCredential: Boolean,
+ private val flowViewModel: FingerprintFlowViewModel,
+ private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+ ) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return FingerprintNavigationViewModel(
+ step,
+ hasConfirmedDeviceCredential,
+ flowViewModel,
+ fingerprintManagerInteractor,
+ )
+ as T
+ }
+ }
+
+ companion object {
+ private const val TAG = "FingerprintNavigationViewModel"
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintScrollViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintScrollViewModel.kt
index 989d4d32372ada6338a47a093212e77b98164686..bc9703d2ad24852c4fc389c20d05120c3a8da5a2 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintScrollViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintScrollViewModel.kt
@@ -39,9 +39,7 @@ class FingerprintScrollViewModel : ViewModel() {
class FingerprintScrollViewModelFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
+ override fun create(modelClass: Class): T {
return FingerprintScrollViewModel() as T
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FoldStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FoldStateViewModel.kt
deleted file mode 100644
index a4c7ff22c57a349d18fa9664cbb95cc9e8cb4b4a..0000000000000000000000000000000000000000
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FoldStateViewModel.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2023 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.fingerprint2.ui.enrollment.viewmodel
-
-import android.content.Context
-import android.content.res.Configuration
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
-import com.android.systemui.unfold.updates.FoldProvider
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-
-/** Represents all of the information on fold state. */
-class FoldStateViewModel(context: Context) : ViewModel() {
-
- private val screenSizeFoldProvider = ScreenSizeFoldProvider(context)
-
- /** A flow that contains the fold state info */
- val isFolded: Flow = callbackFlow {
- val foldStateListener =
- object : FoldProvider.FoldCallback {
- override fun onFoldUpdated(isFolded: Boolean) {
- trySend(isFolded)
- }
- }
- screenSizeFoldProvider.registerCallback(foldStateListener, context.mainExecutor)
- awaitClose { screenSizeFoldProvider.unregisterCallback(foldStateListener) }
- }
-
- fun onConfigurationChange(newConfig: Configuration) {
- screenSizeFoldProvider.onConfigurationChange(newConfig)
- }
-
- class FoldStateViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
- @Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
- return FoldStateViewModel(context) as T
- }
- }
-}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt
deleted file mode 100644
index b68f6d63abbcb645d3c37da46de8886a17d319c7..0000000000000000000000000000000000000000
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2023 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.fingerprint2.ui.enrollment.viewmodel
-
-/**
- * A class that represents an action that the consumer should transition between lastStep and
- * currStep and in what direction this transition is occurring (e.g. forward or backwards)
- */
-open class NavigationStep(
- val lastStep: NextStepViewModel,
- val currStep: NextStepViewModel,
- val forward: Boolean
-) {
- override fun toString(): String {
- return "lastStep=$lastStep, currStep=$currStep, forward=$forward"
- }
-}
-
-/** The navigation state used by a [NavStep] to determine what the [NextStepViewModel] should be. */
-class NavState(val confirmedDevice: Boolean)
-
-interface NavStep {
- fun next(state: NavState): T
- fun prev(state: NavState): T
-}
-
-/**
- * A class to represent a high level step (I.E. EnrollmentIntroduction) for FingerprintEnrollment.
- */
-sealed class NextStepViewModel : NavStep
-
-/**
- * This is the initial state for the previous step, used to indicate that there have been no
- * previous states.
- */
-object PlaceHolderState : NextStepViewModel() {
- override fun next(state: NavState): NextStepViewModel = Finish(null)
-
- override fun prev(state: NavState): NextStepViewModel = Finish(null)
-}
-
-/**
- * This state is the initial state for the current step, and will be used to determine if the user
- * needs to [LaunchConfirmDeviceCredential] if not, it will go to [Intro]
- */
-data object Start : NextStepViewModel() {
- override fun next(state: NavState): NextStepViewModel =
- if (state.confirmedDevice) Intro else LaunchConfirmDeviceCredential
-
- override fun prev(state: NavState): NextStepViewModel = Finish(null)
-}
-
-/** State indicating enrollment has been completed */
-class Finish(val resultCode: Int?) : NextStepViewModel() {
- override fun next(state: NavState): NextStepViewModel = Finish(resultCode)
- override fun prev(state: NavState): NextStepViewModel = Finish(null)
-}
-
-/** State for the FingerprintEnrollment introduction */
-data object Intro : NextStepViewModel() {
- override fun next(state: NavState): NextStepViewModel = Education
- override fun prev(state: NavState): NextStepViewModel = Finish(null)
-}
-
-/** State for the FingerprintEnrollment education */
-data object Education : NextStepViewModel() {
- override fun next(state: NavState): NextStepViewModel = Enrollment
- override fun prev(state: NavState): NextStepViewModel = Intro
-}
-
-/** State for the FingerprintEnrollment enrollment */
-data object Enrollment : NextStepViewModel() {
- override fun next(state: NavState): NextStepViewModel = Confirmation
- override fun prev(state: NavState): NextStepViewModel = Education
-}
-
-/** State for the FingerprintEnrollment confirmation */
-object Confirmation : NextStepViewModel() {
- override fun next(state: NavState): NextStepViewModel = Finish(0)
- override fun prev(state: NavState): NextStepViewModel = Intro
-}
-
-/**
- * State used to send the user to the ConfirmDeviceCredential activity. This activity can either
- * confirm a users device credential, or have them create one.
- */
-object LaunchConfirmDeviceCredential : NextStepViewModel() {
- override fun next(state: NavState): NextStepViewModel = Intro
- override fun prev(state: NavState): NextStepViewModel = Finish(0)
-}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
index debdfb8da63c50eabb88f52fa23686c035ec8a96..540e5ee9f528d123fcf053ea24d097ad1e9546a0 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
@@ -19,8 +19,8 @@ package com.android.settings.biometrics.fingerprint2.ui.settings.binder
import android.hardware.fingerprint.FingerprintManager
import android.util.Log
import androidx.lifecycle.LifecycleCoroutineScope
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollFirstFingerprint
@@ -52,7 +52,7 @@ object FingerprintSettingsViewBinder {
userId: Int,
gateKeeperPasswordHandle: Long?,
challenge: Long?,
- challengeToken: ByteArray?
+ challengeToken: ByteArray?,
)
/** Helper to launch an add fingerprint request */
fun launchAddFingerprint(userId: Int, challengeToken: ByteArray?)
@@ -158,7 +158,7 @@ object FingerprintSettingsViewBinder {
nextStep.userId,
nextStep.gateKeeperPasswordHandle,
nextStep.challenge,
- nextStep.challengeToken
+ nextStep.challengeToken,
)
is EnrollAdditionalFingerprint ->
view.launchAddFingerprint(nextStep.userId, nextStep.challengeToken)
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
index 71a22eb765d569707d0373eeee49fce890d3b1a2..46f64de06368984e8b7b5ea54f46d837f964b009 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
@@ -26,7 +26,7 @@ import android.os.Bundle
import android.os.UserManager
import androidx.appcompat.app.AlertDialog
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -75,8 +75,7 @@ class FingerprintDeletionDialog : InstrumentedDialogFragment() {
message =
devicePolicyManager?.resources?.getString(messageId) {
message + "\n\n" + context.getString(defaultMessageId)
- }
- ?: ""
+ } ?: ""
}
alertDialog =
@@ -85,7 +84,7 @@ class FingerprintDeletionDialog : InstrumentedDialogFragment() {
.setMessage(message)
.setPositiveButton(
R.string.security_settings_fingerprint_enroll_dialog_delete,
- onClickListener
+ onClickListener,
)
.setNegativeButton(R.string.cancel, onNegativeClickListener)
.create()
@@ -94,10 +93,11 @@ class FingerprintDeletionDialog : InstrumentedDialogFragment() {
companion object {
private const val KEY_FINGERPRINT = "fingerprint"
+
suspend fun showInstance(
- fp: FingerprintData,
- lastFingerprint: Boolean,
- target: FingerprintSettingsV2Fragment,
+ fp: FingerprintData,
+ lastFingerprint: Boolean,
+ target: FingerprintSettingsV2Fragment,
) = suspendCancellableCoroutine { continuation ->
val dialog = FingerprintDeletionDialog()
dialog.onClickListener = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
@@ -109,7 +109,7 @@ class FingerprintDeletionDialog : InstrumentedDialogFragment() {
val bundle = Bundle()
bundle.putObject(
KEY_FINGERPRINT,
- android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId)
+ android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
)
bundle.putBoolean(KEY_IS_LAST_FINGERPRINT, lastFingerprint)
dialog.arguments = bundle
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt
index ea26946a482870172ec0b8f5b1f6c49f5e07ef5e..09dcb81fcc53c3b0ad3f9ac37d0c3a77dc4bb6b2 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt
@@ -22,7 +22,7 @@ import android.view.View
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceViewHolder
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.settingslib.widget.TwoTargetPreference
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -30,10 +30,10 @@ import kotlinx.coroutines.launch
private const val TAG = "FingerprintSettingsPreference"
class FingerprintSettingsPreference(
- context: Context,
- val fingerprintViewModel: FingerprintData,
- val fragment: FingerprintSettingsV2Fragment,
- val isLastFingerprint: Boolean
+ context: Context,
+ val fingerprintViewModel: FingerprintData,
+ val fragment: FingerprintSettingsV2Fragment,
+ val isLastFingerprint: Boolean,
) : TwoTargetPreference(context) {
private lateinit var myView: View
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
index ff469f18b2d07b6b5c8e87a942333c2addc5662b..9fef0c5ee3fd6fb5f7dbe390356b377fe8f9bd35 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
@@ -27,7 +27,7 @@ import android.util.Log
import android.widget.ImeAwareEditText
import androidx.appcompat.app.AlertDialog
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -78,7 +78,7 @@ class FingerprintSettingsRenameDialog : InstrumentedDialogFragment() {
end: Int,
dest: Spanned?,
dstart: Int,
- dend: Int
+ dend: Int,
): CharSequence? {
for (index in start until end) {
val c = source[index]
@@ -133,13 +133,13 @@ class FingerprintSettingsRenameDialog : InstrumentedDialogFragment() {
val bundle = Bundle()
bundle.putObject(
KEY_FINGERPRINT,
- android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId)
+ android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
)
dialog.arguments = bundle
Log.d(TAG, "showing dialog $dialog")
dialog.show(
target.parentFragmentManager,
- FingerprintSettingsRenameDialog::class.java.toString()
+ FingerprintSettingsRenameDialog::class.java.toString(),
)
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
index c22a5a73bfbe9388d446f70312b53692ecd3c148..bd905242fc3ebebf712434f595eeee45a631a38d 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
@@ -45,11 +45,12 @@ import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
-import com.android.settings.biometrics.fingerprint2.repository.PressToAuthProviderImpl
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
-import com.android.settings.biometrics.fingerprint2.shared.model.Settings
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.lib.model.Settings
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
@@ -128,12 +129,12 @@ class FingerprintSettingsV2Fragment :
if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) {
navigationViewModel.onEnrollFirstFailure(
"Received RESULT_TIMEOUT when enrolling",
- resultCode
+ resultCode,
)
} else {
navigationViewModel.onEnrollFirstFailure(
"Incorrect resultCode or data was null",
- resultCode
+ resultCode,
)
}
} else {
@@ -212,21 +213,26 @@ class FingerprintSettingsV2Fragment :
context.contentResolver,
Secure.SFPS_PERFORMANT_AUTH_ENABLED,
toReturn,
- userHandle
+ userHandle,
)
}
toReturn == 1
}
+ val fingerprintSensorProvider =
+ FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
+ val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher)
val interactor =
FingerprintManagerInteractorImpl(
context.applicationContext,
backgroundDispatcher,
fingerprintManager,
+ fingerprintSensorProvider,
GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
- PressToAuthProviderImpl(context),
- isAnySuw
+ pressToAuthInteractor,
+ Settings,
+ getIntent()
)
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
@@ -240,8 +246,8 @@ class FingerprintSettingsV2Fragment :
interactor,
backgroundDispatcher,
token,
- challenge
- )
+ challenge,
+ ),
)[FingerprintSettingsNavigationViewModel::class.java]
settingsViewModel =
@@ -252,15 +258,10 @@ class FingerprintSettingsV2Fragment :
interactor,
backgroundDispatcher,
navigationViewModel,
- )
+ ),
)[FingerprintSettingsViewModel::class.java]
- FingerprintSettingsViewBinder.bind(
- this,
- settingsViewModel,
- navigationViewModel,
- lifecycleScope,
- )
+ FingerprintSettingsViewBinder.bind(this, settingsViewModel, navigationViewModel, lifecycleScope)
}
override fun getMetricsCategory(): Int {
@@ -364,7 +365,7 @@ class FingerprintSettingsV2Fragment :
RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
activity,
DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT,
- requireActivity().userId
+ requireActivity().userId,
)
val activity = requireActivity()
val helpIntent =
@@ -404,7 +405,7 @@ class FingerprintSettingsV2Fragment :
column.title =
getString(
R.string.security_settings_fingerprint_enroll_introduction_v3_message,
- DeviceHelper.getDeviceName(requireActivity())
+ DeviceHelper.getDeviceName(requireActivity()),
)
column.learnMoreOnClickListener = learnMoreClickListener
column.learnMoreOverrideText =
@@ -437,13 +438,12 @@ class FingerprintSettingsV2Fragment :
val willDelete =
fingerprintPreferences()
.first { it?.fingerprintViewModel == fingerprintViewModel }
- ?.askUserToDeleteDialog()
- ?: false
+ ?.askUserToDeleteDialog() ?: false
if (willDelete) {
mMetricsFeatureProvider.action(
context,
SettingsEnums.ACTION_FINGERPRINT_DELETE,
- fingerprintViewModel.fingerId
+ fingerprintViewModel.fingerId,
)
}
return willDelete
@@ -466,7 +466,7 @@ class FingerprintSettingsV2Fragment :
mMetricsFeatureProvider.action(
context,
SettingsEnums.ACTION_FINGERPRINT_RENAME,
- toReturn.first.fingerId
+ toReturn.first.fingerId,
)
}
return toReturn
@@ -518,12 +518,12 @@ class FingerprintSettingsV2Fragment :
val intent = Intent()
intent.setClassName(
SETTINGS_PACKAGE_NAME,
- FingerprintEnrollIntroductionInternal::class.java.name
+ FingerprintEnrollIntroductionInternal::class.java.name,
)
intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true)
intent.putExtra(
SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
- SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE
+ SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE,
)
intent.putExtra(Intent.EXTRA_USER_ID, userId)
@@ -546,7 +546,7 @@ class FingerprintSettingsV2Fragment :
val intent = Intent()
intent.setClassName(
SETTINGS_PACKAGE_NAME,
- FingerprintEnrollEnrolling::class.qualifiedName.toString()
+ FingerprintEnrollEnrolling::class.qualifiedName.toString(),
)
intent.putExtra(Intent.EXTRA_USER_ID, userId)
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, challengeToken)
@@ -568,8 +568,7 @@ class FingerprintSettingsV2Fragment :
return category?.let { cat ->
cat.childrenToList().map { it as FingerprintSettingsPreference? }
- }
- ?: emptyList()
+ } ?: emptyList()
}
private fun PreferenceCategory.childrenToList(): List {
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt
index 22a25e1af367cfbe9c212fe066910675895ca319..00b91a8c92e2a2b7309e0f0e5edfdb430b44b7bc 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt
@@ -21,7 +21,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.BiometricEnrollBase
-import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -32,11 +32,11 @@ import kotlinx.coroutines.launch
/** A Viewmodel that represents the navigation of the FingerprintSettings activity. */
class FingerprintSettingsNavigationViewModel(
- private val userId: Int,
- private val fingerprintManagerInteractor: FingerprintManagerInteractor,
- private val backgroundDispatcher: CoroutineDispatcher,
- tokenInit: ByteArray?,
- challengeInit: Long?,
+ private val userId: Int,
+ private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+ private val backgroundDispatcher: CoroutineDispatcher,
+ tokenInit: ByteArray?,
+ challengeInit: Long?,
) : ViewModel() {
private var token = tokenInit
@@ -173,17 +173,15 @@ class FingerprintSettingsNavigationViewModel(
}
class FingerprintSettingsNavigationModelFactory(
- private val userId: Int,
- private val interactor: FingerprintManagerInteractor,
- private val backgroundDispatcher: CoroutineDispatcher,
- private val token: ByteArray?,
- private val challenge: Long?,
+ private val userId: Int,
+ private val interactor: FingerprintManagerInteractor,
+ private val backgroundDispatcher: CoroutineDispatcher,
+ private val token: ByteArray?,
+ private val challenge: Long?,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
+ override fun create(modelClass: Class): T {
return FingerprintSettingsNavigationViewModel(
userId,
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
index 164f79fed5863c074d5145b3f73b6fe71ba16ebf..80bbb43efe7ad68f0479faf7e6d6d5d9e6eb3c5f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
@@ -21,9 +21,9 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -66,7 +66,7 @@ class FingerprintSettingsViewModel(
emit(
Pair(
fingerprintManagerInteractor.canEnrollFingerprints.first(),
- fingerprintManagerInteractor.maxEnrollableFingerprints.first()
+ fingerprintManagerInteractor.maxEnrollableFingerprints.first(),
)
)
}
@@ -120,7 +120,7 @@ class FingerprintSettingsViewModel(
_isLockedOut,
_attemptsSoFar,
_fingerprintSensorType,
- _sensorNullOrEmpty
+ _sensorNullOrEmpty,
) {
dialogShowing,
step,
@@ -140,7 +140,7 @@ class FingerprintSettingsViewModel(
"lockedOut=${isLockedOut}," +
"attempts=${attempts}," +
"sensorType=${sensorType}" +
- "sensorNullOrEmpty=${sensorNullOrEmpty}"
+ "sensorNullOrEmpty=${sensorNullOrEmpty}",
)
}
if (sensorNullOrEmpty) {
@@ -294,9 +294,7 @@ class FingerprintSettingsViewModel(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
- override fun create(
- modelClass: Class,
- ): T {
+ override fun create(modelClass: Class): T {
return FingerprintSettingsViewModel(
userId,
@@ -318,7 +316,7 @@ private inline fun combine(
flow6: Flow,
flow7: Flow,
flow8: Flow,
- crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R,
): Flow {
return combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) { args: Array<*> ->
@Suppress("UNCHECKED_CAST")
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/NextStepViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/NextStepViewModel.kt
index d9155b6d364a99411918e25e16840a6275e6668b..3acedb13afc89c112688aaf98aa21895fcb1eed0 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/NextStepViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/NextStepViewModel.kt
@@ -29,10 +29,8 @@ data class EnrollFirstFingerprint(
val challengeToken: ByteArray?,
) : NextStepViewModel()
-data class EnrollAdditionalFingerprint(
- val userId: Int,
- val challengeToken: ByteArray?,
-) : NextStepViewModel()
+data class EnrollAdditionalFingerprint(val userId: Int, val challengeToken: ByteArray?) :
+ NextStepViewModel()
data class FinishSettings(val reason: String) : NextStepViewModel()
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt
index 181da4e85f67e781e6546b5734e9a334dfb1a83d..2a1d9c665fcd7e7b3063d8bf21c0888d2250da21 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt
@@ -16,15 +16,11 @@
package com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
/** Classed use to represent a Dialogs state. */
sealed class PreferenceViewModel {
- data class RenameDialog(
- val fingerprintViewModel: FingerprintData,
- ) : PreferenceViewModel()
+ data class RenameDialog(val fingerprintViewModel: FingerprintData) : PreferenceViewModel()
- data class DeleteDialog(
- val fingerprintViewModel: FingerprintData,
- ) : PreferenceViewModel()
+ data class DeleteDialog(val fingerprintViewModel: FingerprintData) : PreferenceViewModel()
}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
index 9b25ee8f0a10d326fc54638850076173387f0ac9..1cfec52df69056fc9b8e22caf47a18ff147de47d 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
@@ -23,6 +23,7 @@ import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITI
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_STEPS;
import android.app.Application;
+import android.content.Intent;
import android.content.res.Resources;
import android.hardware.fingerprint.FingerprintManager.EnrollReason;
import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
@@ -212,10 +213,11 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
res.getBoolean(R.bool.enrollment_progress_priority_over_help),
res.getBoolean(R.bool.enrollment_prioritize_acquire_messages),
res.getInteger(R.integer.enrollment_collect_time));
- mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, callback, reason);
+ mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, callback, reason,
+ new Intent());
} else {
mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, mEnrollmentCallback,
- reason);
+ reason, new Intent());
}
return mCancellationSignal;
}
diff --git a/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java b/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java
index 831e83b5ed561fdb7796bc8bc4274c6c2eb877e0..c99cb2ddd575be70563a8acd2d56505cd057538d 100644
--- a/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java
+++ b/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java
@@ -260,8 +260,8 @@ public class UdfpsEnrollView extends FrameLayout {
displayInfo.getNaturalWidth(),
displayInfo.getNaturalHeight(),
scaleFactor,
- displayInfo.rotation);
-
+ displayInfo.rotation,
+ mSensorProperties.sensorType);
post(() -> {
mProgressBarRadius =
diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
index c38e340d35f0188976e35a1c3201b18a1e1bdf50..13268314fc33be06145087953d7bbe9f3d2e5561 100644
--- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
@@ -16,6 +16,8 @@
package com.android.settings.bluetooth;
+import static com.android.settings.bluetooth.Utils.preloadAndRun;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver;
@@ -55,9 +57,13 @@ import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.LayoutPreference;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -236,63 +242,87 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
@VisibleForTesting
void refresh() {
if (mLayoutPreference != null && mCachedDevice != null) {
- final TextView title = mLayoutPreference.findViewById(R.id.entity_header_title);
- title.setText(mCachedDevice.getName());
- final TextView summary = mLayoutPreference.findViewById(R.id.entity_header_summary);
-
- if (!mCachedDevice.isConnected() || mCachedDevice.isBusy()) {
- summary.setText(mCachedDevice.getConnectionSummary(true /* shortSummary */));
- updateDisconnectLayout();
- return;
- }
- final BluetoothDevice device = mCachedDevice.getDevice();
- final String deviceType = BluetoothUtils.getStringMetaData(device,
- BluetoothDevice.METADATA_DEVICE_TYPE);
- if (TextUtils.equals(deviceType,
- BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
- || BluetoothUtils.getBooleanMetaData(device,
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
- summary.setText(mCachedDevice.getConnectionSummary(true /* shortSummary */));
- updateSubLayout(mLayoutPreference.findViewById(R.id.layout_left),
- BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
- R.string.bluetooth_left_name,
- LEFT_DEVICE_ID);
-
- updateSubLayout(mLayoutPreference.findViewById(R.id.layout_middle),
- BluetoothDevice.METADATA_UNTETHERED_CASE_ICON,
- BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
- BluetoothDevice.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
- BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
- R.string.bluetooth_middle_name,
- CASE_DEVICE_ID);
-
- updateSubLayout(mLayoutPreference.findViewById(R.id.layout_right),
- BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON,
- BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
- BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
- BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
- R.string.bluetooth_right_name,
- RIGHT_DEVICE_ID);
-
- showBothDevicesBatteryPredictionIfNecessary();
- } else {
- mLayoutPreference.findViewById(R.id.layout_left).setVisibility(View.GONE);
- mLayoutPreference.findViewById(R.id.layout_right).setVisibility(View.GONE);
-
- summary.setText(mCachedDevice.getConnectionSummary(
- BluetoothUtils.getIntMetaData(device, BluetoothDevice.METADATA_MAIN_BATTERY)
- != BluetoothUtils.META_INT_ERROR));
- updateSubLayout(mLayoutPreference.findViewById(R.id.layout_middle),
- BluetoothDevice.METADATA_MAIN_ICON,
- BluetoothDevice.METADATA_MAIN_BATTERY,
- BluetoothDevice.METADATA_MAIN_LOW_BATTERY_THRESHOLD,
- BluetoothDevice.METADATA_MAIN_CHARGING,
- /* titleResId */ 0,
- MAIN_DEVICE_ID);
- }
+ Supplier deviceName = Suppliers.memoize(() -> mCachedDevice.getName());
+ Supplier disconnected =
+ Suppliers.memoize(() -> !mCachedDevice.isConnected() || mCachedDevice.isBusy());
+ Supplier isUntetheredHeadset =
+ Suppliers.memoize(() -> isUntetheredHeadset(mCachedDevice.getDevice()));
+ Supplier summaryText =
+ Suppliers.memoize(
+ () -> {
+ if (disconnected.get() || isUntetheredHeadset.get()) {
+ return mCachedDevice.getConnectionSummary(
+ /* shortSummary= */ true);
+ }
+ return mCachedDevice.getConnectionSummary(
+ BluetoothUtils.getIntMetaData(
+ mCachedDevice.getDevice(),
+ BluetoothDevice.METADATA_MAIN_BATTERY)
+ != BluetoothUtils.META_INT_ERROR);
+ });
+ preloadAndRun(
+ List.of(deviceName, disconnected, isUntetheredHeadset, summaryText),
+ () -> {
+ final TextView title =
+ mLayoutPreference.findViewById(R.id.entity_header_title);
+ title.setText(deviceName.get());
+ final TextView summary =
+ mLayoutPreference.findViewById(R.id.entity_header_summary);
+
+ if (disconnected.get()) {
+ summary.setText(summaryText.get());
+ updateDisconnectLayout();
+ return;
+ }
+ if (isUntetheredHeadset.get()) {
+ summary.setText(summaryText.get());
+ updateSubLayout(
+ mLayoutPreference.findViewById(R.id.layout_left),
+ BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
+ BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
+ BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
+ BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
+ R.string.bluetooth_left_name,
+ LEFT_DEVICE_ID);
+
+ updateSubLayout(
+ mLayoutPreference.findViewById(R.id.layout_middle),
+ BluetoothDevice.METADATA_UNTETHERED_CASE_ICON,
+ BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
+ BluetoothDevice.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
+ BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
+ R.string.bluetooth_middle_name,
+ CASE_DEVICE_ID);
+
+ updateSubLayout(
+ mLayoutPreference.findViewById(R.id.layout_right),
+ BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON,
+ BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
+ BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
+ BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
+ R.string.bluetooth_right_name,
+ RIGHT_DEVICE_ID);
+
+ showBothDevicesBatteryPredictionIfNecessary();
+ } else {
+ mLayoutPreference
+ .findViewById(R.id.layout_left)
+ .setVisibility(View.GONE);
+ mLayoutPreference
+ .findViewById(R.id.layout_right)
+ .setVisibility(View.GONE);
+
+ summary.setText(summaryText.get());
+ updateSubLayout(
+ mLayoutPreference.findViewById(R.id.layout_middle),
+ BluetoothDevice.METADATA_MAIN_ICON,
+ BluetoothDevice.METADATA_MAIN_BATTERY,
+ BluetoothDevice.METADATA_MAIN_LOW_BATTERY_THRESHOLD,
+ BluetoothDevice.METADATA_MAIN_CHARGING,
+ /* titleResId= */ 0,
+ MAIN_DEVICE_ID);
+ }
+ });
}
}
@@ -315,13 +345,87 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
return drawable;
}
- private void updateSubLayout(LinearLayout linearLayout, int iconMetaKey, int batteryMetaKey,
- int lowBatteryMetaKey, int chargeMetaKey, int titleResId, int deviceId) {
+ private void updateSubLayout(
+ LinearLayout linearLayout,
+ int iconMetaKey,
+ int batteryMetaKey,
+ int lowBatteryMetaKey,
+ int chargeMetaKey,
+ int titleResId,
+ int deviceId) {
if (linearLayout == null) {
return;
}
+ BluetoothDevice bluetoothDevice = mCachedDevice.getDevice();
+ Supplier iconUri =
+ Suppliers.memoize(
+ () -> BluetoothUtils.getStringMetaData(bluetoothDevice, iconMetaKey));
+ Supplier batteryLevel =
+ Suppliers.memoize(
+ () -> BluetoothUtils.getIntMetaData(bluetoothDevice, batteryMetaKey));
+ Supplier charging =
+ Suppliers.memoize(
+ () -> BluetoothUtils.getBooleanMetaData(bluetoothDevice, chargeMetaKey));
+ Supplier lowBatteryLevel =
+ Suppliers.memoize(
+ () -> {
+ int level =
+ BluetoothUtils.getIntMetaData(
+ bluetoothDevice, lowBatteryMetaKey);
+ if (level == BluetoothUtils.META_INT_ERROR) {
+ if (batteryMetaKey
+ == BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY) {
+ level = CASE_LOW_BATTERY_LEVEL;
+ } else {
+ level = LOW_BATTERY_LEVEL;
+ }
+ }
+ return level;
+ });
+ Supplier isUntethered =
+ Suppliers.memoize(() -> isUntetheredHeadset(bluetoothDevice));
+ Supplier nativeBatteryLevel = Suppliers.memoize(bluetoothDevice::getBatteryLevel);
+ preloadAndRun(
+ List.of(
+ iconUri,
+ batteryLevel,
+ charging,
+ lowBatteryLevel,
+ isUntethered,
+ nativeBatteryLevel),
+ () ->
+ updateSubLayoutUi(
+ linearLayout,
+ iconMetaKey,
+ batteryMetaKey,
+ lowBatteryMetaKey,
+ chargeMetaKey,
+ titleResId,
+ deviceId,
+ iconUri,
+ batteryLevel,
+ charging,
+ lowBatteryLevel,
+ isUntethered,
+ nativeBatteryLevel));
+ }
+
+ private void updateSubLayoutUi(
+ LinearLayout linearLayout,
+ int iconMetaKey,
+ int batteryMetaKey,
+ int lowBatteryMetaKey,
+ int chargeMetaKey,
+ int titleResId,
+ int deviceId,
+ Supplier preloadedIconUri,
+ Supplier preloadedBatteryLevel,
+ Supplier preloadedCharging,
+ Supplier preloadedLowBatteryLevel,
+ Supplier preloadedIsUntethered,
+ Supplier preloadedNativeBatteryLevel) {
final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice();
- final String iconUri = BluetoothUtils.getStringMetaData(bluetoothDevice, iconMetaKey);
+ final String iconUri = preloadedIconUri.get();
final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
if (iconUri != null) {
updateIcon(imageView, iconUri);
@@ -331,17 +435,9 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
imageView.setImageDrawable(pair.first);
imageView.setContentDescription(pair.second);
}
- final int batteryLevel = BluetoothUtils.getIntMetaData(bluetoothDevice, batteryMetaKey);
- final boolean charging = BluetoothUtils.getBooleanMetaData(bluetoothDevice, chargeMetaKey);
- int lowBatteryLevel = BluetoothUtils.getIntMetaData(bluetoothDevice,
- lowBatteryMetaKey);
- if (lowBatteryLevel == BluetoothUtils.META_INT_ERROR) {
- if (batteryMetaKey == BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY) {
- lowBatteryLevel = CASE_LOW_BATTERY_LEVEL;
- } else {
- lowBatteryLevel = LOW_BATTERY_LEVEL;
- }
- }
+ final int batteryLevel = preloadedBatteryLevel.get();
+ final boolean charging = preloadedCharging.get();
+ int lowBatteryLevel = preloadedLowBatteryLevel.get();
Log.d(TAG, "buletoothDevice: " + bluetoothDevice.getAnonymizedAddress()
+ ", updateSubLayout() icon : " + iconMetaKey + ", battery : " + batteryMetaKey
@@ -353,7 +449,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
showBatteryPredictionIfNecessary(linearLayout, deviceId, batteryLevel);
}
final TextView batterySummaryView = linearLayout.findViewById(R.id.bt_battery_summary);
- if (isUntetheredHeadset(bluetoothDevice)) {
+ if (preloadedIsUntethered.get()) {
if (batteryLevel != BluetoothUtils.META_INT_ERROR) {
linearLayout.setVisibility(View.VISIBLE);
batterySummaryView.setText(
@@ -364,7 +460,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
if (deviceId == MAIN_DEVICE_ID) {
linearLayout.setVisibility(View.VISIBLE);
linearLayout.findViewById(R.id.bt_battery_icon).setVisibility(View.GONE);
- int level = bluetoothDevice.getBatteryLevel();
+ int level = preloadedNativeBatteryLevel.get();
if (level != BluetoothDevice.BATTERY_LEVEL_UNKNOWN
&& level != BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF) {
batterySummaryView.setText(
diff --git a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java
index 13990325d98c0e3c85ba98c3f7e7059354165d49..8250f70e738e47a5144a66c05e2dbb01b27bd624 100644
--- a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java
@@ -23,7 +23,7 @@ import android.util.Log;
import androidx.preference.Preference;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -70,33 +70,45 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
boolean isFilterMatched = false;
if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
- if (DBG) {
- Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile);
- }
- // If device is Hearing Aid, it is compatible with HFP and A2DP.
- // It would show in Available Devices group.
- if (cachedDevice.isConnectedAshaHearingAidDevice()) {
- Log.d(
- TAG,
- "isFilterMatched() device : "
- + cachedDevice.getName()
- + ", the Hearing Aid profile is connected.");
- return true;
- }
+ Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile);
+
// If device is LE Audio, it is compatible with HFP and A2DP.
// It would show in Available Devices group if the audio sharing flag is disabled or
// the device is not in the audio sharing session.
if (cachedDevice.isConnectedLeAudioDevice()) {
- if (!AudioSharingUtils.isFeatureEnabled()
- || !AudioSharingUtils.hasBroadcastSource(cachedDevice, mLocalManager)) {
+ boolean isAudioSharingFilterMatched =
+ FeatureFactory.getFeatureFactory()
+ .getAudioSharingFeatureProvider()
+ .isAudioSharingFilterMatched(cachedDevice, mLocalManager);
+ if (!isAudioSharingFilterMatched) {
Log.d(
TAG,
"isFilterMatched() device : "
+ cachedDevice.getName()
- + ", the LE Audio profile is connected and not in sharing.");
+ + ", the LE Audio profile is connected and not in sharing "
+ + "if broadcast enabled.");
return true;
+ } else {
+ Log.d(
+ TAG,
+ "Filter out device : "
+ + cachedDevice.getName()
+ + ", it is in audio sharing.");
+ return false;
}
}
+
+ // If device is Hearing Aid, it is compatible with HFP and A2DP.
+ // It would show in Available Devices group.
+ if (cachedDevice.isConnectedAshaHearingAidDevice()) {
+ Log.d(
+ TAG,
+ "isFilterMatched() device : "
+ + cachedDevice.getName()
+ + ", the Hearing Aid profile is connected.");
+ return true;
+ }
+
// According to the current audio profile type,
// this page will show the bluetooth device that have corresponding profile.
// For example:
@@ -111,14 +123,12 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
isFilterMatched = cachedDevice.isConnectedHfpDevice();
break;
}
- if (DBG) {
- Log.d(
- TAG,
- "isFilterMatched() device : "
- + cachedDevice.getName()
- + ", isFilterMatched : "
- + isFilterMatched);
- }
+ Log.d(
+ TAG,
+ "isFilterMatched() device : "
+ + cachedDevice.getName()
+ + ", isFilterMatched : "
+ + isFilterMatched);
}
return isFilterMatched;
}
@@ -128,13 +138,9 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
mMetricsFeatureProvider.logClickedPreference(preference, mMetricsCategory);
final CachedBluetoothDevice device =
((BluetoothDevicePreference) preference).getBluetoothDevice();
- if (AudioSharingUtils.isFeatureEnabled()
- && AudioSharingUtils.isBroadcasting(mLocalBtManager)) {
- if (DBG) {
- Log.d(TAG, "onPreferenceClick stop broadcasting.");
- }
- AudioSharingUtils.stopBroadcasting(mLocalBtManager);
- }
+ FeatureFactory.getFeatureFactory()
+ .getAudioSharingFeatureProvider()
+ .handleMediaDeviceOnClick(mLocalManager);
return device.setActive();
}
diff --git a/src/com/android/settings/bluetooth/BluetoothAutoOnPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothAutoOnPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6f62e8ffd83d3b9fd82745c88456d1dd703357c
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothAutoOnPreferenceController.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 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.bluetooth;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+import com.android.settings.core.TogglePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.flags.Flags;
+import com.android.settingslib.utils.ThreadUtils;
+
+public class BluetoothAutoOnPreferenceController extends TogglePreferenceController
+ implements LifecycleObserver, OnStart, OnStop {
+ private static final String TAG = "BluetoothAutoOnPreferenceController";
+ @VisibleForTesting static final String PREF_KEY = "bluetooth_auto_on_settings_toggle";
+ static final String SETTING_NAME = "bluetooth_automatic_turn_on";
+ static final int UNSET = -1;
+ @VisibleForTesting static final int ENABLED = 1;
+ @VisibleForTesting static final int DISABLED = 0;
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(/* async= */ true)) {
+ @Override
+ public void onChange(boolean selfChange) {
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ updateValue();
+ mContext.getMainExecutor()
+ .execute(
+ () -> {
+ if (mPreference != null) {
+ updateState(mPreference);
+ }
+ });
+ });
+ }
+ };
+ private int mAutoOnValue = UNSET;
+ @Nullable private TwoStatePreference mPreference;
+
+ public BluetoothAutoOnPreferenceController(
+ @NonNull Context context, @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public void onStart() {
+ mContext.getContentResolver()
+ .registerContentObserver(
+ Settings.Secure.getUriFor(SETTING_NAME),
+ /* notifyForDescendants= */ false,
+ mContentObserver);
+ }
+
+ @Override
+ public void onStop() {
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!Flags.bluetoothQsTileDialogAutoOnToggle()) {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ updateValue();
+ return mAutoOnValue != UNSET ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return PREF_KEY;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mAutoOnValue == ENABLED;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ if (getAvailabilityStatus() != AVAILABLE) {
+ Log.w(TAG, "Trying to set toggle value while feature not available.");
+ return false;
+ }
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ boolean updated =
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ SETTING_NAME,
+ isChecked ? ENABLED : DISABLED,
+ UserHandle.myUserId());
+ if (updated) {
+ updateValue();
+ }
+ });
+ return true;
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return 0;
+ }
+
+ private void updateValue() {
+ mAutoOnValue =
+ Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), SETTING_NAME, UNSET, UserHandle.myUserId());
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsExtraOptionsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsExtraOptionsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..ddaf5e5ccf3f4fe9278e85824fb6bc751e6c824a
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsExtraOptionsController.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 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.bluetooth;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.utils.ThreadUtils;
+
+import dagger.internal.Preconditions;
+
+import java.util.List;
+
+public class BluetoothDetailsExtraOptionsController extends BluetoothDetailsController {
+
+ private static final String KEY_BLUETOOTH_EXTRA_OPTIONS = "bt_extra_options";
+
+ @VisibleForTesting @Nullable
+ PreferenceCategory mOptionsContainer;
+ @Nullable PreferenceScreen mPreferenceScreen;
+
+ public BluetoothDetailsExtraOptionsController(
+ Context context,
+ PreferenceFragmentCompat fragment,
+ CachedBluetoothDevice device,
+ Lifecycle lifecycle) {
+ super(context, fragment, device, lifecycle);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_BLUETOOTH_EXTRA_OPTIONS;
+ }
+
+ @Override
+ protected void init(PreferenceScreen screen) {
+ mPreferenceScreen = screen;
+ mOptionsContainer = screen.findPreference(getPreferenceKey());
+ refresh();
+ }
+
+ @Override
+ protected void refresh() {
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ List options =
+ FeatureFactory.getFeatureFactory()
+ .getBluetoothFeatureProvider()
+ .getBluetoothExtraOptions(mContext, mCachedDevice);
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mOptionsContainer != null) {
+ mOptionsContainer.removeAll();
+ for (Preference option : options) {
+ mOptionsContainer.addPreference(option);
+ }
+ setVisible(
+ Preconditions.checkNotNull(mPreferenceScreen),
+ getPreferenceKey(),
+ !options.isEmpty());
+ }
+ });
+ });
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
index 18ad2109405f2748a0ee2b1ce202780f76f31bae..162abc78aef9f42fa26690cb97aeeae1557f909d 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
@@ -26,6 +26,7 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityHearingAidsFragment;
+import com.android.settings.accessibility.ArrowPreference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -38,7 +39,8 @@ import com.google.common.annotations.VisibleForTesting;
public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDetailsController
implements Preference.OnPreferenceClickListener {
- private static final String KEY_DEVICE_CONTROLS_GENERAL_GROUP = "device_controls_general";
+ @VisibleForTesting
+ static final String KEY_DEVICE_CONTROLS_GENERAL_GROUP = "device_controls_general";
@VisibleForTesting
static final String KEY_HEARING_DEVICE_CONTROLS = "hearing_device_controls";
@@ -82,7 +84,7 @@ public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDe
}
private Preference createHearingDeviceControlsPreference(Context context) {
- final Preference preference = new Preference(context);
+ final ArrowPreference preference = new ArrowPreference(context);
preference.setKey(KEY_HEARING_DEVICE_CONTROLS);
preference.setTitle(context.getString(R.string.bluetooth_device_controls_title));
preference.setSummary(context.getString(R.string.bluetooth_device_controls_summary));
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
index 3b162b6c9da5993b8f47070d255416a12ac47575..943d99bb4ee05c85077d4c2067628e8bde152bc1 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
@@ -37,6 +37,8 @@ import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.core.SettingsUIDeviceConfig;
+import com.android.settings.flags.Flags;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -49,11 +51,14 @@ import com.android.settingslib.bluetooth.MapProfile;
import com.android.settingslib.bluetooth.PanProfile;
import com.android.settingslib.bluetooth.PbapServerProfile;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
/**
* This class adds switches for toggling the individual profiles that a Bluetooth device
@@ -79,6 +84,8 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll
private static final String LE_AUDIO_TOGGLE_VISIBLE_PROPERTY =
"persist.bluetooth.leaudio.toggle_visible";
+ private final AtomicReference> mInvisiblePreferenceKey = new AtomicReference<>();
+
private LocalBluetoothManager mManager;
private LocalBluetoothProfileManager mProfileManager;
private CachedBluetoothDevice mCachedDevice;
@@ -547,6 +554,22 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll
*/
@Override
protected void refresh() {
+ if (Flags.enableBluetoothProfileToggleVisibilityChecker()) {
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ mInvisiblePreferenceKey.set(
+ FeatureFactory.getFeatureFactory()
+ .getBluetoothFeatureProvider()
+ .getInvisibleProfilePreferenceKeys(
+ mContext, mCachedDevice.getDevice()));
+ ThreadUtils.postOnMainThread(this::refreshUi);
+ });
+ } else {
+ refreshUi();
+ }
+ }
+
+ private void refreshUi() {
for (LocalBluetoothProfile profile : getProfiles()) {
if (profile == null || !profile.isProfileReady()) {
continue;
@@ -577,6 +600,16 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll
preference.setSelectable(false);
mProfilesContainer.addPreference(preference);
}
+
+ if (Flags.enableBluetoothProfileToggleVisibilityChecker()) {
+ Set invisibleKeys = mInvisiblePreferenceKey.get();
+ if (invisibleKeys != null) {
+ for (int i = 0; i < mProfilesContainer.getPreferenceCount(); ++i) {
+ Preference pref = mProfilesContainer.getPreference(i);
+ pref.setVisible(pref.isVisible() && !invisibleKeys.contains(pref.getKey()));
+ }
+ }
+ }
}
@Override
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
index 0ce1b9787d95b760d60d064efe848e71efc99f15..e5fb365e1997a329044ea0212f392ed741a5103d 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
@@ -21,7 +21,6 @@ import static android.media.Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
import android.content.Context;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
import android.media.Spatializer;
import android.text.TextUtils;
import android.util.Log;
@@ -35,8 +34,12 @@ import androidx.preference.SwitchPreferenceCompat;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* The controller of the Spatial audio setting in the bluetooth detail settings.
@@ -56,14 +59,16 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont
@VisibleForTesting
AudioDeviceAttributes mAudioDevice = null;
+ AtomicBoolean mHasHeadTracker = new AtomicBoolean(false);
+
public BluetoothDetailsSpatialAudioController(
Context context,
PreferenceFragmentCompat fragment,
CachedBluetoothDevice device,
Lifecycle lifecycle) {
super(context, fragment, device, lifecycle);
- AudioManager audioManager = context.getSystemService(AudioManager.class);
- mSpatializer = audioManager.getSpatializer();
+ mSpatializer = FeatureFactory.getFeatureFactory().getBluetoothFeatureProvider()
+ .getSpatializer(context);
}
@Override
@@ -77,7 +82,13 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont
String key = switchPreference.getKey();
if (TextUtils.equals(key, KEY_SPATIAL_AUDIO)) {
updateSpatializerEnabled(switchPreference.isChecked());
- refreshSpatialAudioEnabled(switchPreference);
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ mHasHeadTracker.set(
+ mAudioDevice != null && mSpatializer.hasHeadTracker(mAudioDevice));
+ mContext.getMainExecutor()
+ .execute(() -> refreshSpatialAudioEnabled(switchPreference));
+ });
return true;
} else if (TextUtils.equals(key, KEY_HEAD_TRACKING)) {
updateSpatializerHeadTracking(switchPreference.isChecked());
@@ -124,7 +135,15 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont
if (mAudioDevice == null) {
getAvailableDevice();
}
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ mHasHeadTracker.set(
+ mAudioDevice != null && mSpatializer.hasHeadTracker(mAudioDevice));
+ mContext.getMainExecutor().execute(this::refreshUi);
+ });
+ }
+ private void refreshUi() {
TwoStatePreference spatialAudioPref = mProfilesContainer.findPreference(KEY_SPATIAL_AUDIO);
if (spatialAudioPref == null && mAudioDevice != null) {
spatialAudioPref = createSpatialAudioPreference(mProfilesContainer.getContext());
@@ -145,7 +164,8 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont
refreshSpatialAudioEnabled(spatialAudioPref);
}
- private void refreshSpatialAudioEnabled(TwoStatePreference spatialAudioPref) {
+ private void refreshSpatialAudioEnabled(
+ TwoStatePreference spatialAudioPref) {
boolean isSpatialAudioOn = mSpatializer.getCompatibleAudioDevices().contains(mAudioDevice);
Log.d(TAG, "refresh() isSpatialAudioOn : " + isSpatialAudioOn);
spatialAudioPref.setChecked(isSpatialAudioOn);
@@ -160,9 +180,8 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont
private void refreshHeadTracking(TwoStatePreference spatialAudioPref,
TwoStatePreference headTrackingPref) {
- boolean isHeadTrackingAvailable =
- spatialAudioPref.isChecked() && mSpatializer.hasHeadTracker(mAudioDevice);
- Log.d(TAG, "refresh() has head tracker : " + mSpatializer.hasHeadTracker(mAudioDevice));
+ boolean isHeadTrackingAvailable = spatialAudioPref.isChecked() && mHasHeadTracker.get();
+ Log.d(TAG, "refresh() has head tracker : " + mHasHeadTracker.get());
headTrackingPref.setVisible(isHeadTrackingAvailable);
if (isHeadTrackingAvailable) {
headTrackingPref.setChecked(mSpatializer.isHeadTrackerEnabled(mAudioDevice));
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index cfe79629c58c789808c9f140d81e2f61ac61328e..9c68c9cc870c04c09b4140f31d31b03c923ccc09 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -22,6 +22,7 @@ import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
+import android.content.Intent;
import android.content.res.TypedArray;
import android.hardware.input.InputManager;
import android.net.Uri;
@@ -53,6 +54,7 @@ import com.android.settings.slices.SlicePreferenceController;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.ArrayList;
@@ -324,10 +326,16 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
lifecycle));
controllers.add(new BluetoothDetailsPairOtherController(context, this, mCachedDevice,
lifecycle));
- controllers.add(new BluetoothDetailsHearingDeviceControlsController(context, this,
- mCachedDevice, lifecycle));
+ // Don't need to show hearing device again when launched from the same page.
+ if (!isLaunchFromHearingDevicePage()) {
+ controllers.add(new BluetoothDetailsHearingDeviceControlsController(context, this,
+ mCachedDevice, lifecycle));
+ }
controllers.add(new BluetoothDetailsDataSyncController(context, this,
mCachedDevice, lifecycle));
+ controllers.add(
+ new BluetoothDetailsExtraOptionsController(
+ context, this, mCachedDevice, lifecycle));
}
return controllers;
}
@@ -345,6 +353,16 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
return width;
}
+ private boolean isLaunchFromHearingDevicePage() {
+ final Intent intent = getIntent();
+ if (intent == null) {
+ return false;
+ }
+
+ return intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
+ SettingsEnums.PAGE_UNKNOWN) == SettingsEnums.ACCESSIBILITY_HEARING_AID_SETTINGS;
+ }
+
@VisibleForTesting
void setTitleForInputDevice() {
if (StylusDevicesController.isDeviceStylus(mInputDevice, mCachedDevice)) {
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
index 67c32ed5da138f7afadefbb9ba009144a90127f8..d71328eed3e7c111eccb04b2ae1c4681f25a2231 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
@@ -16,7 +16,6 @@
package com.android.settings.bluetooth;
-import static android.app.Activity.RESULT_OK;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.bluetooth.BluetoothAdapter;
@@ -94,7 +93,6 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
if (bondState == BluetoothDevice.BOND_BONDED) {
// If one device is connected(bonded), then close this fragment.
- setResult(RESULT_OK);
finish();
return;
} else if (bondState == BluetoothDevice.BOND_BONDING) {
@@ -126,7 +124,6 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
if (cachedDevice != null && cachedDevice.isConnected()) {
final BluetoothDevice device = cachedDevice.getDevice();
if (device != null && mSelectedList.contains(device)) {
- setResult(RESULT_OK);
finish();
} else {
onDeviceDeleted(cachedDevice);
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index 98d78f24341ab90e74f8049fca323c5797b63b2b..ac0c63bc6dec47509a539e4b2be63564eb7d4f39 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -156,7 +156,7 @@ public final class BluetoothDevicePreference extends GearPreference {
return R.layout.preference_widget_gear;
}
- CachedBluetoothDevice getCachedDevice() {
+ public CachedBluetoothDevice getCachedDevice() {
return mCachedDevice;
}
@@ -362,7 +362,11 @@ public final class BluetoothDevicePreference extends GearPreference {
}
}
- void onClicked() {
+ /**
+ * Performs different actions according to the device connected and bonded state after
+ * clicking on the preference.
+ */
+ public void onClicked() {
Context context = getContext();
int bondState = mCachedDevice.getBondState();
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java
index 59aa418643167f312a220aa71178dc2c38898084..4b21b2ecfb0932e3adbc5037a9113a15b3f983e0 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java
@@ -183,10 +183,8 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback,
@Override
public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
- if (DBG) {
- Log.d(getLogTag(), "onAclConnectionStateChanged() device: " + cachedDevice.getName()
- + ", state: " + state);
- }
+ Log.d(getLogTag(), "onAclConnectionStateChanged() device: " + cachedDevice.getName()
+ + ", state: " + state);
update(cachedDevice);
}
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
index 648ca307d591d54eb6d9281199326fd2a949e1fb..1751082a45feb6eb8d4633d80bd536c00b96db65 100644
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
@@ -18,9 +18,16 @@ package com.android.settings.bluetooth;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
+import android.content.Context;
+import android.media.Spatializer;
import android.net.Uri;
+import androidx.preference.Preference;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
import java.util.List;
+import java.util.Set;
/**
* Provider for bluetooth related features.
@@ -50,4 +57,31 @@ public interface BluetoothFeatureProvider {
* @return list of {@link ComponentName}
*/
List getRelatedTools();
+
+ /**
+ * Gets the instance of {@link Spatializer}.
+ *
+ * @param context Context
+ * @return the Spatializer instance
+ */
+ Spatializer getSpatializer(Context context);
+
+ /**
+ * Gets bluetooth device extra options
+ *
+ * @param context Context
+ * @param device the bluetooth device
+ * @return the extra bluetooth preference list
+ */
+ List getBluetoothExtraOptions(Context context, CachedBluetoothDevice device);
+
+ /**
+ * Gets the bluetooth profile preference keys which should be hidden in the device details page.
+ *
+ * @param context Context
+ * @param bluetoothDevice the bluetooth device
+ * @return the profiles which should be hidden
+ */
+ Set getInvisibleProfilePreferenceKeys(
+ Context context, BluetoothDevice bluetoothDevice);
}
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
index 6d0e15cdb5d2b1c261fc796497148cce1a2b0ab7..2d4ac496d49a1e18ad517eef71b5758faedf9dbb 100644
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
@@ -18,11 +18,21 @@ package com.android.settings.bluetooth;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.Spatializer;
import android.net.Uri;
+import androidx.preference.Preference;
+
import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.util.List;
+import java.util.Set;
/**
* Impl of {@link BluetoothFeatureProvider}
@@ -45,4 +55,22 @@ public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider {
public List getRelatedTools() {
return null;
}
+
+ @Override
+ public Spatializer getSpatializer(Context context) {
+ AudioManager audioManager = context.getSystemService(AudioManager.class);
+ return audioManager.getSpatializer();
+ }
+
+ @Override
+ public List getBluetoothExtraOptions(Context context,
+ CachedBluetoothDevice device) {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public Set getInvisibleProfilePreferenceKeys(
+ Context context, BluetoothDevice bluetoothDevice) {
+ return ImmutableSet.of();
+ }
}
diff --git a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
index 74c39b6f8c293089c125eb3b9265706ecf9bdc6e..c5b29f3e7e05ed7d21e2543ba11f23ad10682f83 100644
--- a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
@@ -132,7 +132,7 @@ abstract class BluetoothNameDialogFragment extends InstrumentedDialogFragment
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if (actionId == EditorInfo.IME_ACTION_DONE) {
+ if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
setDeviceName(v.getText().toString());
if (mAlertDialog != null && mAlertDialog.isShowing()) {
mAlertDialog.dismiss();
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
index 0cd36d0f471c62897dffdc5b58207d5ea7306050..d5b421184635c68916ec1dfa3e09c29aef82ee29 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
@@ -16,7 +16,8 @@
package com.android.settings.bluetooth;
-import android.annotation.Nullable;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -24,11 +25,10 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
/**
* BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
* for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
diff --git a/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java
index 6fd5070303c0c2608325716ab49ff058f1c77f62..ac5575803c1c1806f87b4d67db689d832da20b94 100644
--- a/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java
+++ b/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java
@@ -15,8 +15,13 @@
*/
package com.android.settings.bluetooth;
+import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.SETTING_NAME;
+import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.UNSET;
+
import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.view.View;
import androidx.annotation.VisibleForTesting;
@@ -29,6 +34,7 @@ import com.android.settings.widget.SwitchWidgetController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.FooterPreference;
/**
@@ -36,8 +42,11 @@ import com.android.settingslib.widget.FooterPreference;
* is delegated to the SwitchWidgetController it uses.
*/
public class BluetoothSwitchPreferenceController
- implements LifecycleObserver, OnStart, OnStop,
- SwitchWidgetController.OnSwitchChangeListener, View.OnClickListener {
+ implements LifecycleObserver,
+ OnStart,
+ OnStop,
+ SwitchWidgetController.OnSwitchChangeListener,
+ View.OnClickListener {
private BluetoothEnabler mBluetoothEnabler;
private RestrictionUtils mRestrictionUtils;
@@ -46,18 +55,21 @@ public class BluetoothSwitchPreferenceController
private FooterPreference mFooterPreference;
private boolean mIsAlwaysDiscoverable;
- @VisibleForTesting
- AlwaysDiscoverable mAlwaysDiscoverable;
+ @VisibleForTesting AlwaysDiscoverable mAlwaysDiscoverable;
- public BluetoothSwitchPreferenceController(Context context,
+ public BluetoothSwitchPreferenceController(
+ Context context,
SwitchWidgetController switchController,
FooterPreference footerPreference) {
this(context, new RestrictionUtils(), switchController, footerPreference);
}
@VisibleForTesting
- public BluetoothSwitchPreferenceController(Context context, RestrictionUtils restrictionUtils,
- SwitchWidgetController switchController, FooterPreference footerPreference) {
+ public BluetoothSwitchPreferenceController(
+ Context context,
+ RestrictionUtils restrictionUtils,
+ SwitchWidgetController switchController,
+ FooterPreference footerPreference) {
mRestrictionUtils = restrictionUtils;
mSwitch = switchController;
mContext = context;
@@ -66,11 +78,13 @@ public class BluetoothSwitchPreferenceController
mSwitch.setupView();
updateText(mSwitch.isChecked());
- mBluetoothEnabler = new BluetoothEnabler(context,
- switchController,
- FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(),
- SettingsEnums.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE,
- mRestrictionUtils);
+ mBluetoothEnabler =
+ new BluetoothEnabler(
+ context,
+ switchController,
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(),
+ SettingsEnums.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE,
+ mRestrictionUtils);
mBluetoothEnabler.setToggleCallback(this);
mAlwaysDiscoverable = new AlwaysDiscoverable(context);
}
@@ -97,8 +111,8 @@ public class BluetoothSwitchPreferenceController
/**
* Set whether the device can be discovered. By default the value will be {@code false}.
*
- * @param isAlwaysDiscoverable {@code true} if the device can be discovered,
- * otherwise {@code false}
+ * @param isAlwaysDiscoverable {@code true} if the device can be discovered, otherwise {@code
+ * false}
*/
public void setAlwaysDiscoverable(boolean isAlwaysDiscoverable) {
mIsAlwaysDiscoverable = isAlwaysDiscoverable;
@@ -119,15 +133,35 @@ public class BluetoothSwitchPreferenceController
.launch();
}
- @VisibleForTesting void updateText(boolean isChecked) {
+ @VisibleForTesting
+ void updateText(boolean isChecked) {
if (!isChecked && Utils.isBluetoothScanningEnabled(mContext)) {
- mFooterPreference.setTitle(R.string.bluetooth_scanning_on_info_message);
+ if (isAutoOnFeatureAvailable()) {
+ mFooterPreference.setTitle(
+ R.string.bluetooth_scanning_on_info_message_auto_on_available);
+ } else {
+ mFooterPreference.setTitle(R.string.bluetooth_scanning_on_info_message);
+ }
mFooterPreference.setLearnMoreText(mContext.getString(R.string.bluetooth_scan_change));
mFooterPreference.setLearnMoreAction(v -> onClick(v));
} else {
- mFooterPreference.setTitle(R.string.bluetooth_empty_list_bluetooth_off);
+ if (isAutoOnFeatureAvailable()) {
+ mFooterPreference.setTitle(
+ R.string.bluetooth_empty_list_bluetooth_off_auto_on_available);
+ } else {
+ mFooterPreference.setTitle(R.string.bluetooth_empty_list_bluetooth_off);
+ }
mFooterPreference.setLearnMoreText("");
mFooterPreference.setLearnMoreAction(null);
}
}
+
+ private boolean isAutoOnFeatureAvailable() {
+ if (!Flags.bluetoothQsTileDialogAutoOnToggle()) {
+ return false;
+ }
+ return Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), SETTING_NAME, UNSET, UserHandle.myUserId())
+ != UNSET;
+ }
}
diff --git a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
index 489c0953fdd76e9a4d1f672fc2cd0cccff844b15..d15696b5d07f1264c4833a3bb380d15504c887a5 100644
--- a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
@@ -24,7 +24,9 @@ import android.util.Log;
import androidx.preference.Preference;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
/**
* Controller to maintain connected bluetooth devices
@@ -95,6 +97,15 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater {
cachedDevice.getName() + ", isFilterMatched : " + isFilterMatched);
}
}
+ if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
+ if (BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ cachedDevice.getDevice())) {
+ if (DBG) {
+ Log.d(TAG, "isFilterMatched() hide BluetoothDevice with exclusive manager");
+ }
+ return false;
+ }
+ }
return isFilterMatched;
}
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
index 77a80b8eb1cdd8ac6195734281b1ea80c4519c4e..095bed97fb923db44e09bfef2d58ce8d459b4869 100644
--- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
+++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
@@ -36,9 +36,11 @@ import com.android.settings.R
import com.android.settings.dashboard.RestrictedDashboardFragment
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.BluetoothDeviceFilter
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.flags.Flags
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -217,6 +219,14 @@ abstract class DeviceListPreferenceFragment(restrictedKey: String?) :
)
return
}
+ if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
+ if (cachedDevice.device.bondState == BluetoothDevice.BOND_BONDED
+ && BluetoothUtils.isExclusivelyManagedBluetoothDevice(
+ prefContext, cachedDevice.device)) {
+ Log.d(TAG, "Trying to create preference for a exclusively managed device")
+ return
+ }
+ }
// Only add device preference when it's not found in the map and there's no other state
// message showing in the list
val preference = devicePreferenceMap.computeIfAbsent(cachedDevice) {
diff --git a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
index 12cbd58c48af770cd254fb5569dda07bf95600e4..73c7d73e856d1709947f1804c8eb2fa0da9035e9 100644
--- a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
+++ b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
@@ -28,7 +28,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
-import com.android.settings.accessibility.HearingDevicePairingDetail;
+import com.android.settings.accessibility.HearingDevicePairingFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -124,7 +124,7 @@ public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment
final boolean launchFromA11y = (launchPage == SettingsEnums.ACCESSIBILITY)
|| (launchPage == SettingsEnums.ACCESSIBILITY_HEARING_AID_SETTINGS);
final String destination = launchFromA11y
- ? HearingDevicePairingDetail.class.getName()
+ ? HearingDevicePairingFragment.class.getName()
: BluetoothPairingDetail.class.getName();
new SubSettingLauncher(getActivity())
.setDestination(destination)
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java b/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java
index ebca34250e4d6749b0ad32f4ca101323792e5fe2..6dd1105569e7f488ff42faf1e2ad82899b6e903b 100644
--- a/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java
+++ b/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java
@@ -16,7 +16,6 @@
package com.android.settings.bluetooth;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -26,6 +25,8 @@ import android.content.res.Configuration;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.bluetooth.LocalBluetoothManager;
/**
diff --git a/src/com/android/settings/bluetooth/QrCodeScanModeActivity.java b/src/com/android/settings/bluetooth/QrCodeScanModeActivity.java
index a0b249dee873b0dcbf13719faee0139c9329b084..a023420078ef46bde6c1971cd7d2de12fdbfbd35 100644
--- a/src/com/android/settings/bluetooth/QrCodeScanModeActivity.java
+++ b/src/com/android/settings/bluetooth/QrCodeScanModeActivity.java
@@ -18,6 +18,7 @@ package com.android.settings.bluetooth;
import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK;
import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP;
+import static com.android.settingslib.flags.Flags.legacyLeAudioSharing;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
@@ -53,6 +54,10 @@ public class QrCodeScanModeActivity extends QrCodeScanModeBaseActivity {
@Override
protected void handleIntent(Intent intent) {
+ if (!legacyLeAudioSharing()) {
+ finish();
+ }
+
String action = intent != null ? intent.getAction() : null;
if (DEBUG) {
Log.d(TAG, "handleIntent(), action = " + action);
diff --git a/src/com/android/settings/bluetooth/RequestPermissionActivity.java b/src/com/android/settings/bluetooth/RequestPermissionActivity.java
index 32ca2777392836cbb9a9ba1bcda7ea8d16d06b67..d670554a5c2ded340c845b8d1b408546a710f51a 100644
--- a/src/com/android/settings/bluetooth/RequestPermissionActivity.java
+++ b/src/com/android/settings/bluetooth/RequestPermissionActivity.java
@@ -18,7 +18,6 @@ package com.android.settings.bluetooth;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-import android.annotation.NonNull;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -37,6 +36,7 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
diff --git a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
index 43bc4f38d269f4f295b8e0e8950dcae0394e91b4..ed1be7a5c5f174ff44b4eb2dc0fd7c44636b033e 100644
--- a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
@@ -25,8 +25,10 @@ import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.flags.Flags;
import java.util.ArrayList;
import java.util.List;
@@ -38,7 +40,6 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater
implements Preference.OnPreferenceClickListener {
private static final String TAG = "SavedBluetoothDeviceUpdater";
- private static final boolean DBG = Log.isLoggable(BluetoothDeviceUpdater.TAG, Log.DEBUG);
private static final String PREF_KEY = "saved_bt";
@@ -100,14 +101,22 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater
@Override
public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
final BluetoothDevice device = cachedDevice.getDevice();
- if (DBG) {
- Log.d(TAG, "isFilterMatched() device name : " + cachedDevice.getName() +
- ", is connected : " + device.isConnected() + ", is profile connected : "
- + cachedDevice.isConnected());
+ boolean isExclusivelyManaged = BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ cachedDevice.getDevice());
+ Log.d(TAG, "isFilterMatched() device name : " + cachedDevice.getName()
+ + ", is connected : " + device.isConnected() + ", is profile connected : "
+ + cachedDevice.isConnected() + ", is exclusively managed : "
+ + isExclusivelyManaged);
+ if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
+ return device.getBondState() == BluetoothDevice.BOND_BONDED
+ && (mShowConnectedDevice || (!device.isConnected()
+ && isDeviceInCachedDevicesList(cachedDevice)))
+ && !isExclusivelyManaged;
+ } else {
+ return device.getBondState() == BluetoothDevice.BOND_BONDED
+ && (mShowConnectedDevice || (!device.isConnected()
+ && isDeviceInCachedDevicesList(cachedDevice)));
}
- return device.getBondState() == BluetoothDevice.BOND_BONDED
- && (mShowConnectedDevice || (!device.isConnected() && isDeviceInCachedDevicesList(
- cachedDevice)));
}
@Override
diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java
index f8c033c615ec5acd9b57eda162ced9a4a6891db4..f6288b2c85c61e67970cfe66fb895aaa607d6da4 100644
--- a/src/com/android/settings/bluetooth/Utils.java
+++ b/src/com/android/settings/bluetooth/Utils.java
@@ -37,12 +37,16 @@ import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
+import com.android.settings.flags.Flags;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback;
+import com.android.settingslib.utils.ThreadUtils;
+
+import com.google.common.base.Supplier;
import java.util.ArrayList;
import java.util.List;
@@ -272,4 +276,22 @@ public final class Utils {
+ " , deviceList = " + cachedBluetoothDevices);
return cachedBluetoothDevices;
}
+
+ /**
+ * Preloads the values and run the Runnable afterwards.
+ * @param suppliers the value supplier, should be a memoized supplier
+ * @param runnable the runnable to be run after value is preloaded
+ */
+ public static void preloadAndRun(List> suppliers, Runnable runnable) {
+ if (!Flags.enableOffloadBluetoothOperationsToBackgroundThread()) {
+ runnable.run();
+ return;
+ }
+ ThreadUtils.postOnBackgroundThread(() -> {
+ for (Supplier> supplier : suppliers) {
+ supplier.get();
+ }
+ ThreadUtils.postOnMainThread(runnable);
+ });
+ }
}
diff --git a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
index fc3493c9a9597a04da297ca82466a493798c5620..0535d153e91221363f9a75a496a7a5972a872d47 100644
--- a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
@@ -17,18 +17,17 @@ package com.android.settings.connecteddevice;
import static com.android.settingslib.Utils.isAudioModeOngoingCall;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
@@ -38,138 +37,66 @@ import com.android.settings.accessibility.HearingAidUtils;
import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnStart;
-import com.android.settingslib.core.lifecycle.events.OnStop;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
+import com.android.settingslib.core.lifecycle.Lifecycle;
/**
* Controller to maintain the {@link androidx.preference.PreferenceGroup} for all available media
* devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference}
*/
public class AvailableMediaDeviceGroupController extends BasePreferenceController
- implements LifecycleObserver, OnStart, OnStop, DevicePreferenceCallback, BluetoothCallback {
+ implements DefaultLifecycleObserver, DevicePreferenceCallback, BluetoothCallback {
private static final boolean DEBUG = BluetoothUtils.D;
private static final String TAG = "AvailableMediaDeviceGroupController";
private static final String KEY = "available_device_list";
- @VisibleForTesting PreferenceGroup mPreferenceGroup;
+ @VisibleForTesting @Nullable PreferenceGroup mPreferenceGroup;
@VisibleForTesting LocalBluetoothManager mLocalBluetoothManager;
- private final Executor mExecutor;
- private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
- private FragmentManager mFragmentManager;
- private BluetoothLeBroadcastAssistant.Callback mAssistantCallback =
- new BluetoothLeBroadcastAssistant.Callback() {
- @Override
- public void onSearchStarted(int reason) {}
-
- @Override
- public void onSearchStartFailed(int reason) {}
-
- @Override
- public void onSearchStopped(int reason) {}
-
- @Override
- public void onSearchStopFailed(int reason) {}
-
- @Override
- public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
-
- @Override
- public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
- mBluetoothDeviceUpdater.forceUpdate();
- }
-
- @Override
- public void onSourceAddFailed(
- @NonNull BluetoothDevice sink,
- @NonNull BluetoothLeBroadcastMetadata source,
- int reason) {}
+ @Nullable private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
+ @Nullable private FragmentManager mFragmentManager;
- @Override
- public void onSourceModified(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceModifyFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceRemoved(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {
- mBluetoothDeviceUpdater.forceUpdate();
- }
-
- @Override
- public void onSourceRemoveFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink,
- int sourceId,
- BluetoothLeBroadcastReceiveState state) {}
- };
-
- public AvailableMediaDeviceGroupController(Context context) {
+ public AvailableMediaDeviceGroupController(
+ Context context,
+ @Nullable DashboardFragment fragment,
+ @Nullable Lifecycle lifecycle) {
super(context, KEY);
+ if (fragment != null) {
+ init(fragment);
+ }
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ }
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
- mExecutor = Executors.newSingleThreadExecutor();
}
@Override
- public void onStart() {
+ public void onStart(@NonNull LifecycleOwner owner) {
if (mLocalBluetoothManager == null) {
Log.e(TAG, "onStart() Bluetooth is not supported on this device");
return;
}
- if (AudioSharingUtils.isFeatureEnabled()) {
- LocalBluetoothLeBroadcastAssistant assistant =
- mLocalBluetoothManager
- .getProfileManager()
- .getLeAudioBroadcastAssistantProfile();
- if (assistant != null) {
- if (DEBUG) {
- Log.d(TAG, "onStart() Register callbacks for assistant.");
- }
- assistant.registerServiceCallBack(mExecutor, mAssistantCallback);
- }
- }
- mBluetoothDeviceUpdater.registerCallback();
mLocalBluetoothManager.getEventManager().registerCallback(this);
- mBluetoothDeviceUpdater.refreshPreference();
+ if (mBluetoothDeviceUpdater != null) {
+ mBluetoothDeviceUpdater.registerCallback();
+ mBluetoothDeviceUpdater.refreshPreference();
+ }
}
@Override
- public void onStop() {
+ public void onStop(@NonNull LifecycleOwner owner) {
if (mLocalBluetoothManager == null) {
Log.e(TAG, "onStop() Bluetooth is not supported on this device");
return;
}
- if (AudioSharingUtils.isFeatureEnabled()) {
- LocalBluetoothLeBroadcastAssistant assistant =
- mLocalBluetoothManager
- .getProfileManager()
- .getLeAudioBroadcastAssistantProfile();
- if (assistant != null) {
- if (DEBUG) {
- Log.d(TAG, "onStop() Register callbacks for assistant.");
- }
- assistant.unregisterServiceCallBack(mAssistantCallback);
- }
+ if (mBluetoothDeviceUpdater != null) {
+ mBluetoothDeviceUpdater.unregisterCallback();
}
- mBluetoothDeviceUpdater.unregisterCallback();
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
@@ -178,12 +105,16 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
super.displayPreference(screen);
mPreferenceGroup = screen.findPreference(KEY);
- mPreferenceGroup.setVisible(false);
+ if (mPreferenceGroup != null) {
+ mPreferenceGroup.setVisible(false);
+ }
if (isAvailable()) {
updateTitle();
- mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
- mBluetoothDeviceUpdater.forceUpdate();
+ if (mBluetoothDeviceUpdater != null) {
+ mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
+ mBluetoothDeviceUpdater.forceUpdate();
+ }
}
}
@@ -201,17 +132,21 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
@Override
public void onDeviceAdded(Preference preference) {
- if (mPreferenceGroup.getPreferenceCount() == 0) {
- mPreferenceGroup.setVisible(true);
+ if (mPreferenceGroup != null) {
+ if (mPreferenceGroup.getPreferenceCount() == 0) {
+ mPreferenceGroup.setVisible(true);
+ }
+ mPreferenceGroup.addPreference(preference);
}
- mPreferenceGroup.addPreference(preference);
}
@Override
public void onDeviceRemoved(Preference preference) {
- mPreferenceGroup.removePreference(preference);
- if (mPreferenceGroup.getPreferenceCount() == 0) {
- mPreferenceGroup.setVisible(false);
+ if (mPreferenceGroup != null) {
+ mPreferenceGroup.removePreference(preference);
+ if (mPreferenceGroup.getPreferenceCount() == 0) {
+ mPreferenceGroup.setVisible(false);
+ }
}
}
@@ -253,14 +188,16 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
}
private void updateTitle() {
- if (isAudioModeOngoingCall(mContext)) {
- // in phone call
- mPreferenceGroup.setTitle(
- mContext.getString(R.string.connected_device_call_device_title));
- } else {
- // without phone call
- mPreferenceGroup.setTitle(
- mContext.getString(R.string.connected_device_media_device_title));
+ if (mPreferenceGroup != null) {
+ if (isAudioModeOngoingCall(mContext)) {
+ // in phone call
+ mPreferenceGroup.setTitle(
+ mContext.getString(R.string.connected_device_call_device_title));
+ } else {
+ // without phone call
+ mPreferenceGroup.setTitle(
+ mContext.getString(R.string.connected_device_media_device_title));
+ }
}
}
}
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
index 27001d6e071895ff54f10844153d93605d6233ea..04ba5d2bd78cd38b8ee0d3a39626995e400c0dac 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
@@ -22,13 +22,12 @@ import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingDevicePreferenceController;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
@@ -36,8 +35,13 @@ import com.android.settings.overlay.SurveyFeatureProvider;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.slices.SlicePreferenceController;
import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
+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.List;
+
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class ConnectedDeviceDashboardFragment extends DashboardFragment {
@@ -87,10 +91,6 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment {
+ ", action : "
+ action);
}
- if (AudioSharingUtils.isFeatureEnabled()) {
- use(AudioSharingDevicePreferenceController.class).init(this);
- }
- use(AvailableMediaDeviceGroupController.class).init(this);
use(ConnectedDeviceGroupController.class).init(this);
use(PreviouslyConnectedDevicePreferenceController.class).init(this);
use(SlicePreferenceController.class)
@@ -112,6 +112,31 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment {
}
}
+ @Override
+ protected List