{
}
protected abstract void onCountComplete(int num);
+
protected abstract boolean includeInCount(ApplicationInfo info);
}
diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java
index 3261d6cf3b969d14776906be4d4ed6dcdcaa2532..2c41be4ad413310f0b0145f7ebcb0018ddfd0f5a 100644
--- a/src/com/android/settings/applications/AppInfoBase.java
+++ b/src/com/android/settings/applications/AppInfoBase.java
@@ -90,8 +90,8 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment
super.onCreate(savedInstanceState);
mFinishing = false;
final Activity activity = getActivity();
- mApplicationFeatureProvider = FeatureFactory.getFactory(activity)
- .getApplicationFeatureProvider(activity);
+ mApplicationFeatureProvider = FeatureFactory.getFeatureFactory()
+ .getApplicationFeatureProvider();
mState = ApplicationsState.getInstance(activity.getApplication());
mSession = mState.newSession(this, getSettingsLifecycle());
mDpm = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
@@ -144,10 +144,14 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment
if (mAppEntry != null) {
// Get application info again to refresh changed properties of application
try {
- mPackageInfo = mPm.getPackageInfoAsUser(mAppEntry.info.packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS |
- PackageManager.GET_SIGNING_CERTIFICATES |
- PackageManager.GET_PERMISSIONS, mUserId);
+ mPackageInfo = mPm.getPackageInfoAsUser(
+ mAppEntry.info.packageName,
+ PackageManager.PackageInfoFlags.of(
+ PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.GET_SIGNING_CERTIFICATES
+ | PackageManager.GET_PERMISSIONS
+ | PackageManager.MATCH_ARCHIVED_PACKAGES),
+ mUserId);
} catch (NameNotFoundException e) {
Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
}
diff --git a/src/com/android/settings/applications/AppInfoWithHeader.java b/src/com/android/settings/applications/AppInfoWithHeader.java
index 7bf9f6460ac8c72bc8ef66479c294728af76cf12..8645628863ee2a413ed629c01cb6838436c7d5b8 100644
--- a/src/com/android/settings/applications/AppInfoWithHeader.java
+++ b/src/com/android/settings/applications/AppInfoWithHeader.java
@@ -46,7 +46,6 @@ public abstract class AppInfoWithHeader extends AppInfoBase {
final Activity activity = getActivity();
final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* header */)
- .setRecyclerView(getListView(), getSettingsLifecycle())
.setIcon(Utils.getBadgedIcon(getContext(), mPackageInfo.applicationInfo))
.setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
.setSummary(mPackageInfo)
@@ -55,7 +54,7 @@ public abstract class AppInfoWithHeader extends AppInfoBase {
.setUid(mPackageInfo.applicationInfo.uid)
.setHasAppInfoLink(true)
.setButtonActions(ActionType.ACTION_NONE, ActionType.ACTION_NONE)
- .done(activity, getPrefContext());
+ .done(getPrefContext());
getPreferenceScreen().addPreference(pref);
}
}
diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java
index 807f0432990831db0799d7830dec89493697943f..e45657fc0673af275b47400c3a4197134cf7a53a 100644
--- a/src/com/android/settings/applications/AppStorageSettings.java
+++ b/src/com/android/settings/applications/AppStorageSettings.java
@@ -53,6 +53,7 @@ import androidx.preference.PreferenceCategory;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.deviceinfo.StorageWizardMoveConfirm;
+import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState.Callbacks;
@@ -359,6 +360,8 @@ public class AppStorageSettings extends AppInfoWithHeader
mButtonsPref.setButton1Enabled(false);
// Invoke uninstall or clear user data based on sysPackage
String packageName = mAppEntry.info.packageName;
+ DynamicDenylistManager.getInstance(getContext())
+ .resetDenylistIfNeeded(packageName, /* force= */ false);
Log.i(TAG, "Clearing user data for package : " + packageName);
if (mClearDataObserver == null) {
mClearDataObserver = new ClearUserDataObserver();
diff --git a/src/com/android/settings/applications/InstalledAppCounter.java b/src/com/android/settings/applications/InstalledAppCounter.java
index aeac26e4e75e8302c4416d589b5e0fd2d8b3616e..9da4c9a8fde54352f7f902668d40ad0f62fafe97 100644
--- a/src/com/android/settings/applications/InstalledAppCounter.java
+++ b/src/com/android/settings/applications/InstalledAppCounter.java
@@ -17,10 +17,15 @@ package com.android.settings.applications;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureFlags;
+import android.content.pm.FeatureFlagsImpl;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
import java.util.List;
public abstract class InstalledAppCounter extends AppCounter {
@@ -32,9 +37,15 @@ public abstract class InstalledAppCounter extends AppCounter {
private final int mInstallReason;
- public InstalledAppCounter(Context context, int installReason,
- PackageManager packageManager) {
- super(context, packageManager);
+ public InstalledAppCounter(@NonNull Context context, int installReason,
+ @NonNull PackageManager packageManager) {
+ this(context, installReason, packageManager, new FeatureFlagsImpl());
+ }
+
+ @VisibleForTesting
+ InstalledAppCounter(@NonNull Context context, int installReason,
+ @NonNull PackageManager packageManager, @NonNull FeatureFlags featureFlags) {
+ super(context, packageManager, featureFlags);
mInstallReason = installReason;
}
diff --git a/src/com/android/settings/applications/ProcStatsData.java b/src/com/android/settings/applications/ProcStatsData.java
index 7742e98b56e8c89e65b7b2679ae498b88d73d78e..aedb06640d05242cfc26e09d693ea48c59abb3ef 100644
--- a/src/com/android/settings/applications/ProcStatsData.java
+++ b/src/com/android/settings/applications/ProcStatsData.java
@@ -29,6 +29,8 @@ import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
+import androidx.annotation.WorkerThread;
+
import com.android.internal.app.ProcessMap;
import com.android.internal.app.procstats.DumpUtils;
import com.android.internal.app.procstats.IProcessStats;
@@ -85,24 +87,10 @@ public class ProcStatsData {
}
}
- public void setTotalTime(int totalTime) {
- memTotalTime = totalTime;
- }
-
public void xferStats() {
sStatsXfer = mStats;
}
- public void setMemStates(int[] memStates) {
- mMemStates = memStates;
- refreshStats(false);
- }
-
- public void setStats(int[] stats) {
- this.mStates = stats;
- refreshStats(false);
- }
-
public int getMemState() {
int factor = mStats.mMemFactor;
if (factor == ProcessStats.ADJ_NOTHING) {
@@ -118,15 +106,13 @@ public class ProcStatsData {
return mMemInfo;
}
- public long getElapsedTime() {
- return mStats.mTimePeriodEndRealtime - mStats.mTimePeriodStartRealtime;
- }
-
+ /**
+ * Sets the duration.
+ *
+ * Note: {@link #refreshStats(boolean)} needs to called manually to take effect.
+ */
public void setDuration(long duration) {
- if (duration != mDuration) {
- mDuration = duration;
- refreshStats(true);
- }
+ mDuration = duration;
}
public long getDuration() {
@@ -137,6 +123,12 @@ public class ProcStatsData {
return pkgEntries;
}
+ /**
+ * Refreshes the stats.
+ *
+ *
Note: This needs to be called manually to take effect.
+ */
+ @WorkerThread
public void refreshStats(boolean forceLoad) {
if (mStats == null || forceLoad) {
load();
diff --git a/src/com/android/settings/applications/ProcessStatsDetail.java b/src/com/android/settings/applications/ProcessStatsDetail.java
index 266c19513018b7b4105a364ed89e6a886e4f933f..de21a46a9b24bf609feb4a96fbebee3ba989378e 100644
--- a/src/com/android/settings/applications/ProcessStatsDetail.java
+++ b/src/com/android/settings/applications/ProcessStatsDetail.java
@@ -129,7 +129,6 @@ public class ProcessStatsDetail extends SettingsPreferenceFragment {
final Activity activity = getActivity();
final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* appHeader */)
- .setRecyclerView(getListView(), getSettingsLifecycle())
.setIcon(mApp.mUiTargetApp != null
? IconDrawableFactory.newInstance(activity).getBadgedIcon(mApp.mUiTargetApp)
: new ColorDrawable(0))
@@ -140,7 +139,7 @@ public class ProcessStatsDetail extends SettingsPreferenceFragment {
: UserHandle.USER_NULL)
.setHasAppInfoLink(true)
.setButtonActions(ActionType.ACTION_NONE, ActionType.ACTION_NONE)
- .done(activity, getPrefContext());
+ .done(getPrefContext());
getPreferenceScreen().addPreference(pref);
}
diff --git a/src/com/android/settings/applications/RunningServiceDetails.java b/src/com/android/settings/applications/RunningServiceDetails.java
index e95b41ae0e78de9bb851be3dc5c40d437765cfa6..3d5bddb4cc563b10d28b122a6349cd57be1e82be 100644
--- a/src/com/android/settings/applications/RunningServiceDetails.java
+++ b/src/com/android/settings/applications/RunningServiceDetails.java
@@ -226,8 +226,8 @@ public class RunningServiceDetails extends InstrumentedFragment
void addServicesHeader() {
if (mNumServices == 0) {
- mServicesHeader = (TextView) mInflater.inflate(R.layout.preference_category,
- mAllDetails, false);
+ mServicesHeader = (TextView) mInflater.inflate(
+ androidx.preference.R.layout.preference_category, mAllDetails, false);
mServicesHeader.setText(R.string.runningservicedetails_services_title);
mAllDetails.addView(mServicesHeader);
}
@@ -236,8 +236,8 @@ public class RunningServiceDetails extends InstrumentedFragment
void addProcessesHeader() {
if (mNumProcesses == 0) {
- mProcessesHeader = (TextView) mInflater.inflate(R.layout.preference_category,
- mAllDetails, false);
+ mProcessesHeader = (TextView) mInflater.inflate(
+ androidx.preference.R.layout.preference_category, mAllDetails, false);
mProcessesHeader.setText(R.string.runningservicedetails_processes_title);
mAllDetails.addView(mProcessesHeader);
}
diff --git a/src/com/android/settings/applications/RunningServices.java b/src/com/android/settings/applications/RunningServices.java
index b1689d5c5913680e04eea9ebd57c56c62eb77f96..c75fe0698bcbc67e3cb105e6a35e68b405f178fa 100644
--- a/src/com/android/settings/applications/RunningServices.java
+++ b/src/com/android/settings/applications/RunningServices.java
@@ -42,7 +42,7 @@ public class RunningServices extends SettingsPreferenceFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getActivity().setTitle(R.string.runningservices_settings_title);
+ getActivity().setTitle(com.android.settingslib.R.string.runningservices_settings_title);
}
@Override
diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java
index 4adb6c179535d9e56161d9ab04f39bd49b97d9c8..b81c7192a22d544c442e0b0f297c6539dda4b168 100644
--- a/src/com/android/settings/applications/UsageAccessDetails.java
+++ b/src/com/android/settings/applications/UsageAccessDetails.java
@@ -38,7 +38,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.Preference.OnPreferenceClickListener;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.applications.AppStateUsageBridge.UsageState;
@@ -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 SwitchPreference mSwitchPref;
+ private TwoStatePreference 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 = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
+ mSwitchPref = (TwoStatePreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
mUsageDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC);
getPreferenceScreen().setTitle(R.string.usage_access);
@@ -148,7 +148,7 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
int logCategory = newState ? SettingsEnums.APP_SPECIAL_PERMISSION_USAGE_VIEW_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_USAGE_VIEW_DENY;
final MetricsFeatureProvider metricsFeatureProvider =
- FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider();
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
metricsFeatureProvider.action(
metricsFeatureProvider.getAttribution(getActivity()),
logCategory,
diff --git a/src/com/android/settings/applications/appcompat/RadioWithImagePreference.java b/src/com/android/settings/applications/appcompat/RadioWithImagePreference.java
index 77cd86c23dc023ed76b20a08474ec74e125dfac4..b47b679aec9fb8ed9371fa74f0d017ebb9c89a06 100644
--- a/src/com/android/settings/applications/appcompat/RadioWithImagePreference.java
+++ b/src/com/android/settings/applications/appcompat/RadioWithImagePreference.java
@@ -127,7 +127,7 @@ public class RadioWithImagePreference extends CheckBoxPreference {
}
private void init() {
- setWidgetLayoutResource(com.android.settingslib.R.layout.preference_widget_radiobutton);
+ setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
setLayoutResource(R.layout.radio_with_image_preference);
setIconSpaceReserved(false);
}
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
index dfb583ce2fc51f564e9efd1d25674105be5949b8..02d5c27e5c667db080ebac2ab54fb177d571283f 100644
--- a/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
@@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import android.app.ActivityManager;
import android.app.IActivityManager;
+import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
@@ -44,8 +45,10 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.widget.ActionButtonsPreference;
import java.util.ArrayList;
@@ -104,6 +107,7 @@ public class UserAspectRatioDetails extends AppInfoBase implements
Log.e(TAG, "Unable to set user min aspect ratio");
return;
}
+ logActionMetrics(selectedKey, mSelectedKey);
// Only update to selected aspect ratio if nothing goes wrong
mSelectedKey = selectedKey;
updateAllPreferences(mSelectedKey);
@@ -118,8 +122,7 @@ public class UserAspectRatioDetails extends AppInfoBase implements
@Override
public int getMetricsCategory() {
- // TODO(b/292566895): add metrics for logging
- return 0;
+ return SettingsEnums.USER_ASPECT_RATIO_APP_INFO_SETTINGS;
}
@Override
@@ -197,7 +200,7 @@ public class UserAspectRatioDetails extends AppInfoBase implements
.setHasAppInfoLink(true)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE)
- .done(getActivity(), getPrefContext());
+ .done(getPrefContext());
getPreferenceScreen().addPreference(pref);
}
@@ -244,6 +247,68 @@ public class UserAspectRatioDetails extends AppInfoBase implements
}
}
+ private void logActionMetrics(@NonNull String selectedKey, @NonNull String unselectedKey) {
+ final MetricsFeatureProvider metricsFeatureProvider =
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
+ final int attribution = metricsFeatureProvider.getAttribution(getActivity());
+ metricsFeatureProvider.action(
+ attribution,
+ getUnselectedAspectRatioAction(unselectedKey),
+ getMetricsCategory(),
+ mPackageName,
+ mUserId
+ );
+ metricsFeatureProvider.action(
+ attribution,
+ getSelectedAspectRatioAction(selectedKey),
+ getMetricsCategory(),
+ mPackageName,
+ mUserId
+ );
+ }
+
+ private static int getSelectedAspectRatioAction(@NonNull String selectedKey) {
+ switch (selectedKey) {
+ case KEY_PREF_DEFAULT:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_APP_DEFAULT_SELECTED;
+ case KEY_PREF_FULLSCREEN:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_FULL_SCREEN_SELECTED;
+ case KEY_PREF_HALF_SCREEN:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_HALF_SCREEN_SELECTED;
+ case KEY_PREF_4_3:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_4_3_SELECTED;
+ case KEY_PREF_16_9:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_16_9_SELECTED;
+ case KEY_PREF_3_2:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_3_2_SELECTED;
+ case KEY_PREF_DISPLAY_SIZE:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_DISPLAY_SIZE_SELECTED;
+ default:
+ return SettingsEnums.ACTION_UNKNOWN;
+ }
+ }
+
+ private static int getUnselectedAspectRatioAction(@NonNull String unselectedKey) {
+ switch (unselectedKey) {
+ case KEY_PREF_DEFAULT:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_APP_DEFAULT_UNSELECTED;
+ case KEY_PREF_FULLSCREEN:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_FULL_SCREEN_UNSELECTED;
+ case KEY_PREF_HALF_SCREEN:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_HALF_SCREEN_UNSELECTED;
+ case KEY_PREF_4_3:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_4_3_UNSELECTED;
+ case KEY_PREF_16_9:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_16_9_UNSELECTED;
+ case KEY_PREF_3_2:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_3_2_UNSELECTED;
+ case KEY_PREF_DISPLAY_SIZE:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_DISPLAY_SIZE_UNSELECTED;
+ default:
+ return SettingsEnums.ACTION_UNKNOWN;
+ }
+ }
+
@VisibleForTesting
UserAspectRatioManager getAspectRatioManager() {
return mUserAspectRatioManager;
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
index b940dc844a5bb4a599185d0b56b60a04051b262b..3cca5f6771e4183d57fd51542c1cc7668d8f129f 100644
--- a/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
@@ -24,7 +24,6 @@ import static java.lang.Boolean.FALSE;
import android.app.AppGlobals;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
@@ -48,10 +47,6 @@ import java.util.Map;
* {@link PackageManager.UserMinAspectRatio} set by user
*/
public class UserAspectRatioManager {
- private static final Intent LAUNCHER_ENTRY_INTENT =
- new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
-
- // TODO(b/288142656): Enable user aspect ratio settings by default
private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS = true;
@VisibleForTesting
static final String KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS =
@@ -173,12 +168,9 @@ public class UserAspectRatioManager {
DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN);
}
- LauncherApps getLauncherApps() {
- return mContext.getSystemService(LauncherApps.class);
- }
-
private boolean hasLauncherEntry(@NonNull ApplicationInfo app) {
- return !getLauncherApps().getActivityList(app.packageName, getUserHandleForUid(app.uid))
+ return !mContext.getSystemService(LauncherApps.class)
+ .getActivityList(app.packageName, getUserHandleForUid(app.uid))
.isEmpty();
}
@@ -232,7 +224,7 @@ public class UserAspectRatioManager {
@NonNull
private String getAccessibleOption(String numerator, String denominator) {
- return mContext.getResources().getString(R.string.user_aspect_ratio_option_a11y,
+ return mContext.getString(R.string.user_aspect_ratio_option_a11y,
numerator, denominator);
}
diff --git a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
index 8decbd99ff77ef2ae9ade67b58f88242766840a6..35fc1cb19634a8fe06e2d83e50d0823de885456c 100644
--- a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
@@ -124,8 +124,9 @@ public class AppBatteryPreferenceController extends BasePreferenceController
mParent.getMetricsCategory(),
mBatteryDiffEntry,
Utils.formatPercentage(
- mBatteryDiffEntry.getPercentage(), /* round */ true),
- /*slotInformation=*/ null, /*showTimeInformation=*/ false);
+ mBatteryDiffEntry.getPercentage(), /*round=*/ true),
+ /*slotInformation=*/ null, /*showTimeInformation=*/ false,
+ /*anomalyHintPrefKey=*/ null, /*anomalyHintText=*/ null);
return true;
}
diff --git a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java
index ff191ab4476737015daf1b81d85e475e3254f0ff..03053fdeeb63fd32b6c8158dc65996ef5f30ada0 100644
--- a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java
@@ -138,9 +138,9 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp
"Fragment should implement AppButtonsDialogListener");
}
- final FeatureFactory factory = FeatureFactory.getFactory(activity);
+ final FeatureFactory factory = FeatureFactory.getFeatureFactory();
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
- mApplicationFeatureProvider = factory.getApplicationFeatureProvider(activity);
+ mApplicationFeatureProvider = factory.getApplicationFeatureProvider();
mState = state;
mDpm = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE);
diff --git a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java
index 32662a22347ab91d551c7a44c873b88e906df3d8..baa1dafa4699721e1bfd6c1a964e001f8701e80e 100644
--- a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java
@@ -17,7 +17,6 @@
package com.android.settings.applications.appinfo;
import android.content.Context;
-import android.net.NetworkStats;
import android.net.NetworkTemplate;
import android.os.Bundle;
import android.os.Process;
@@ -34,8 +33,8 @@ import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.datausage.AppDataUsage;
-import com.android.settings.datausage.DataUsageUtils;
-import com.android.settings.network.SubscriptionUtil;
+import com.android.settings.datausage.lib.NetworkTemplates;
+import com.android.settings.spa.app.appinfo.AppDataUsagePreferenceKt;
import com.android.settingslib.AppItem;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -46,6 +45,10 @@ import com.android.settingslib.net.NetworkCycleDataForUidLoader;
import java.util.List;
+/**
+ * @deprecated Will be removed, use {@link AppDataUsagePreferenceKt} instead.
+ */
+@Deprecated(forRemoval = true)
public class AppDataUsagePreferenceController extends AppInfoPreferenceControllerBase
implements LoaderManager.LoaderCallbacks>, LifecycleObserver,
OnResume, OnPause {
@@ -92,7 +95,7 @@ public class AppDataUsagePreferenceController extends AppInfoPreferenceControlle
@Override
public Loader> onCreateLoader(int id, Bundle args) {
- final NetworkTemplate template = getTemplate(mContext);
+ final NetworkTemplate template = NetworkTemplates.INSTANCE.getDefaultTemplate(mContext);
final int uid = mParent.getAppEntry().info.uid;
final NetworkCycleDataForUidLoader.Builder builder =
@@ -147,18 +150,6 @@ public class AppDataUsagePreferenceController extends AppInfoPreferenceControlle
return mContext.getString(R.string.computing_size);
}
- private static NetworkTemplate getTemplate(Context context) {
- if (SubscriptionUtil.isSimHardwareVisible(context)
- && DataUsageUtils.hasReadyMobileRadio(context)) {
- return new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE).setMeteredness(
- NetworkStats.METERED_YES).build();
- }
- if (DataUsageUtils.hasWifiRadio(context)) {
- return new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build();
- }
- return new NetworkTemplate.Builder(NetworkTemplate.MATCH_ETHERNET).build();
- }
-
@VisibleForTesting
boolean isBandwidthControlEnabled() {
return Utils.isBandwidthControlEnabled();
diff --git a/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java b/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java
index 285493a16c9e354956de4fdca1e700fb2f97bb60..554208769e3c1575422c0564b66018d55a48210f 100644
--- a/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java
@@ -66,7 +66,6 @@ public class AppHeaderViewPreferenceController extends BasePreferenceController
final Activity activity = mParent.getActivity();
mEntityHeaderController = EntityHeaderController
.newInstance(activity, mParent, mHeader.findViewById(R.id.entity_header))
- .setRecyclerView(mParent.getListView(), mLifecycle)
.setPackageName(mPackageName)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE)
@@ -86,6 +85,6 @@ public class AppHeaderViewPreferenceController extends BasePreferenceController
.setLabel(appEntry)
.setIcon(appEntry)
.setIsInstantApp(isInstantApp)
- .done(activity, false /* rebindActions */);
+ .done(false /* rebindActions */);
}
}
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index e771ff4776130f39c06727505c5157ec4bb41a9a..82d55f3d9f820732e60285b9e4afa9ea529709c7 100644
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -731,10 +731,12 @@ public class AppInfoDashboardFragment extends DashboardFragment
try {
mPackageInfo = activity.getPackageManager().getPackageInfo(
mAppEntry.info.packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS |
- PackageManager.MATCH_ANY_USER |
- PackageManager.GET_SIGNATURES |
- PackageManager.GET_PERMISSIONS);
+ PackageManager.PackageInfoFlags.of(
+ PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_ANY_USER
+ | PackageManager.GET_SIGNATURES
+ | PackageManager.GET_PERMISSIONS
+ | PackageManager.MATCH_ARCHIVED_PACKAGES));
} catch (NameNotFoundException e) {
Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
}
diff --git a/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java
index 5e99e8bedb17ac7f255923ae54171c87322b0aff..1216ae8cc363f6fa13fdd01380f0d337dc3ca452 100644
--- a/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java
@@ -18,7 +18,6 @@ package com.android.settings.applications.appinfo;
import android.content.Context;
import android.content.Intent;
-import android.os.UserManager;
import androidx.preference.Preference;
@@ -39,10 +38,6 @@ public class AppInstallerInfoPreferenceController extends AppInfoPreferenceContr
@Override
public int getAvailabilityStatus() {
- if (UserManager.get(mContext).isManagedProfile()) {
- return DISABLED_FOR_USER;
- }
-
if (AppUtils.isMainlineModule(mContext.getPackageManager(), mPackageName)) {
return DISABLED_FOR_USER;
}
diff --git a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java
index 6144a73821c0e957d6f127734dde795535688e88..1e7ca1e5802803bcf871ecf66cb8756ccebcfb9a 100644
--- a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java
+++ b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java
@@ -137,7 +137,6 @@ public class AppLocaleDetails extends SettingsPreferenceFragment {
final Activity activity = getActivity();
final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* header */)
- .setRecyclerView(getListView(), getSettingsLifecycle())
.setIcon(Utils.getBadgedIcon(getContext(), mApplicationInfo))
.setLabel(mApplicationInfo.loadLabel(getContext().getPackageManager()))
.setIsInstantApp(AppUtils.isInstant(mApplicationInfo))
@@ -146,7 +145,7 @@ public class AppLocaleDetails extends SettingsPreferenceFragment {
.setHasAppInfoLink(true)
.setButtonActions(ActionType.ACTION_NONE, ActionType.ACTION_NONE)
.setOrder(10)
- .done(activity, getPrefContext());
+ .done(getPrefContext());
getPreferenceScreen().addPreference(pref);
}
diff --git a/src/com/android/settings/applications/appinfo/AppSettingPreferenceController.java b/src/com/android/settings/applications/appinfo/AppSettingPreferenceController.java
index cacbffb946f0a858ed519dd38f6a9678fcbba3af..52ed31157c945d143fca38f914152c5a15f2b591 100644
--- a/src/com/android/settings/applications/appinfo/AppSettingPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppSettingPreferenceController.java
@@ -59,7 +59,7 @@ public class AppSettingPreferenceController extends AppInfoPreferenceControllerB
if (intent == null) {
return false;
}
- FeatureFactory.getFactory(mContext).getMetricsFeatureProvider()
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
.action(SettingsEnums.PAGE_UNKNOWN,
SettingsEnums.ACTION_OPEN_APP_SETTING,
mParent.getMetricsCategory(),
diff --git a/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java b/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java
index 5f7e56fa0e46822d75960300d66f77561b94cf6f..409b695f3128dd0371a2a4e5dffeec98a884d7ab 100644
--- a/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java
+++ b/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java
@@ -23,12 +23,13 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.Preference.OnPreferenceClickListener;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.Utils;
@@ -50,7 +51,8 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc
// TODO: Break out this functionality into its own class.
private AppStateOverlayBridge mOverlayBridge;
private AppOpsManager mAppOpsManager;
- private SwitchPreference mSwitchPref;
+ @Nullable
+ private TwoStatePreference mSwitchPref = null;
private OverlayState mOverlayState;
@Override
@@ -121,7 +123,7 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc
int logCategory = newState ? SettingsEnums.APP_SPECIAL_PERMISSION_APPDRAW_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_APPDRAW_DENY;
final MetricsFeatureProvider metricsFeatureProvider =
- FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider();
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
metricsFeatureProvider.action(
metricsFeatureProvider.getAttribution(getActivity()),
logCategory,
diff --git a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
index b723274436525a663dfcfcf82e6cabdeaaeb559a..9a41f2519bfe8a63c43a7ef3d8c3849f1db38263 100644
--- a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
+++ b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
@@ -88,9 +88,9 @@ public class ExternalSourcesDetails extends AppInfoWithHeader
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
userHandle);
if ((userRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
- return context.getString(R.string.disabled_by_admin);
+ return context.getString(com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
} else if (userRestrictionSource != 0) {
- return context.getString(R.string.disabled);
+ return context.getString(com.android.settingslib.R.string.disabled);
}
final InstallAppsState appsState = new AppStateInstallAppsBridge(context, null, null)
.createInstallAppsStateFor(entry.info.packageName, entry.info.uid);
@@ -113,7 +113,7 @@ public class ExternalSourcesDetails extends AppInfoWithHeader
if (mUserManager.hasBaseUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
UserHandle.of(UserHandle.myUserId()))) {
mSwitchPref.setChecked(false);
- mSwitchPref.setSummary(R.string.disabled);
+ mSwitchPref.setSummary(com.android.settingslib.R.string.disabled);
mSwitchPref.setEnabled(false);
return true;
}
diff --git a/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java b/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java
index e3c577b189ef03643bc48ac7c9f16b8beda46fe1..12703dd6d62981f2215d832806ebc4f8c6e21d25 100644
--- a/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java
@@ -37,7 +37,7 @@ import android.util.Slog;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.google.common.annotations.VisibleForTesting;
@@ -107,7 +107,7 @@ public final class HibernationSwitchPreferenceController extends AppInfoPreferen
@Override
public void updateState(Preference preference) {
super.updateState(preference);
- ((SwitchPreference) preference).setChecked(isAppEligibleForHibernation()
+ ((TwoStatePreference) preference).setChecked(isAppEligibleForHibernation()
&& !isPackageHibernationExemptByUser());
preference.setEnabled(isAppEligibleForHibernation());
if (!mHibernationEligibilityLoaded) {
diff --git a/src/com/android/settings/applications/appinfo/InstantAppButtonDialogFragment.java b/src/com/android/settings/applications/appinfo/InstantAppButtonDialogFragment.java
index 5a6d3df46c36bcc46b88219dab7e8d9fce364811..f514ed67862170f101701d8006022769d39a7186 100644
--- a/src/com/android/settings/applications/appinfo/InstantAppButtonDialogFragment.java
+++ b/src/com/android/settings/applications/appinfo/InstantAppButtonDialogFragment.java
@@ -63,7 +63,7 @@ public class InstantAppButtonDialogFragment extends InstrumentedDialogFragment i
public void onClick(DialogInterface dialog, int which) {
final Context context = getContext();
final PackageManager packageManager = context.getPackageManager();
- FeatureFactory.getFactory(context).getMetricsFeatureProvider()
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
.action(context, SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP, mPackageName);
packageManager.deletePackageAsUser(mPackageName, null, 0, UserHandle.myUserId());
}
diff --git a/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceController.java b/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceController.java
index 68f893c360214b1abe621f0ba985bba048e837de..0f2b5aaa2293aad4fa01066e81f5c61beacecf2e 100644
--- a/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceController.java
@@ -40,8 +40,8 @@ public class LongBackgroundTasksDetailsPreferenceController extends
public LongBackgroundTasksDetailsPreferenceController(Context context, String key) {
super(context, key);
- mAppFeatureProvider = FeatureFactory.getFactory(context)
- .getApplicationFeatureProvider(context);
+ mAppFeatureProvider = FeatureFactory.getFeatureFactory()
+ .getApplicationFeatureProvider();
}
@VisibleForTesting
diff --git a/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java b/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java
index 6c840d5d94deec27d506dbff4e3a3c5c92b8a3b5..e7eff9fc377cc3e17a3184087b15c57e75e38eac 100644
--- a/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java
+++ b/src/com/android/settings/applications/appinfo/ManageExternalStorageDetails.java
@@ -27,7 +27,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.Preference.OnPreferenceClickListener;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoWithHeader;
@@ -47,7 +47,7 @@ public class ManageExternalStorageDetails extends AppInfoWithHeader implements
private AppStateManageExternalStorageBridge mBridge;
private AppOpsManager mAppOpsManager;
- private SwitchPreference mSwitchPref;
+ private TwoStatePreference mSwitchPref;
private PermissionState mPermissionState;
private MetricsFeatureProvider mMetricsFeatureProvider;
@@ -67,7 +67,7 @@ public class ManageExternalStorageDetails extends AppInfoWithHeader implements
mSwitchPref.setOnPreferenceChangeListener(this);
mMetricsFeatureProvider =
- FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider();
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
@Override
diff --git a/src/com/android/settings/applications/appinfo/MediaManagementAppsDetails.java b/src/com/android/settings/applications/appinfo/MediaManagementAppsDetails.java
index f60fb4fdaa057f3a0cca831c7606fe3cf365aef2..71b494b76314b39f5b81cfb3ab6579cd30158dac 100644
--- a/src/com/android/settings/applications/appinfo/MediaManagementAppsDetails.java
+++ b/src/com/android/settings/applications/appinfo/MediaManagementAppsDetails.java
@@ -23,7 +23,7 @@ import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoWithHeader;
@@ -41,7 +41,7 @@ public class MediaManagementAppsDetails extends AppInfoWithHeader implements
private AppStateMediaManagementAppsBridge mAppBridge;
private AppOpsManager mAppOpsManager;
- private SwitchPreference mSwitchPref;
+ private TwoStatePreference mSwitchPref;
private PermissionState mPermissionState;
@Override
diff --git a/src/com/android/settings/applications/appinfo/TimeSpentInAppPreferenceController.java b/src/com/android/settings/applications/appinfo/TimeSpentInAppPreferenceController.java
index 3682af27f2fddc2aa8be5898db7a8eb10acd2f41..b0ef00c508e317744a960fc74981bcd59306288a 100644
--- a/src/com/android/settings/applications/appinfo/TimeSpentInAppPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/TimeSpentInAppPreferenceController.java
@@ -53,8 +53,8 @@ public class TimeSpentInAppPreferenceController extends LiveDataController {
public TimeSpentInAppPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mPackageManager = context.getPackageManager();
- mAppFeatureProvider = FeatureFactory.getFactory(context)
- .getApplicationFeatureProvider(context);
+ mAppFeatureProvider = FeatureFactory.getFeatureFactory()
+ .getApplicationFeatureProvider();
}
public void setPackageName(String packageName) {
diff --git a/src/com/android/settings/applications/appinfo/TurnScreenOnDetails.java b/src/com/android/settings/applications/appinfo/TurnScreenOnDetails.java
index 39e1a5fa2adac9b0061b60d8deedbbfce37997b0..95694db5a79ae07e014fc9326073e855fd02b40d 100644
--- a/src/com/android/settings/applications/appinfo/TurnScreenOnDetails.java
+++ b/src/com/android/settings/applications/appinfo/TurnScreenOnDetails.java
@@ -28,7 +28,7 @@ import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.Settings;
@@ -46,7 +46,7 @@ public class TurnScreenOnDetails extends AppInfoWithHeader implements OnPreferen
private AppStateTurnScreenOnBridge mAppBridge;
private AppOpsManager mAppOpsManager;
- private SwitchPreference mSwitchPref;
+ private TwoStatePreference mSwitchPref;
private AppStateAppOpsBridge.PermissionState mPermissionState;
@@ -60,7 +60,7 @@ public class TurnScreenOnDetails extends AppInfoWithHeader implements OnPreferen
// find preferences
addPreferencesFromResource(R.xml.turn_screen_on_permissions_details);
- mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
+ mSwitchPref = (TwoStatePreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
mSwitchPref.setOnPreferenceChangeListener(this);
}
diff --git a/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java b/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java
index d17f843a3377d008ef8899c9d0ea11d7994538cc..94ebc41f06ca188f9564c3628390ac11d7c81c72 100644
--- a/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java
+++ b/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java
@@ -24,7 +24,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.Preference.OnPreferenceClickListener;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoWithHeader;
@@ -43,7 +43,7 @@ public class WriteSettingsDetails extends AppInfoWithHeader implements OnPrefere
// TODO: Break out this functionality into its own class.
private AppStateWriteSettingsBridge mAppBridge;
private AppOpsManager mAppOpsManager;
- private SwitchPreference mSwitchPref;
+ private TwoStatePreference mSwitchPref;
private WriteSettingsState mWriteSettingsState;
@Override
@@ -89,7 +89,7 @@ public class WriteSettingsDetails extends AppInfoWithHeader implements OnPrefere
void logSpecialPermissionChange(boolean newState, String packageName) {
int logCategory = newState ? SettingsEnums.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_DENY;
- FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(getContext(),
logCategory, packageName);
}
diff --git a/src/com/android/settings/applications/appops/AppOpsCategory.java b/src/com/android/settings/applications/appops/AppOpsCategory.java
index 57d7dc4c49005727bfebd2dcab1a7be4cabd6337..95ee256a1fd739667e3ab3bd28eb61dd4f51b4cb 100644
--- a/src/com/android/settings/applications/appops/AppOpsCategory.java
+++ b/src/com/android/settings/applications/appops/AppOpsCategory.java
@@ -29,9 +29,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
+import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ListView;
-import android.widget.Switch;
import android.widget.TextView;
import androidx.fragment.app.ListFragment;
@@ -244,14 +244,12 @@ public class AppOpsCategory extends ListFragment implements
public static class AppListAdapter extends BaseAdapter {
private final Resources mResources;
private final LayoutInflater mInflater;
- private final AppOpsState mState;
List mList;
- public AppListAdapter(Context context, AppOpsState state) {
+ public AppListAdapter(Context context) {
mResources = context.getResources();
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mState = state;
}
public void setData(List data) {
@@ -294,7 +292,7 @@ public class AppOpsCategory extends ListFragment implements
((TextView) view.findViewById(R.id.op_name)).setText(
item.getTimeText(mResources, false));
view.findViewById(R.id.op_time).setVisibility(View.GONE);
- ((Switch) view.findViewById(R.id.op_switch)).setChecked(
+ ((CompoundButton) view.findViewById(R.id.op_switch)).setChecked(
item.getPrimaryOpMode() == AppOpsManager.MODE_ALLOWED);
return view;
@@ -318,7 +316,7 @@ public class AppOpsCategory extends ListFragment implements
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
- mAdapter = new AppListAdapter(getActivity(), mState);
+ mAdapter = new AppListAdapter(getActivity());
setListAdapter(mAdapter);
// Start out with a progress indicator.
@@ -332,7 +330,7 @@ public class AppOpsCategory extends ListFragment implements
AppOpEntry entry = mAdapter.getItem(position);
if (entry != null) {
// We treat this as tapping on the check box, toggling the app op state.
- Switch sw = v.findViewById(R.id.op_switch);
+ CompoundButton sw = v.findViewById(R.id.op_switch);
boolean checked = !sw.isChecked();
sw.setChecked(checked);
AppOpsManager.OpEntry op = entry.getOpEntry(0);
diff --git a/src/com/android/settings/applications/assist/ManageAssist.java b/src/com/android/settings/applications/assist/ManageAssist.java
index ad6c71e08372dcd9d1fab2edc7ccea19c54696ca..28c7d96eb665d18afa7ceed3fef1f885b10c855e 100644
--- a/src/com/android/settings/applications/assist/ManageAssist.java
+++ b/src/com/android/settings/applications/assist/ManageAssist.java
@@ -21,7 +21,6 @@ import android.content.Context;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.gestures.AssistGestureSettingsPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -59,12 +58,6 @@ public class ManageAssist extends DashboardFragment {
return SettingsEnums.APPLICATIONS_MANAGE_ASSIST;
}
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- use(AssistGestureSettingsPreferenceController.class).setAssistOnly(true);
- }
-
private static List buildPreferenceControllers(Context context,
Lifecycle lifecycle) {
final List controllers = new ArrayList<>();
diff --git a/src/com/android/settings/applications/autofill/AutofillPickerActivity.java b/src/com/android/settings/applications/autofill/AutofillPickerActivity.java
index 93b98805181f6e9d9e3afd7243814c90d39dec86..e6180da9668c678c06bd373c6f7738912590b265 100644
--- a/src/com/android/settings/applications/autofill/AutofillPickerActivity.java
+++ b/src/com/android/settings/applications/autofill/AutofillPickerActivity.java
@@ -18,10 +18,10 @@ import android.os.Bundle;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
-import com.android.settings.applications.defaultapps.DefaultAutofillPicker;
+import com.android.settings.applications.credentials.DefaultCombinedPicker;
/**
- * Standalone activity used to launch a {@link DefaultAutofillPicker} fragment from a
+ * Standalone activity used to launch a {@link DefaultCombinedPicker} fragment from a
* {@link android.provider.Settings#ACTION_REQUEST_SET_AUTOFILL_SERVICE} intent.
*/
public class AutofillPickerActivity extends SettingsActivity {
@@ -30,15 +30,15 @@ public class AutofillPickerActivity extends SettingsActivity {
protected void onCreate(Bundle savedInstanceState) {
final Intent intent = getIntent();
final String packageName = intent.getData().getSchemeSpecificPart();
- intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultAutofillPicker.class.getName());
- intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, R.string.autofill_app);
- intent.putExtra(DefaultAutofillPicker.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPicker.class.getName());
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, R.string.credman_picker_title);
+ intent.putExtra(DefaultCombinedPicker.EXTRA_PACKAGE_NAME, packageName);
super.onCreate(savedInstanceState);
}
@Override
protected boolean isValidFragment(String fragmentName) {
return super.isValidFragment(fragmentName)
- || DefaultAutofillPicker.class.getName().equals(fragmentName);
+ || DefaultCombinedPicker.class.getName().equals(fragmentName);
}
}
diff --git a/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java b/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java
index ee58bfea79988e7124aee2fb995a54874d9945da..fd17a93bcdb20ba3e0ebde373007d6c0339e93c5 100644
--- a/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java
+++ b/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java
@@ -18,11 +18,10 @@ import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.autofill.AutofillManager;
-
-import com.android.settings.applications.defaultapps.DefaultAutofillPicker;
+import com.android.settings.applications.credentials.DefaultCombinedPicker;
/**
- * Standalone activity used to launch a {@link DefaultAutofillPicker} fragment from a
+ * Standalone activity used to launch a {@link DefaultCombinedPicker} fragment from a
* {@link android.provider.Settings#ACTION_REQUEST_SET_AUTOFILL_SERVICE} intent.
*
* It first check for cases that can fail fast, then forward to {@link AutofillPickerActivity}
diff --git a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
index 03a551fb798bb0ba912306f12b083a805fdbbe19..73fef1bcedc0b7c1993d0471f46499b197806443 100644
--- a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
+++ b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
@@ -17,6 +17,7 @@
package com.android.settings.applications.autofill;
import static android.app.admin.DevicePolicyResources.Strings.Settings.AUTO_SYNC_PERSONAL_DATA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.AUTO_SYNC_PRIVATE_DATA;
import static android.app.admin.DevicePolicyResources.Strings.Settings.AUTO_SYNC_WORK_DATA;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.service.autofill.AutofillService.EXTRA_RESULT;
@@ -122,6 +123,8 @@ public class PasswordsPreferenceController extends BasePreferenceController
AUTO_SYNC_PERSONAL_DATA, R.string.account_settings_menu_auto_sync_personal);
replaceEnterpriseStringTitle(screen, "auto_sync_work_account_data",
AUTO_SYNC_WORK_DATA, R.string.account_settings_menu_auto_sync_work);
+ replaceEnterpriseStringTitle(screen, "auto_sync_private_account_data",
+ AUTO_SYNC_PRIVATE_DATA, R.string.account_settings_menu_auto_sync_private);
}
private void addPasswordPreferences(
diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
index 074fb7b31c2bfa23c46ff4cb98f7da017fecaccf..e85413896e50ee528f584a476f2aac65ab849cc0 100644
--- a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
+++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
@@ -82,6 +82,37 @@ public final class CombinedProviderInfo {
return mAutofillServiceInfo.getServiceInfo().applicationInfo;
}
+ /** Returns the package name. */
+ public @Nullable String getPackageName() {
+ ApplicationInfo ai = getApplicationInfo();
+ if (ai != null) {
+ return ai.packageName;
+ }
+
+ return null;
+ }
+
+ /** Returns the settings activity. */
+ public @Nullable String getSettingsActivity() {
+ // This logic is not used by the top entry but rather what activity should
+ // be launched from the settings screen.
+ for (CredentialProviderInfo cpi : mCredentialProviderInfos) {
+ final CharSequence settingsActivity = cpi.getSettingsActivity();
+ if (!TextUtils.isEmpty(settingsActivity)) {
+ return String.valueOf(settingsActivity);
+ }
+ }
+
+ if (mAutofillServiceInfo != null) {
+ final String settingsActivity = mAutofillServiceInfo.getSettingsActivity();
+ if (!TextUtils.isEmpty(settingsActivity)) {
+ return settingsActivity;
+ }
+ }
+
+ return null;
+ }
+
/** Returns the app icon. */
@Nullable
public Drawable getAppIcon(@NonNull Context context, int userId) {
diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
index 6747b36936d823708915c503f1113751598f6393..b0905bab077a90ae802e164b8c7f53cd9dd8fd55 100644
--- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
@@ -45,8 +45,9 @@ import android.os.UserManager;
import android.provider.Settings;
import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
-import android.util.IconDrawableFactory;
import android.util.Log;
+import android.view.View;
+import android.widget.CompoundButton;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
@@ -58,7 +59,7 @@ import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
+import androidx.preference.PreferenceViewHolder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
@@ -67,6 +68,7 @@ import com.android.settings.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.utils.ThreadUtils;
+import com.android.settingslib.widget.TwoTargetPreference;
import java.util.ArrayList;
import java.util.HashMap;
@@ -93,14 +95,16 @@ 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 IconDrawableFactory mIconFactory;
private final List mServices;
private final Set mEnabledPackageNames;
private final @Nullable CredentialManager mCredentialManager;
private final Executor mExecutor;
- private final Map mPrefs = new HashMap<>(); // key is package name
+ private final Map mPrefs = new HashMap<>(); // key is package name
private final List mPendingServiceInfos = new ArrayList<>();
private final Handler mHandler = new Handler();
private final SettingContentObserver mSettingsContentObserver;
@@ -116,7 +120,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
public CredentialManagerPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mPm = context.getPackageManager();
- mIconFactory = IconDrawableFactory.newInstance(mContext);
mServices = new ArrayList<>();
mEnabledPackageNames = new HashSet<>();
mExecutor = ContextCompat.getMainExecutor(mContext);
@@ -156,6 +159,16 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
return CONDITIONALLY_UNAVAILABLE;
}
+ // If we are in work profile mode and there is no user then we
+ // should hide for now. We use CONDITIONALLY_UNAVAILABLE
+ // because it is possible for the user to be set later.
+ if (mIsWorkProfile) {
+ UserHandle workProfile = getWorkProfileUserHandle();
+ if (workProfile == null) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ }
+
return AVAILABLE;
}
@@ -183,12 +196,17 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
fragment.getSettingsLifecycle().addObserver(this);
mFragmentManager = fragmentManager;
mIsWorkProfile = isWorkProfile;
+
setDelegate(delegate);
verifyReceivedIntent(launchIntent);
// Recreate the content observers because the user might have changed.
mSettingsContentObserver.unregister();
mSettingsContentObserver.register();
+
+ // When we set the mIsWorkProfile above we should try and force a refresh
+ // so we can get the correct data.
+ delegate.forceDelegateRefresh();
}
/**
@@ -299,8 +317,44 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
null);
}
+ private Set buildComponentNameSet(
+ List providers, boolean removeNonPrimary) {
+ Set output = new HashSet<>();
+
+ for (CredentialProviderInfo cpi : providers) {
+ if (removeNonPrimary && !cpi.isPrimary()) {
+ continue;
+ }
+
+ output.add(cpi.getComponentName());
+ }
+
+ return output;
+ }
+
private void updateFromExternal() {
- update();
+ if (mCredentialManager == null) {
+ return;
+ }
+
+ // Get the list of new providers and components.
+ List newProviders =
+ mCredentialManager.getCredentialProviderServices(
+ getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
+ Set newComponents = buildComponentNameSet(newProviders, false);
+ Set newPrimaryComponents = buildComponentNameSet(newProviders, true);
+
+ // Get the list of old components
+ Set oldComponents = buildComponentNameSet(mServices, false);
+ Set oldPrimaryComponents = buildComponentNameSet(mServices, true);
+
+ // If the sets are equal then don't update the UI.
+ if (oldComponents.equals(newComponents)
+ && oldPrimaryComponents.equals(newPrimaryComponents)) {
+ return;
+ }
+
+ setAvailableServices(newProviders, null);
if (mPreferenceScreen != null) {
displayPreference(mPreferenceScreen);
@@ -389,7 +443,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
/** Aggregates the list of services and builds a list of UI prefs to show. */
@VisibleForTesting
- public Map buildPreferenceList(
+ public Map buildPreferenceList(
Context context, PreferenceGroup group) {
// Get the selected autofill provider. If it is the placeholder then replace it with an
// empty string.
@@ -415,7 +469,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
return new HashMap<>();
}
- Map output = new HashMap<>();
+ Map output = new HashMap<>();
for (CombinedProviderInfo combinedInfo : providers) {
final String packageName = combinedInfo.getApplicationInfo().packageName;
@@ -430,13 +484,22 @@ 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);
// Build the pref and add it to the output & group.
- SwitchPreference pref =
+ CombiPreference pref =
addProviderPreference(
- context, title, icon, packageName, combinedInfo.getSettingsSubtitle());
+ context,
+ title == null ? "" : title,
+ icon,
+ packageName,
+ combinedInfo.getSettingsSubtitle(),
+ settingsActivity);
output.put(packageName, pref);
group.addPreference(pref);
}
@@ -449,14 +512,15 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
/** Creates a preference object based on the provider info. */
@VisibleForTesting
- public SwitchPreference createPreference(Context context, CredentialProviderInfo service) {
+ public CombiPreference createPreference(Context context, CredentialProviderInfo service) {
CharSequence label = service.getLabel(context);
return addProviderPreference(
context,
label == null ? "" : label,
service.getServiceIcon(mContext),
service.getServiceInfo().packageName,
- service.getSettingsSubtitle());
+ service.getSettingsSubtitle(),
+ service.getSettingsActivity());
}
/**
@@ -510,54 +574,96 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
return enabledServices;
}
- private SwitchPreference addProviderPreference(
+ private CombiPreference addProviderPreference(
@NonNull Context prefContext,
@NonNull CharSequence title,
@Nullable Drawable icon,
@NonNull String packageName,
- @Nullable CharSequence subtitle) {
- final SwitchPreference pref = new SwitchPreference(prefContext);
+ @Nullable CharSequence subtitle,
+ @Nullable CharSequence settingsActivity) {
+ final CombiPreference pref =
+ new CombiPreference(prefContext, mEnabledPackageNames.contains(packageName));
pref.setTitle(title);
- pref.setChecked(mEnabledPackageNames.contains(packageName));
if (icon != null) {
- pref.setIcon(Utils.getSafeIcon(icon));
+ pref.setIcon(icon);
}
+ pref.setLayoutResource(R.layout.preference_icon_credman);
+
if (subtitle != null) {
pref.setSummary(subtitle);
}
- pref.setOnPreferenceClickListener(
- p -> {
- boolean isChecked = pref.isChecked();
+ pref.setPreferenceListener(
+ new CombiPreference.OnCombiPreferenceClickListener() {
+ @Override
+ public void onCheckChanged(CombiPreference p, boolean isChecked) {
+ if (isChecked) {
+ if (mEnabledPackageNames.size() >= MAX_SELECTABLE_PROVIDERS) {
+ // Show the error if too many enabled.
+ pref.setChecked(false);
+ final DialogFragment fragment = newErrorDialogFragment();
+
+ if (fragment == null || mFragmentManager == null) {
+ return;
+ }
+
+ fragment.show(mFragmentManager, ErrorDialogFragment.TAG);
+ return;
+ }
- if (isChecked) {
- if (mEnabledPackageNames.size() >= MAX_SELECTABLE_PROVIDERS) {
- // Show the error if too many enabled.
- pref.setChecked(false);
- final DialogFragment fragment = newErrorDialogFragment();
+ togglePackageNameEnabled(packageName);
- if (fragment == null || mFragmentManager == null) {
- return true;
+ // Enable all prefs.
+ if (mPrefs.containsKey(packageName)) {
+ mPrefs.get(packageName).setChecked(true);
}
+ } else {
+ togglePackageNameDisabled(packageName);
+ }
+ }
- fragment.show(mFragmentManager, ErrorDialogFragment.TAG);
- return true;
+ @Override
+ public void onLeftSideClicked() {
+ if (settingsActivity == null) {
+ Log.w(TAG, "settingsActivity was null");
+ return;
}
- togglePackageNameEnabled(packageName);
+ 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;
+ }
- // Enable all prefs.
- if (mPrefs.containsKey(packageName)) {
- mPrefs.get(packageName).setChecked(true);
+ 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);
}
- return true;
- } else {
- togglePackageNameDisabled(packageName);
- }
- return true;
+ context.startActivity(intent);
+ }
});
return pref;
@@ -672,12 +778,22 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
protected int getUser() {
if (mIsWorkProfile) {
- UserHandle workProfile = Utils.getManagedProfile(UserManager.get(mContext));
- return workProfile.getIdentifier();
+ UserHandle workProfile = getWorkProfileUserHandle();
+ if (workProfile != null) {
+ return workProfile.getIdentifier();
+ }
}
return UserHandle.myUserId();
}
+ private @Nullable UserHandle getWorkProfileUserHandle() {
+ if (mIsWorkProfile) {
+ return Utils.getManagedProfile(UserManager.get(mContext));
+ }
+
+ return null;
+ }
+
/** Called when the dialog button is clicked. */
private static interface DialogHost {
void onDialogClick(int whichButton);
@@ -836,4 +952,95 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
updateFromExternal();
}
}
+
+ /** CombiPreference is a combination of TwoTargetPreference and SwitchPreference. */
+ public static class CombiPreference extends TwoTargetPreference {
+
+ private final Listener mListener = new Listener();
+
+ private class Listener implements View.OnClickListener {
+ @Override
+ public void onClick(View buttonView) {
+ // Forward the event.
+ if (mSwitch != null) {
+ mOnClickListener.onCheckChanged(CombiPreference.this, mSwitch.isChecked());
+ }
+ }
+ }
+
+ // Stores a reference to the switch view.
+ private @Nullable CompoundButton mSwitch;
+
+ // Switch text for on and off states
+ private @NonNull boolean mChecked = false;
+ private @Nullable OnCombiPreferenceClickListener mOnClickListener = null;
+
+ public interface OnCombiPreferenceClickListener {
+ /** Called when the check is updated */
+ void onCheckChanged(CombiPreference p, boolean isChecked);
+
+ /** Called when the left side is clicked. */
+ void onLeftSideClicked();
+ }
+
+ public CombiPreference(Context context, boolean initialValue) {
+ super(context);
+ mChecked = initialValue;
+ }
+
+ /** Set the new checked value */
+ public void setChecked(boolean isChecked) {
+ // Don't update if we don't need too.
+ if (mChecked == isChecked) {
+ return;
+ }
+
+ mChecked = isChecked;
+
+ if (mSwitch != null) {
+ mSwitch.setChecked(isChecked);
+ }
+ }
+
+ public boolean isChecked() {
+ return mChecked;
+ }
+
+ public void setPreferenceListener(OnCombiPreferenceClickListener onClickListener) {
+ mOnClickListener = onClickListener;
+ }
+
+ @Override
+ protected int getSecondTargetResId() {
+ return com.android.settingslib.R.layout.preference_widget_primary_switch;
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+
+ // Setup the switch.
+ View checkableView =
+ view.itemView.findViewById(com.android.settingslib.R.id.switchWidget);
+ if (checkableView instanceof CompoundButton switchView) {
+ switchView.setChecked(mChecked);
+ switchView.setOnClickListener(mListener);
+
+ // Store this for later.
+ mSwitch = switchView;
+ }
+
+ super.setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (mOnClickListener != null) {
+ mOnClickListener.onLeftSideClicked();
+ }
+
+ return true;
+ }
+ });
+ }
+ }
}
diff --git a/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..495c104d661b79b8ef613dc57a61b7a4408f658b
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.applications.credentials;
+
+
+import com.android.settings.SettingsActivity;
+
+/** Standalone activity used to launch a {@link DefaultCombinedPicker} fragment. */
+public class CredentialsPickerActivity extends SettingsActivity {
+
+ @Override
+ protected boolean isValidFragment(String fragmentName) {
+ return super.isValidFragment(fragmentName)
+ || DefaultCombinedPicker.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 dcf8fa8244e497b038c5d010a8168df73a7dc56c..a813ce4f5ddb46ee12e301f8e2a1dedc7cb0c5e8 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
@@ -28,6 +28,8 @@ import android.credentials.CredentialProviderInfo;
import android.credentials.SetEnabledProvidersException;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.UserHandle;
import android.provider.Settings;
@@ -43,7 +45,6 @@ import com.android.internal.content.PackageMonitor;
import com.android.settings.R;
import com.android.settings.applications.defaultapps.DefaultAppPickerFragment;
import com.android.settingslib.applications.DefaultAppInfo;
-import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.CandidateInfo;
import java.util.ArrayList;
@@ -65,6 +66,8 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment {
private CredentialManager mCredentialManager;
private int mIntentSenderUserId = -1;
+ private static final Handler sMainHandler = new Handler(Looper.getMainLooper());
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -132,17 +135,44 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment {
new PackageMonitor() {
@Override
public void onPackageAdded(String packageName, int uid) {
- ThreadUtils.postOnMainThread(() -> update());
+ sMainHandler.post(
+ () -> {
+ // See b/296164461 for context
+ if (getContext() == null) {
+ Log.w(TAG, "context is null");
+ return;
+ }
+
+ update();
+ });
}
@Override
public void onPackageModified(String packageName) {
- ThreadUtils.postOnMainThread(() -> update());
+ sMainHandler.post(
+ () -> {
+ // See b/296164461 for context
+ if (getContext() == null) {
+ Log.w(TAG, "context is null");
+ return;
+ }
+
+ update();
+ });
}
@Override
public void onPackageRemoved(String packageName, int uid) {
- ThreadUtils.postOnMainThread(() -> update());
+ sMainHandler.post(
+ () -> {
+ // See b/296164461 for context
+ if (getContext() == null) {
+ Log.w(TAG, "context is null");
+ return;
+ }
+
+ update();
+ });
}
};
@@ -275,10 +305,7 @@ 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(R.string.credman_confirmation_message);
return Html.fromHtml(message);
}
final CharSequence appName = appInfo.loadLabel();
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java b/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java
new file mode 100644
index 0000000000000000000000000000000000000000..722cb1a13434600bbe80a588a3eb5f6ee0fe9758
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java
@@ -0,0 +1,30 @@
+/*
+ * 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.applications.credentials;
+
+import android.os.UserManager;
+
+import com.android.settings.Utils;
+import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType;
+
+public class DefaultCombinedPickerPrivate extends DefaultCombinedPicker {
+ @Override
+ protected int getUser() {
+ UserManager userManager = getContext().getSystemService(UserManager.class);
+ return Utils.getCurrentUserIdOfType(userManager, ProfileType.PRIVATE);
+ }
+}
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
index 59c33b2c8eadbb3b511e5b5b57e70f9ca6265c83..47a89ec649787ffe68106425b58329e51db523d9 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
@@ -16,11 +16,10 @@
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.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
@@ -29,16 +28,19 @@ import android.provider.Settings;
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
-import android.util.Log;
import android.view.autofill.AutofillManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settingslib.applications.DefaultAppInfo;
import java.util.ArrayList;
import java.util.List;
-public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController {
+public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController
+ implements Preference.OnPreferenceClickListener {
private static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE);
private static final String TAG = "DefaultCombinedPreferenceController";
@@ -73,18 +75,55 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
@Override
protected Intent getSettingIntent(DefaultAppInfo info) {
- if (info == null) {
- return null;
+ // 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);
+ }
+
+ @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);
}
- final AutofillSettingIntentProvider intentProvider =
- new AutofillSettingIntentProvider(mContext, getUser(), info.getKey());
- return intentProvider.getIntent();
}
@Override
- protected DefaultAppInfo getDefaultAppInfo() {
+ public boolean onPreferenceClick(Preference preference) {
+ // Get the selected provider.
+ final CombinedProviderInfo topProvider = getTopProvider();
+ if (topProvider == null) {
+ return false;
+ }
+
+ // 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;
+ }
+
+ return false;
+ }
+
+ private @Nullable CombinedProviderInfo getTopProvider() {
List providers = getAllProviders(getUser());
- CombinedProviderInfo topProvider = CombinedProviderInfo.getTopProvider(providers);
+ return CombinedProviderInfo.getTopProvider(providers);
+ }
+
+ @Override
+ protected DefaultAppInfo getDefaultAppInfo() {
+ CombinedProviderInfo topProvider = getTopProvider();
if (topProvider != null) {
ServiceInfo brandingService = topProvider.getBrandingService();
if (brandingService == null) {
@@ -138,53 +177,6 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
return true;
}
- /** Provides Intent to setting activity for the specified autofill service. */
- static final class AutofillSettingIntentProvider {
-
- private final String mKey;
- private final Context mContext;
- private final int mUserId;
-
- public AutofillSettingIntentProvider(Context context, int userId, String key) {
- mKey = key;
- mContext = context;
- mUserId = userId;
- }
-
- public Intent getIntent() {
- final List resolveInfos =
- mContext.getPackageManager()
- .queryIntentServicesAsUser(
- AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId);
-
- for (ResolveInfo resolveInfo : resolveInfos) {
- final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-
- // If there are multiple autofill services then pick the first one.
- if (mKey != null && mKey.startsWith(serviceInfo.packageName)) {
- final String settingsActivity;
- try {
- settingsActivity =
- new AutofillServiceInfo(mContext, serviceInfo)
- .getSettingsActivity();
- } catch (SecurityException e) {
- // Service does not declare the proper permission, ignore it.
- Log.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
- return null;
- }
- if (TextUtils.isEmpty(settingsActivity)) {
- return null;
- }
- return new Intent(Intent.ACTION_MAIN)
- .setComponent(
- new ComponentName(serviceInfo.packageName, settingsActivity));
- }
- }
-
- return null;
- }
- }
-
protected int getUser() {
return UserHandle.myUserId();
}
diff --git a/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt b/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt
new file mode 100644
index 0000000000000000000000000000000000000000..990e221db42ae84fc635dcdd9e7afa1259bbe00e
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.applications.credentials
+
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import com.android.settings.Utils
+import com.android.settings.dashboard.profileselector.ProfileSelectFragment
+import com.android.settingslib.applications.DefaultAppInfo
+
+class DefaultPrivateCombinedPreferenceController(context: Context?) : DefaultCombinedPreferenceController(context) {
+ private val userHandle: UserHandle? =
+ Utils.getProfileOfType(mUserManager, ProfileSelectFragment.ProfileType.PRIVATE)
+
+ override fun isAvailable(): Boolean {
+ return if (userHandle == null) {
+ false
+ } else super.isAvailable()
+ }
+
+ override fun getPreferenceKey(): String {
+ return "default_credman_autofill_private"
+ }
+
+ override fun startActivity(intent: Intent) {
+ userHandle?.let { handle ->
+ mContext.startActivityAsUser(intent, handle)
+ }
+ }
+
+ override fun getUser(): Int {
+ return userHandle?.identifier ?: UserHandle.myUserId()
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
index eb0aa7abfd32bedd9332c858d84d2c5662c56a39..f7ca20455e1e32434b14ae72ea5526c2181e556b 100644
--- a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
@@ -21,7 +21,6 @@ import android.content.Intent;
import android.os.UserHandle;
import com.android.settings.Utils;
-import com.android.settingslib.applications.DefaultAppInfo;
public class DefaultWorkCombinedPreferenceController extends DefaultCombinedPreferenceController {
private final UserHandle mUserHandle;
@@ -44,17 +43,6 @@ public class DefaultWorkCombinedPreferenceController extends DefaultCombinedPref
return "default_credman_autofill_main_work";
}
- @Override
- protected Intent getSettingIntent(DefaultAppInfo info) {
- if (info == null) {
- return null;
- }
- final AutofillSettingIntentProvider intentProvider =
- new AutofillSettingIntentProvider(
- mContext, mUserHandle.getIdentifier(), info.getKey());
- return intentProvider.getIntent();
- }
-
@Override
protected void startActivity(Intent intent) {
mContext.startActivityAsUser(intent, mUserHandle);
diff --git a/src/com/android/settings/applications/defaultapps/DefaultPrivateAutofillPreferenceController.kt b/src/com/android/settings/applications/defaultapps/DefaultPrivateAutofillPreferenceController.kt
new file mode 100644
index 0000000000000000000000000000000000000000..67211b4a369cb926348852bf46053eac74b17d66
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultPrivateAutofillPreferenceController.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.applications.defaultapps
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import android.provider.Settings
+import android.text.TextUtils
+import com.android.settings.Utils
+import com.android.settings.dashboard.profileselector.ProfileSelectFragment
+import com.android.settingslib.applications.DefaultAppInfo
+
+class DefaultPrivateAutofillPreferenceController(context: Context?) : DefaultAutofillPreferenceController(context) {
+ private val userHandle: UserHandle? = Utils
+ .getProfileOfType(mUserManager, ProfileSelectFragment.ProfileType.PRIVATE)
+
+ override fun isAvailable(): Boolean {
+ return if (userHandle == null) {
+ false
+ } else super.isAvailable()
+ }
+
+ override fun getPreferenceKey(): String {
+ return "default_autofill_private"
+ }
+
+ override fun getDefaultAppInfo(): DefaultAppInfo ? {
+ val flattenComponent = userHandle?.let { handle ->
+ Settings.Secure.getStringForUser(
+ mContext.contentResolver,
+ DefaultAutofillPicker.SETTING,
+ handle.identifier
+ )
+ }
+ return if (!flattenComponent.isNullOrEmpty()) {
+ userHandle?.let {
+ DefaultAppInfo(
+ mContext,
+ mPackageManager,
+ it.identifier,
+ ComponentName.unflattenFromString(flattenComponent))
+ }
+ } else null
+ }
+
+ override fun startActivity(intent: Intent) {
+ if (userHandle == null) {
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT)
+ } else mContext.startActivityAsUser(intent, userHandle)
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
index cc662aad2e9523af5bd9539e847a038b33277595..72f754379aa16d59d312f445a1133f0181cf96f0 100644
--- a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
+++ b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
@@ -35,7 +35,8 @@ import android.util.ArraySet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.Switch;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
@@ -51,7 +52,6 @@ import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.MainSwitchPreference;
-import com.android.settingslib.widget.OnMainSwitchChangeListener;
import java.util.HashMap;
import java.util.List;
@@ -62,7 +62,7 @@ import java.util.UUID;
/** The page of the Open by default */
public class AppLaunchSettings extends AppInfoBase implements
- Preference.OnPreferenceChangeListener, OnMainSwitchChangeListener {
+ Preference.OnPreferenceChangeListener, OnCheckedChangeListener {
private static final String TAG = "AppLaunchSettings";
// Preference keys
private static final String MAIN_SWITCH_PREF_KEY = "open_by_default_supported_links";
@@ -168,7 +168,7 @@ public class AppLaunchSettings extends AppInfoBase implements
}
@Override
- public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
IntentPickerUtils.logd("onSwitchChanged: isChecked=" + isChecked);
if (mMainSwitchPreference != null) { //mMainSwitchPreference synced with Switch
mMainSwitchPreference.setChecked(isChecked);
@@ -200,7 +200,6 @@ public class AppLaunchSettings extends AppInfoBase implements
final String summary = activity.getString(R.string.app_launch_top_intro_message);
final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* header */)
- .setRecyclerView(getListView(), getSettingsLifecycle())
.setIcon(Utils.getBadgedIcon(mContext, mPackageInfo.applicationInfo))
.setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
.setSummary(summary) // add intro text
@@ -210,7 +209,7 @@ public class AppLaunchSettings extends AppInfoBase implements
.setHasAppInfoLink(true)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE)
- .done(activity, getPrefContext());
+ .done(getPrefContext());
getPreferenceScreen().addPreference(pref);
}
diff --git a/src/com/android/settings/applications/intentpicker/LeftSideCheckBoxPreference.java b/src/com/android/settings/applications/intentpicker/LeftSideCheckBoxPreference.java
index fdb6d2541e0400c1462f0df4132fa59304aed4b5..199dc4ecbcc5cbaa870bf658655283d5b3f17349 100644
--- a/src/com/android/settings/applications/intentpicker/LeftSideCheckBoxPreference.java
+++ b/src/com/android/settings/applications/intentpicker/LeftSideCheckBoxPreference.java
@@ -22,7 +22,6 @@ import android.widget.CheckBox;
import androidx.preference.PreferenceViewHolder;
-import com.android.settings.R;
import com.android.settingslib.widget.TwoTargetPreference;
/** This preference has a check box in the left side. */
@@ -33,7 +32,7 @@ public class LeftSideCheckBoxPreference extends TwoTargetPreference {
public LeftSideCheckBoxPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- setLayoutResource(R.layout.preference_checkable_two_target);
+ setLayoutResource(com.android.settingslib.R.layout.preference_checkable_two_target);
}
public LeftSideCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -51,7 +50,7 @@ public class LeftSideCheckBoxPreference extends TwoTargetPreference {
public LeftSideCheckBoxPreference(Context context, boolean isChecked) {
super(context);
mChecked = isChecked;
- setLayoutResource(R.layout.preference_checkable_two_target);
+ setLayoutResource(com.android.settingslib.R.layout.preference_checkable_two_target);
}
@Override
diff --git a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
index e7bb88dadf146e7ac84ba89863c09a38206d1f64..fd998f57d36ec4bcc69066362db02df0ddb858a2 100644
--- a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
+++ b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
@@ -167,13 +167,13 @@ public class AppFilterRegistry {
mFilters[FILTER_APPS_PERSONAL] = new AppFilterItem(
ApplicationsState.FILTER_PERSONAL,
FILTER_APPS_PERSONAL,
- R.string.category_personal);
+ com.android.settingslib.R.string.category_personal);
// Work
mFilters[FILTER_APPS_WORK] = new AppFilterItem(
ApplicationsState.FILTER_WORK,
FILTER_APPS_WORK,
- R.string.category_work);
+ com.android.settingslib.R.string.category_work);
// Usage access screen, never displayed.
mFilters[FILTER_APPS_USAGE_ACCESS] = new AppFilterItem(
@@ -219,7 +219,7 @@ public class AppFilterRegistry {
mFilters[FILTER_ALARMS_AND_REMINDERS] = new AppFilterItem(
AppStateAlarmsAndRemindersBridge.FILTER_CLOCK_APPS,
FILTER_ALARMS_AND_REMINDERS,
- R.string.alarms_and_reminders_title);
+ com.android.settingslib.R.string.alarms_and_reminders_title);
// Apps that can manage media files
mFilters[FILTER_APPS_MEDIA_MANAGEMENT] = new AppFilterItem(
@@ -279,7 +279,7 @@ public class AppFilterRegistry {
mFilters[FILTER_APPS_TURN_SCREEN_ON] = new AppFilterItem(
AppStateTurnScreenOnBridge.FILTER_TURN_SCREEN_ON_APPS,
FILTER_APPS_TURN_SCREEN_ON,
- R.string.turn_screen_on_title);
+ com.android.settingslib.R.string.turn_screen_on_title);
}
public static AppFilterRegistry getInstance() {
diff --git a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
index 58232ea4f8592a0b0109da6cd4478ba13d459758..1d96688c1684f56037e7f962ae6ece5d9799e76f 100644
--- a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
+++ b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
@@ -31,9 +31,9 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
-import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.StringRes;
@@ -46,11 +46,16 @@ import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.spaprivileged.template.app.AppListItemModelKt;
+import com.android.settingslib.spaprivileged.template.app.AppListPageKt;
import com.android.settingslib.widget.LottieColorUtils;
import com.airbnb.lottie.LottieAnimationView;
-
+/**
+ * @deprecated Will be removed, use {@link AppListItemModelKt} {@link AppListPageKt} instead.
+ */
+@Deprecated(forRemoval = true)
public class ApplicationViewHolder extends RecyclerView.ViewHolder {
@VisibleForTesting
@@ -62,7 +67,7 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
@VisibleForTesting
final ViewGroup mWidgetContainer;
@VisibleForTesting
- final Switch mSwitch;
+ final CompoundButton mSwitch;
final ImageView mAddIcon;
final ProgressBar mProgressBar;
@@ -73,8 +78,8 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
mAppName = itemView.findViewById(android.R.id.title);
mAppIcon = itemView.findViewById(android.R.id.icon);
mSummary = itemView.findViewById(android.R.id.summary);
- mDisabled = itemView.findViewById(R.id.appendix);
- mSwitch = itemView.findViewById(R.id.switchWidget);
+ mDisabled = itemView.findViewById(com.android.settingslib.widget.preference.app.R.id.appendix);
+ mSwitch = itemView.findViewById(com.android.settingslib.R.id.switchWidget);
mWidgetContainer = itemView.findViewById(android.R.id.widget_frame);
mAddIcon = itemView.findViewById(R.id.add_preference_widget);
mProgressBar = itemView.findViewById(R.id.progressBar_cyclic);
@@ -86,7 +91,7 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
static View newView(ViewGroup parent, boolean twoTarget, int listType) {
ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext())
- .inflate(R.layout.preference_app, parent, false);
+ .inflate(com.android.settingslib.widget.preference.app.R.layout.preference_app, parent, false);
ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame);
if (twoTarget) {
if (widgetFrame != null) {
@@ -94,11 +99,13 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
LayoutInflater.from(parent.getContext())
.inflate(R.layout.preference_widget_add_progressbar, widgetFrame, true);
} else {
- LayoutInflater.from(parent.getContext())
- .inflate(R.layout.preference_widget_primary_switch, widgetFrame, true);
+ LayoutInflater.from(parent.getContext()).inflate(
+ com.android.settingslib.R.layout.preference_widget_primary_switch,
+ widgetFrame, true);
}
View divider = LayoutInflater.from(parent.getContext()).inflate(
- R.layout.preference_two_target_divider, view, false);
+ com.android.settingslib.widget.preference.twotarget.R.layout.preference_two_target_divider,
+ view, false);
// second to last, before widget frame
view.addView(divider, view.getChildCount() - 1);
}
@@ -110,7 +117,8 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
static View newHeader(ViewGroup parent, int resText) {
ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext())
- .inflate(R.layout.preference_app_header, parent, false);
+ .inflate(com.android.settingslib.widget.preference.app.R.layout.preference_app_header,
+ parent, false);
TextView textView = view.findViewById(R.id.apps_top_intro_text);
textView.setText(resText);
return view;
@@ -165,10 +173,6 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
mAppName.setContentDescription(contentDescription);
}
- void setIcon(int drawableRes) {
- mAppIcon.setImageResource(drawableRes);
- }
-
void setIcon(Drawable icon) {
if (icon == null) {
return;
@@ -183,7 +187,7 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
} else if (!info.enabled || info.enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
mDisabled.setVisibility(View.VISIBLE);
- mDisabled.setText(R.string.disabled);
+ mDisabled.setText(com.android.settingslib.R.string.disabled);
} else {
mDisabled.setVisibility(View.GONE);
}
@@ -211,7 +215,8 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
}
}
- void updateSwitch(Switch.OnCheckedChangeListener listener, boolean enabled, boolean checked) {
+ void updateSwitch(CompoundButton.OnCheckedChangeListener listener, boolean enabled,
+ boolean checked) {
if (mSwitch != null && mWidgetContainer != null) {
mWidgetContainer.setFocusable(false);
mWidgetContainer.setClickable(false);
@@ -244,7 +249,7 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
public void onClick(View v) {
CloneBackend cloneBackend = CloneBackend.getInstance(context);
final MetricsFeatureProvider metricsFeatureProvider =
- FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
String packageName = entry.info.packageName;
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index d734a27f0335ee480a99d33f4d0e52c2945e73bf..e370f3eef64f900009ba2c1f3c3ba9fdd508663a 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -689,7 +689,8 @@ public class ManageApplications extends InstrumentedFragment
startAppInfoFragment(WriteSettingsDetails.class, R.string.write_system_settings);
break;
case LIST_TYPE_MANAGE_SOURCES:
- startAppInfoFragment(ExternalSourcesDetails.class, R.string.install_other_apps);
+ startAppInfoFragment(ExternalSourcesDetails.class,
+ com.android.settingslib.R.string.install_other_apps);
break;
case LIST_TYPE_GAMES:
startAppInfoFragment(AppStorageSettings.class, R.string.game_storage_settings);
@@ -704,7 +705,7 @@ public class ManageApplications extends InstrumentedFragment
break;
case LIST_TYPE_ALARMS_AND_REMINDERS:
startAppInfoFragment(AlarmsAndRemindersDetails.class,
- R.string.alarms_and_reminders_label);
+ com.android.settingslib.R.string.alarms_and_reminders_label);
break;
case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
startAppInfoFragment(MediaManagementAppsDetails.class,
@@ -741,7 +742,8 @@ public class ManageApplications extends InstrumentedFragment
R.string.change_nfc_tag_apps_title);
break;
case LIST_TYPE_TURN_SCREEN_ON:
- startAppInfoFragment(TurnScreenOnDetails.class, R.string.turn_screen_on_title);
+ startAppInfoFragment(TurnScreenOnDetails.class,
+ com.android.settingslib.R.string.turn_screen_on_title);
break;
// TODO: Figure out if there is a way where we can spin up the profile's settings
// process ahead of time, to avoid a long load of data when user clicks on a managed
@@ -1052,7 +1054,7 @@ public class ManageApplications extends InstrumentedFragment
} else if (className.equals(WriteSettingsActivity.class.getName())) {
screenTitle = R.string.write_settings;
} else if (className.equals(ManageExternalSourcesActivity.class.getName())) {
- screenTitle = R.string.install_other_apps;
+ screenTitle = com.android.settingslib.R.string.install_other_apps;
} else if (className.equals(ChangeWifiStateActivity.class.getName())) {
screenTitle = R.string.change_wifi_state_title;
} else if (className.equals(ManageExternalStorageActivity.class.getName())) {
@@ -1060,7 +1062,7 @@ public class ManageApplications extends InstrumentedFragment
} else if (className.equals(MediaManagementAppsActivity.class.getName())) {
screenTitle = R.string.media_management_apps_title;
} else if (className.equals(AlarmsAndRemindersActivity.class.getName())) {
- screenTitle = R.string.alarms_and_reminders_title;
+ screenTitle = com.android.settingslib.R.string.alarms_and_reminders_title;
} else if (className.equals(NotificationAppListActivity.class.getName())
|| className.equals(
NotificationReviewPermissionsActivity.class.getName())) {
@@ -1076,7 +1078,7 @@ public class ManageApplications extends InstrumentedFragment
} else if (className.equals(ChangeNfcTagAppsActivity.class.getName())) {
screenTitle = R.string.change_nfc_tag_apps_title;
} else if (className.equals(TurnScreenOnSettingsActivity.class.getName())) {
- screenTitle = R.string.turn_screen_on_title;
+ screenTitle = com.android.settingslib.R.string.turn_screen_on_title;
} else {
if (screenTitle == -1) {
screenTitle = R.string.all_apps;
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
index 8313686f29f73a8e8276cbe0d56057acc10c5798..82e987e3f6fadc28a341bbf82f701a0a22e5a647 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
+++ b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
@@ -64,10 +64,13 @@ import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvi
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
+import com.android.settings.spa.app.specialaccess.LongBackgroundTasksAppListProvider
import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider
import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
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
@@ -119,6 +122,11 @@ object ManageApplicationsUtil {
LIST_TYPE_MAIN -> AllAppListPageProvider.name
LIST_TYPE_NFC_TAG_APPS -> NfcTagAppsSettingsProvider.getAppListRoute()
LIST_TYPE_USER_ASPECT_RATIO_APPS -> UserAspectRatioAppsPageProvider.name
+ LIST_TYPE_LONG_BACKGROUND_TASKS -> LongBackgroundTasksAppListProvider.getAppListRoute()
+ LIST_TYPE_TURN_SCREEN_ON -> TurnScreenOnAppsAppListProvider.getAppListRoute()
+ // TODO(b/292165031) enable once sorting is supported
+ //LIST_TYPE_STORAGE -> StorageAppListPageProvider.Apps.name
+ //LIST_TYPE_GAMES -> StorageAppListPageProvider.Games.name
else -> null
}
}
diff --git a/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java b/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java
index 6da3e529065834a2395d075dda0ea98177eb3c45..b2b7512d560eadfa6edcc47133b9c447ac3e01da 100644
--- a/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java
+++ b/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java
@@ -39,6 +39,7 @@ import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
import com.android.settings.fuelgauge.BatteryOptimizeUtils;
+import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import java.util.Arrays;
import java.util.List;
@@ -155,6 +156,8 @@ public class ResetAppsHelper implements DialogInterface.OnClickListener,
}
mAom.resetAllModes();
BatteryOptimizeUtils.resetAppOptimizationMode(mContext, mIPm, mAom);
+ DynamicDenylistManager.getInstance(mContext)
+ .resetDenylistIfNeeded(/* packageName= */ null, /* force= */ true);
final int[] restrictedUids = mNpm.getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND);
final int currentUserId = ActivityManager.getCurrentUser();
for (int uid : restrictedUids) {
diff --git a/src/com/android/settings/applications/specialaccess/MediaRoutingControlPreferenceController.java b/src/com/android/settings/applications/specialaccess/MediaRoutingControlPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..72011bad33948f8c4adc592029c42101d5b50238
--- /dev/null
+++ b/src/com/android/settings/applications/specialaccess/MediaRoutingControlPreferenceController.java
@@ -0,0 +1,53 @@
+/*
+ * 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.applications.specialaccess;
+
+import android.Manifest;
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+
+import com.android.media.flags.Flags;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.spa.SpaActivity;
+import com.android.settings.spa.app.specialaccess.MediaRoutingControlAppListProvider;
+
+/**
+ * This controller manages features availability for special app access for
+ * {@link Manifest.permission#MEDIA_ROUTING_CONTROL} permission.
+ */
+public class MediaRoutingControlPreferenceController extends BasePreferenceController {
+ public MediaRoutingControlPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return Flags.enablePrivilegedRoutingForMediaRoutingControl()
+ ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (TextUtils.equals(preference.getKey(), mPreferenceKey)) {
+ SpaActivity.startSpaActivity(
+ mContext, MediaRoutingControlAppListProvider.INSTANCE.getAppListRoute());
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/settings/applications/specialaccess/applications/LongBackgroundTaskController.java b/src/com/android/settings/applications/specialaccess/applications/LongBackgroundTaskController.java
index ccfa9c88d35a32de826d16f3663ff45bd1a7243e..93bf1ad4cece925a0c8f2182319f2dd09039edc1 100644
--- a/src/com/android/settings/applications/specialaccess/applications/LongBackgroundTaskController.java
+++ b/src/com/android/settings/applications/specialaccess/applications/LongBackgroundTaskController.java
@@ -30,8 +30,8 @@ public class LongBackgroundTaskController extends BasePreferenceController {
public LongBackgroundTaskController(Context context, String preferenceKey) {
super(context, preferenceKey);
- mAppFeatureProvider = FeatureFactory.getFactory(context)
- .getApplicationFeatureProvider(context);
+ mAppFeatureProvider = FeatureFactory.getFeatureFactory()
+ .getApplicationFeatureProvider();
}
@Override
diff --git a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java
index c43bde64a35486da0bece85cab96bca8523f0bac..bb9876bd212e154d8cf4879f7e7685c86e347f2c 100644
--- a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java
+++ b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java
@@ -573,7 +573,7 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity {
void logSpecialPermissionChange(boolean allow, String packageName) {
int logCategory = allow ? SettingsEnums.APP_SPECIAL_PERMISSION_ADMIN_ALLOW :
SettingsEnums.APP_SPECIAL_PERMISSION_ADMIN_DENY;
- FeatureFactory.getFactory(this).getMetricsFeatureProvider().action(
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(
SettingsEnums.PAGE_UNKNOWN,
logCategory,
SettingsEnums.PAGE_UNKNOWN,
@@ -647,7 +647,8 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity {
}
void updateInterface() {
- findViewById(R.id.restricted_icon).setVisibility(View.GONE);
+ findViewById(com.android.settingslib.widget.restricted.R.id.restricted_icon)
+ .setVisibility(View.GONE);
mAdminIcon.setImageDrawable(mDeviceAdmin.loadIcon(getPackageManager()));
mAdminName.setText(mDeviceAdmin.loadLabel(getPackageManager()));
try {
@@ -682,7 +683,8 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity {
final boolean hasBaseRestriction = hasBaseCantRemoveProfileRestriction();
if ((hasBaseRestriction && mDPM.isOrganizationOwnedDeviceWithManagedProfile())
|| (admin != null && !hasBaseRestriction)) {
- findViewById(R.id.restricted_icon).setVisibility(View.VISIBLE);
+ findViewById(com.android.settingslib.widget.restricted.R.id.restricted_icon)
+ .setVisibility(View.VISIBLE);
}
mActionButton.setEnabled(admin == null && !hasBaseRestriction);
} else if (isProfileOwner || mDeviceAdmin.getComponent().equals(
diff --git a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
index 94033165c1d3d96249d7398eb64987527a5c78df..1184d8e41c55359986037e2180464ad3af75c11d 100644
--- a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
@@ -104,7 +104,7 @@ public class DeviceAdminListPreferenceController extends BasePreferenceControlle
mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
mPackageManager = mContext.getPackageManager();
mIPackageManager = AppGlobals.getPackageManager();
- mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
@Override
diff --git a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetails.java b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetails.java
index 4149e23c88be0caef724c98b84c7d53c61dbf57c..faa1b5109afd13c40e5b8b066682f7ed6d701cf5 100644
--- a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetails.java
+++ b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetails.java
@@ -208,7 +208,8 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
title.setText(appLabel);
}
- final ImageView personalIconView = mHeader.findViewById(R.id.entity_header_icon_personal);
+ final ImageView personalIconView = mHeader.findViewById(
+ com.android.settingslib.widget.preference.layout.R.id.entity_header_icon_personal);
if (personalIconView != null) {
Drawable icon = IconDrawableFactory.newInstance(mContext)
.getBadgedIcon(mPackageInfo.applicationInfo, personalProfile.getIdentifier())
@@ -219,7 +220,8 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
personalIconView.setImageDrawable(icon);
}
- final ImageView workIconView = mHeader.findViewById(R.id.entity_header_icon_work);
+ final ImageView workIconView = mHeader.findViewById(
+ com.android.settingslib.widget.preference.layout.R.id.entity_header_icon_work);
if (workIconView != null) {
Drawable icon = IconDrawableFactory.newInstance(mContext)
.getBadgedIcon(mPackageInfo.applicationInfo, workProfile.getIdentifier())
@@ -499,20 +501,24 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
private void enableSwitchPref() {
mSwitchPref.setChecked(true);
mSwitchPref.setTitle(R.string.interact_across_profiles_switch_enabled);
- final ImageView horizontalArrowIcon = mHeader.findViewById(R.id.entity_header_swap_horiz);
+ final ImageView horizontalArrowIcon =
+ mHeader.findViewById(com.android.settingslib.widget.preference.layout.R.id.entity_header_swap_horiz);
if (horizontalArrowIcon != null) {
horizontalArrowIcon.setImageDrawable(
- mContext.getDrawable(R.drawable.ic_swap_horiz_blue));
+ mContext.getDrawable(
+ com.android.settingslib.widget.preference.layout.R.drawable.ic_swap_horiz_blue));
}
}
private void disableSwitchPref() {
mSwitchPref.setChecked(false);
mSwitchPref.setTitle(R.string.interact_across_profiles_switch_disabled);
- final ImageView horizontalArrowIcon = mHeader.findViewById(R.id.entity_header_swap_horiz);
+ final ImageView horizontalArrowIcon =
+ mHeader.findViewById(com.android.settingslib.widget.preference.layout.R.id.entity_header_swap_horiz);
if (horizontalArrowIcon != null) {
horizontalArrowIcon.setImageDrawable(
- mContext.getDrawable(R.drawable.ic_swap_horiz_grey));
+ mContext.getDrawable(
+ com.android.settingslib.widget.preference.layout.R.drawable.ic_swap_horiz_grey));
}
}
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
index 6bee62cc688eda995f8070311be86a9c857945a5..fb78e3eb077542e276350669260bb36bd7a9865f 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
@@ -41,6 +41,8 @@ public class ApprovalPreferenceController extends BasePreferenceController {
private PreferenceFragmentCompat mParent;
private NotificationManager mNm;
private PackageManager mPm;
+ // The appOp representing this preference
+ private String mAppOpStr;
public ApprovalPreferenceController(Context context, String key) {
super(context, key);
@@ -71,6 +73,14 @@ public class ApprovalPreferenceController extends BasePreferenceController {
return this;
}
+ /**
+ * Set the associated appOp for the Setting
+ */
+ public ApprovalPreferenceController setAppOpStr(String appOpStr) {
+ mAppOpStr = appOpStr;
+ return this;
+ }
+
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
@@ -107,8 +117,20 @@ public class ApprovalPreferenceController extends BasePreferenceController {
return false;
}
});
- preference.updateState(
- mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isAllowedCn, isEnabled);
+
+ if (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);
+ }
+ } else {
+ preference.updateState(
+ mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isAllowedCn, isEnabled);
+ }
}
public void disable(final ComponentName cn) {
@@ -135,7 +157,7 @@ public class ApprovalPreferenceController extends BasePreferenceController {
void logSpecialPermissionChange(boolean enable, String packageName) {
final int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY;
- FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext,
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(mContext,
logCategory, packageName);
}
}
\ No newline at end of file
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/HeaderPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/HeaderPreferenceController.java
index 8ccf7ed7eefe20e98b49bb9d8b70df3bae04f304..b63626467ea8745bdb471a1f9d9a6575a90bcade 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/HeaderPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/HeaderPreferenceController.java
@@ -109,7 +109,6 @@ public class HeaderPreferenceController extends BasePreferenceController
mHeaderController = EntityHeaderController.newInstance(
mFragment.getActivity(), mFragment, pref.findViewById(R.id.entity_header));
pref = mHeaderController
- .setRecyclerView(mFragment.getListView(), mFragment.getSettingsLifecycle())
.setIcon(IconDrawableFactory.newInstance(mFragment.getActivity())
.getBadgedIcon(mPackageInfo.applicationInfo))
.setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
@@ -122,7 +121,7 @@ public class HeaderPreferenceController extends BasePreferenceController
.setHasAppInfoLink(true)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE)
- .done(mFragment.getActivity(), mContext);
+ .done(mContext);
pref.findViewById(R.id.entity_header).setVisibility(View.VISIBLE);
}
}
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
index 17dabe4b7a41347f181ba4aba915185c940b5e52..89767ddb011cba74292706cd90d76dfdb1d9e2ad 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
@@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME;
import android.Manifest;
+import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.companion.ICompanionDeviceManager;
@@ -102,6 +103,7 @@ public class NotificationAccessDetails extends DashboardFragment {
.setCn(mComponentName)
.setNm(context.getSystemService(NotificationManager.class))
.setPm(mPm)
+ .setAppOpStr(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS)
.setParent(this);
use(HeaderPreferenceController.class)
.setFragment(this)
diff --git a/src/com/android/settings/applications/specialaccess/pictureinpicture/PictureInPictureDetails.java b/src/com/android/settings/applications/specialaccess/pictureinpicture/PictureInPictureDetails.java
index 1c322ff6c49eb901b6e4014b22ebd6cffd9caced..d166b82f435ca8bfdd7f2e9b1c0809fc87b23cf5 100644
--- a/src/com/android/settings/applications/specialaccess/pictureinpicture/PictureInPictureDetails.java
+++ b/src/com/android/settings/applications/specialaccess/pictureinpicture/PictureInPictureDetails.java
@@ -28,7 +28,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoWithHeader;
@@ -41,7 +41,7 @@ public class PictureInPictureDetails extends AppInfoWithHeader
private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
private static final String LOG_TAG = "PictureInPictureDetails";
- private SwitchPreference mSwitchPref;
+ private TwoStatePreference mSwitchPref;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -49,7 +49,7 @@ public class PictureInPictureDetails extends AppInfoWithHeader
// find preferences
addPreferencesFromResource(R.xml.picture_in_picture_permissions_details);
- mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
+ mSwitchPref = (TwoStatePreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
// set title/summary for all of them
mSwitchPref.setTitle(R.string.picture_in_picture_app_detail_switch);
@@ -124,7 +124,7 @@ public class PictureInPictureDetails extends AppInfoWithHeader
? SettingsEnums.APP_PICTURE_IN_PICTURE_ALLOW
: SettingsEnums.APP_PICTURE_IN_PICTURE_DENY;
final MetricsFeatureProvider metricsFeatureProvider =
- FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider();
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
metricsFeatureProvider.action(
metricsFeatureProvider.getAttribution(getActivity()),
logCategory,
diff --git a/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java b/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java
index 54ac63ebca5e6261c167110db33db849813dfdda..c186e07a327b7cdb7f7892fdf56f6246aa8cfbdc 100644
--- a/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java
+++ b/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java
@@ -126,7 +126,7 @@ public class PremiumSmsAccess extends EmptyTextSettings
if (category != SmsManager.PREMIUM_SMS_CONSENT_UNKNOWN) {
// TODO(117860032): Category is wrong. It should be defined in SettingsEnums.
final MetricsFeatureProvider metricsFeatureProvider =
- FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider();
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
metricsFeatureProvider.action(
metricsFeatureProvider.getAttribution(getActivity()),
category,
diff --git a/src/com/android/settings/applications/specialaccess/vrlistener/VrListenerSettings.java b/src/com/android/settings/applications/specialaccess/vrlistener/VrListenerSettings.java
index a70d03e46ac35e31613cdd4de24b560b8d574a10..d35a492706835a5e4cb6c0667c4a044e4a764753 100644
--- a/src/com/android/settings/applications/specialaccess/vrlistener/VrListenerSettings.java
+++ b/src/com/android/settings/applications/specialaccess/vrlistener/VrListenerSettings.java
@@ -69,7 +69,7 @@ public class VrListenerSettings extends ManagedServiceSettings {
int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_VRHELPER_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_VRHELPER_DENY;
final MetricsFeatureProvider metricsFeatureProvider =
- FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider();
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
metricsFeatureProvider.action(
metricsFeatureProvider.getAttribution(getActivity()),
logCategory,
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
index 2c52a8c6d62b1d7c851942d810ecb988074508fd..b4a0c88d950eab9d899164ee2e2e8ec630c054d8 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
@@ -23,7 +23,6 @@ import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.ParceledListSlice;
-import android.os.AsyncTask;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
@@ -110,7 +109,7 @@ public class ZenAccessController extends BasePreferenceController {
static void logSpecialPermissionChange(boolean enable, String packageName, Context context) {
int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_DND_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_DND_DENY;
- FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(context,
logCategory, packageName);
}
}
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
index 0a326c6629bfab08d18fc9213bac97beba5cfba4..ffe13e6f5a0f8266088e759075d2b3819b218d0d 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
@@ -16,13 +16,12 @@
package com.android.settings.applications.specialaccess.zenaccess;
-import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoWithHeader;
@@ -66,7 +65,7 @@ public class ZenAccessDetails extends AppInfoWithHeader implements
return null;
}
- public void updatePreference(Context context, SwitchPreference preference) {
+ private void updatePreference(Context context, TwoStatePreference preference) {
final CharSequence label = mPackageInfo.applicationInfo.loadLabel(mPm);
final Set autoApproved = ZenAccessController.getAutoApprovedPackages(context);
if (autoApproved.contains(mPackageName)) {
diff --git a/src/com/android/settings/aware/AwareFeatureProvider.java b/src/com/android/settings/aware/AwareFeatureProvider.java
deleted file mode 100644
index a4e9c0cd31d929d52b30851e2a0ef075ae53cc83..0000000000000000000000000000000000000000
--- a/src/com/android/settings/aware/AwareFeatureProvider.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2019 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.aware;
-
-import android.content.Context;
-
-import androidx.fragment.app.Fragment;
-
-public interface AwareFeatureProvider {
- /** Returns true if the aware sensor is supported. */
- boolean isSupported(Context context);
-
- /** Returns true if the aware feature is enabled. */
- boolean isEnabled(Context context);
-
- /** Show information dialog. */
- void showRestrictionDialog(Fragment parent);
-
- /** Return Quick Gestures Summary. */
- CharSequence getGestureSummary(Context context, boolean sensorSupported,
- boolean assistGestureEnabled, boolean assistGestureSilenceEnabled);
-}
diff --git a/src/com/android/settings/aware/AwareFeatureProviderImpl.java b/src/com/android/settings/aware/AwareFeatureProviderImpl.java
deleted file mode 100644
index 6f80d8a7ae59467ebaec2d8de3bdfc10839090d6..0000000000000000000000000000000000000000
--- a/src/com/android/settings/aware/AwareFeatureProviderImpl.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2019 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.aware;
-
-import android.content.Context;
-
-import androidx.fragment.app.Fragment;
-
-public class AwareFeatureProviderImpl implements AwareFeatureProvider {
- @Override
- public boolean isSupported(Context context) {
- return false;
- }
-
- @Override
- public boolean isEnabled(Context context) {
- return false;
- }
-
- @Override
- public void showRestrictionDialog(Fragment parent) {
- }
-
- @Override
- public CharSequence getGestureSummary(Context context, boolean sensorSupported,
- boolean assistGestureEnabled, boolean assistGestureSilenceEnabled) {
- return null;
- }
-}
diff --git a/src/com/android/settings/backup/AutoRestorePreferenceController.java b/src/com/android/settings/backup/AutoRestorePreferenceController.java
index bf63e25b522b0b342c2fbaecdaa1f669e8de6dee..1394ce1361973aa9665c8127dea1072a0ec403e1 100644
--- a/src/com/android/settings/backup/AutoRestorePreferenceController.java
+++ b/src/com/android/settings/backup/AutoRestorePreferenceController.java
@@ -25,7 +25,7 @@ import android.provider.Settings;
import android.util.Log;
import androidx.preference.Preference;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
@@ -79,7 +79,7 @@ public class AutoRestorePreferenceController extends TogglePreferenceController
backupManager.setAutoRestore(nextValue);
result = true;
} catch (RemoteException e) {
- ((SwitchPreference) mPreference).setChecked(!nextValue);
+ ((TwoStatePreference) mPreference).setChecked(!nextValue);
Log.e(TAG, "Error can't set setAutoRestore", e);
}
diff --git a/src/com/android/settings/backup/BackupSettingsHelper.java b/src/com/android/settings/backup/BackupSettingsHelper.java
index b55172e64873725c9ad5da1f3d7b9931a4bc1fc2..258f54dc07d7b5c815123fd4897617fc12204f4a 100644
--- a/src/com/android/settings/backup/BackupSettingsHelper.java
+++ b/src/com/android/settings/backup/BackupSettingsHelper.java
@@ -24,7 +24,6 @@ import android.content.Intent;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
@@ -50,24 +49,6 @@ public class BackupSettingsHelper {
mContext = context;
}
- /**
- * If there is only one profile, show whether the backup is on or off.
- * Otherwise, show nothing.
- */
- public String getSummary() {
- UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (userManager.getUserProfiles().size() == 1) {
- try {
- int resId = mBackupManager.isBackupEnabled()
- ? R.string.backup_summary_state_on : R.string.backup_summary_state_off;
- return mContext.getText(resId).toString();
- } catch (RemoteException e) {
- Log.e(TAG, "Error getting isBackupEnabled", e);
- }
- }
- return null;
- }
-
/**
* Returns an intent to launch backup settings from backup transport if the intent was provided
* by the transport. Otherwise returns the intent to launch the default backup settings screen.
diff --git a/src/com/android/settings/backup/SettingsBackupHelper.java b/src/com/android/settings/backup/SettingsBackupHelper.java
index a682df8b88b6cada0d824be1b9e2f138931dcc85..04935a7bf0b32cea7a8f28f8c6b5fb9503eb0857 100644
--- a/src/com/android/settings/backup/SettingsBackupHelper.java
+++ b/src/com/android/settings/backup/SettingsBackupHelper.java
@@ -16,29 +16,47 @@
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.onboarding.OnboardingFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.shortcut.CreateShortcutPreferenceController;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import com.android.settings.flags.Flags;
/**
* 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";
@Override
public void onCreate() {
super.onCreate();
addHelper("no-op", new NoOpHelper());
addHelper(BatteryBackupHelper.TAG, new BatteryBackupHelper(this));
+ addHelper(PREF_LOCALE_NOTIFICATION,
+ new SharedPreferencesBackupHelper(this, LOCALE_NOTIFICATION));
+ if (Flags.enableSoundBackup()) {
+ OnboardingFeatureProvider onboardingFeatureProvider =
+ FeatureFactory.getFeatureFactory().getOnboardingFeatureProvider();
+ if (onboardingFeatureProvider != null) {
+ addHelper(SOUND_BACKUP_HELPER, onboardingFeatureProvider.
+ getSoundBackupHelper(this, this.getBackupRestoreEventLogger()));
+ }
+ }
}
@Override
diff --git a/src/com/android/settings/backup/ToggleBackupSettingFragment.java b/src/com/android/settings/backup/ToggleBackupSettingFragment.java
index 8f60be9a1cfeb32b433af84136779088cc543190..c3ad92fe23b458f744d05c40d8f8faefc459b490 100644
--- a/src/com/android/settings/backup/ToggleBackupSettingFragment.java
+++ b/src/com/android/settings/backup/ToggleBackupSettingFragment.java
@@ -11,7 +11,6 @@ import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
-import android.widget.Switch;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
@@ -116,8 +115,7 @@ public class ToggleBackupSettingFragment extends SettingsPreferenceFragment
mSwitchBar.setOnBeforeCheckedChangeListener(
new SettingsMainSwitchBar.OnBeforeCheckedChangeListener() {
@Override
- public boolean onBeforeCheckedChanged(
- Switch toggleSwitch, boolean checked) {
+ public boolean onBeforeCheckedChanged(boolean checked) {
if (!checked) {
// Don't change Switch status until user makes choice in dialog
// so return true here.
diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
index ebbe2e85bcbcedf6ab1aeeedb7f45fff7347c761..40763e3934e21cedd0f0915bddda03b0cd879310 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
@@ -238,7 +238,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
if (parentalConsent && isMultiSensor && mIsFaceEnrollable) {
// Exclude face enrollment from setup wizard if feature config not supported
// in setup wizard flow, we still allow user enroll faces through settings.
- mIsFaceEnrollable = FeatureFactory.getFactory(getApplicationContext())
+ mIsFaceEnrollable = FeatureFactory.getFeatureFactory()
.getFaceFeatureProvider()
.isSetupWizardSupported(getApplicationContext());
Log.d(TAG, "config_suw_support_face_enroll: " + mIsFaceEnrollable);
@@ -424,7 +424,9 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
// handles responses while parental consent is pending
private void handleOnActivityResultWhileConsenting(
int requestCode, int resultCode, Intent data) {
- overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
+ overridePendingTransition(
+ com.google.android.setupdesign.R.anim.sud_slide_next_in,
+ com.google.android.setupdesign.R.anim.sud_slide_next_out);
switch (requestCode) {
case REQUEST_CHOOSE_LOCK:
diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java
index 6e110799c463de8b86f54511c701312fdced2931..c9c8cff034c00f95af31be83a7ced1a278f188f5 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollBase.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java
@@ -171,7 +171,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
mNextLaunched = savedInstanceState.getBoolean(EXTRA_KEY_NEXT_LAUNCHED);
}
mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
- mPostureGuidanceIntent = FeatureFactory.getFactory(getApplicationContext())
+ mPostureGuidanceIntent = FeatureFactory.getFeatureFactory()
.getFaceFeatureProvider().getPostureGuidanceIntent(getApplicationContext());
}
diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
index 46f534df364781542ebd97556bdbdf5a93eeadb2..44b1b3b17e563321ffca34a0504c2d9bb08ac132 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
@@ -367,7 +367,9 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
updatePasswordQuality();
final boolean handled = onSetOrConfirmCredentials(data);
if (!handled) {
- overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
+ overridePendingTransition(
+ com.google.android.setupdesign.R.anim.sud_slide_next_in,
+ com.google.android.setupdesign.R.anim.sud_slide_next_out);
getNextButton().setEnabled(false);
getChallenge(((sensorId, userId, challenge) -> {
mSensorId = sensorId;
@@ -387,7 +389,9 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
if (resultCode == RESULT_OK && data != null) {
final boolean handled = onSetOrConfirmCredentials(data);
if (!handled) {
- overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
+ overridePendingTransition(
+ com.google.android.setupdesign.R.anim.sud_slide_next_in,
+ com.google.android.setupdesign.R.anim.sud_slide_next_out);
getNextButton().setEnabled(false);
getChallenge(((sensorId, userId, challenge) -> {
mSensorId = sensorId;
@@ -403,7 +407,9 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
finish();
}
} else if (requestCode == LEARN_MORE_REQUEST) {
- overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out);
+ overridePendingTransition(
+ com.google.android.setupdesign.R.anim.sud_slide_back_in,
+ com.google.android.setupdesign.R.anim.sud_slide_back_out);
} else if (requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST
|| cameFromMultiBioFpAuthAddAnother) {
if (isResultFinished(resultCode)) {
diff --git a/src/com/android/settings/biometrics/BiometricHandoffActivity.java b/src/com/android/settings/biometrics/BiometricHandoffActivity.java
index 7f28ced20290f3db373295bb4225235196d37100..263bab1607ee277a67ec0e9295b01951b55a4ae6 100644
--- a/src/com/android/settings/biometrics/BiometricHandoffActivity.java
+++ b/src/com/android/settings/biometrics/BiometricHandoffActivity.java
@@ -56,7 +56,7 @@ public class BiometricHandoffActivity extends BiometricEnrollBase {
.setText(R.string.biometric_settings_hand_back_to_guardian_ok)
.setButtonType(FooterButton.ButtonType.NEXT)
.setListener(this::onNextButtonClick)
- .setTheme(R.style.SudGlifButton_Primary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build();
}
return mPrimaryFooterButton;
diff --git a/src/com/android/settings/biometrics/BiometricSettingsProvider.kt b/src/com/android/settings/biometrics/BiometricSettingsProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..308f24508e3fc972d784b229e46a476c69ba852d
--- /dev/null
+++ b/src/com/android/settings/biometrics/BiometricSettingsProvider.kt
@@ -0,0 +1,78 @@
+/*
+ * 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
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.database.Cursor
+import android.net.Uri
+import android.os.Bundle
+import com.android.settings.flags.Flags
+
+class BiometricSettingsProvider : ContentProvider() {
+ companion object {
+ const val GET_SUW_FACE_ENABLED = "getSuwFaceEnabled"
+ const val SUW_FACE_ENABLED = "suw_face_enabled"
+ }
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int {
+ throw UnsupportedOperationException("query operation not supported currently.")
+ }
+
+ override fun getType(uri: Uri): String? {
+ throw UnsupportedOperationException("getType not supported")
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ throw UnsupportedOperationException("insert not supported")
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array?,
+ selection: String?,
+ selectionArgs: Array?,
+ sortOrder: String?
+ ): Cursor? {
+ throw UnsupportedOperationException("query not supported")
+ }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array?
+ ): Int {
+ throw UnsupportedOperationException("update not supported")
+ }
+
+ override fun onCreate(): Boolean = true
+
+ override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
+ val bundle = Bundle()
+ if (Flags.biometricSettingsProvider()) {
+ if (GET_SUW_FACE_ENABLED == method) {
+ val faceEnabled =
+ requireContext()
+ .resources
+ .getBoolean(com.android.settings.R.bool.config_suw_support_face_enroll)
+ bundle.putBoolean(SUW_FACE_ENABLED, faceEnabled)
+ }
+ }
+ return bundle
+ }
+}
diff --git a/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java b/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java
index 2f9ae6b81172c3510228608fd84d3fc88ffa4919..b9d20720e1e9e244e7e6e11a098a97ec4dda5fd0 100644
--- a/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java
@@ -71,7 +71,7 @@ public abstract class BiometricStatusPreferenceController extends BasePreference
public BiometricStatusPreferenceController(Context context, String key) {
super(context, key);
mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mLockPatternUtils = FeatureFactory.getFactory(context)
+ mLockPatternUtils = FeatureFactory.getFeatureFactory()
.getSecurityFeatureProvider()
.getLockPatternUtils(context);
mProfileChallengeUserId = Utils.getManagedProfileId(mUm, mUserId);
diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java
index 4e1a2f329b4de02b1d119ffa2187311d11f5c7fe..53892bd9a1cb547c9258c72e8c7531773a5eae0e 100644
--- a/src/com/android/settings/biometrics/BiometricUtils.java
+++ b/src/com/android/settings/biometrics/BiometricUtils.java
@@ -16,6 +16,8 @@
package com.android.settings.biometrics;
+import static android.util.FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT;
+
import android.annotation.IntDef;
import android.app.Activity;
import android.app.PendingIntent;
@@ -249,20 +251,22 @@ public class BiometricUtils {
*/
public static Intent getFingerprintFindSensorIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
- if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT)) {
- final Intent intent = new Intent(context, FingerprintEnrollmentActivity.class);
+ final boolean isSuw = WizardManagerHelper.isAnySetupWizard(activityIntent);
+ final Intent intent;
+ if (FeatureFlagUtils.isEnabled(context, SETTINGS_BIOMETRICS2_ENROLLMENT)) {
+ intent = new Intent(context, isSuw
+ ? FingerprintEnrollmentActivity.SetupActivity.class
+ : FingerprintEnrollmentActivity.class);
intent.putExtra(BiometricEnrollActivity.EXTRA_SKIP_INTRO, true);
- if (WizardManagerHelper.isAnySetupWizard(activityIntent)) {
- SetupWizardUtils.copySetupExtras(activityIntent, intent);
- }
- return intent;
- } else if (WizardManagerHelper.isAnySetupWizard(activityIntent)) {
- Intent intent = new Intent(context, SetupFingerprintEnrollFindSensor.class);
- SetupWizardUtils.copySetupExtras(activityIntent, intent);
- return intent;
} else {
- return new Intent(context, FingerprintEnrollFindSensor.class);
+ intent = new Intent(context, isSuw
+ ? SetupFingerprintEnrollFindSensor.class
+ : FingerprintEnrollFindSensor.class);
}
+ if (isSuw) {
+ SetupWizardUtils.copySetupExtras(activityIntent, intent);
+ }
+ return intent;
}
/**
@@ -272,19 +276,21 @@ public class BiometricUtils {
*/
public static Intent getFingerprintIntroIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
- if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT)) {
- final Intent intent = new Intent(context, FingerprintEnrollmentActivity.class);
- if (WizardManagerHelper.isAnySetupWizard(activityIntent)) {
- WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
- }
- return intent;
- } else if (WizardManagerHelper.isAnySetupWizard(activityIntent)) {
- Intent intent = new Intent(context, SetupFingerprintEnrollIntroduction.class);
- WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
- return intent;
+ final boolean isSuw = WizardManagerHelper.isAnySetupWizard(activityIntent);
+ final Intent intent;
+ if (FeatureFlagUtils.isEnabled(context, SETTINGS_BIOMETRICS2_ENROLLMENT)) {
+ intent = new Intent(context, isSuw
+ ? FingerprintEnrollmentActivity.SetupActivity.class
+ : FingerprintEnrollmentActivity.class);
} else {
- return new Intent(context, FingerprintEnrollIntroduction.class);
+ intent = new Intent(context, isSuw
+ ? SetupFingerprintEnrollIntroduction.class
+ : FingerprintEnrollIntroduction.class);
}
+ if (isSuw) {
+ WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
+ }
+ return intent;
}
/**
@@ -508,7 +514,7 @@ public class BiometricUtils {
* Returns true if the device supports Face enrollment in SUW flow
*/
public static boolean isFaceSupportedInSuw(Context context) {
- return FeatureFactory.getFactory(context).getFaceFeatureProvider().isSetupWizardSupported(
+ return FeatureFactory.getFeatureFactory().getFaceFeatureProvider().isSetupWizardSupported(
context);
}
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockRequireBiometricSetup.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockRequireBiometricSetup.java
index 1f30e566022519dff8622cfb19c7c1854b3a777e..ce12c5929b199dcfa1cea62c2dbbd535a81416bb 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockRequireBiometricSetup.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockRequireBiometricSetup.java
@@ -88,7 +88,7 @@ public class ActiveUnlockRequireBiometricSetup extends BiometricEnrollBase {
.setText(R.string.cancel)
.setListener(this::onCancelClick)
.setButtonType(FooterButton.ButtonType.CANCEL)
- .setTheme(R.style.SudGlifButton_Secondary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
);
@@ -97,7 +97,7 @@ public class ActiveUnlockRequireBiometricSetup extends BiometricEnrollBase {
.setText(R.string.security_settings_activeunlock_biometric_setup)
.setListener(this::onNextButtonClick)
.setButtonType(FooterButton.ButtonType.NEXT)
- .setTheme(R.style.SudGlifButton_Primary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build()
);
}
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
index 4d925986081cf406f7e5639cd16fef1b62fbc4b7..2eee9df64f43ab4010f0cf064fb1f9a581e7d7cb 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
@@ -191,13 +191,10 @@ public class ActiveUnlockStatusUtils {
public String getIntroForActiveUnlock() {
final boolean faceAllowed = Utils.hasFaceHardware(mContext);
final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
- if (useBiometricFailureLayout()) {
+ if (isAvailable()) {
int introRes = getIntroRes(faceAllowed, fingerprintAllowed);
return introRes == 0 ? "" : mContext.getString(introRes);
}
- if (useUnlockIntentLayout() && (!faceAllowed || !fingerprintAllowed)) {
- return "";
- }
return mContext.getString(R.string.biometric_settings_intro);
}
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index 69ae9a735e364ab8506ed80f575edd4cb542ec69..d8d34844948f4624a827c83715b34d56938cae89 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -328,8 +328,9 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
if (BiometricUtils.containsGatekeeperPasswordHandle(data)) {
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
if (!TextUtils.isEmpty(mRetryPreferenceKey)) {
- getActivity().overridePendingTransition(R.anim.sud_slide_next_in,
- R.anim.sud_slide_next_out);
+ getActivity().overridePendingTransition(
+ com.google.android.setupdesign.R.anim.sud_slide_next_in,
+ com.google.android.setupdesign.R.anim.sud_slide_next_out);
retryPreferenceKey(mRetryPreferenceKey, mRetryPreferenceExtra);
}
} else {
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollAccessibilityToggle.java b/src/com/android/settings/biometrics/face/FaceEnrollAccessibilityToggle.java
index f6ba0f989e506fd8911e0eb85b18dd453ba84725..b0b94888231d282b26bdad2d6f2f1723ac6a9b11 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollAccessibilityToggle.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollAccessibilityToggle.java
@@ -22,17 +22,18 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
-import android.widget.Switch;
import android.widget.TextView;
import com.android.settings.R;
+import com.google.android.material.materialswitch.MaterialSwitch;
+
/**
* A layout that contains a start-justified title, and an end-justified switch.
*/
public class FaceEnrollAccessibilityToggle extends LinearLayout {
- private Switch mSwitch;
+ private final MaterialSwitch mSwitch;
public FaceEnrollAccessibilityToggle(Context context) {
this(context, null /* attrs */);
@@ -70,13 +71,14 @@ public class FaceEnrollAccessibilityToggle extends LinearLayout {
public void setChecked(boolean checked) {
mSwitch.setChecked(checked);
+ mSwitch.jumpDrawablesToCurrentState(); // Do not trigger animation from activity
}
public void setListener(CompoundButton.OnCheckedChangeListener listener) {
mSwitch.setOnCheckedChangeListener(listener);
}
- public Switch getSwitch() {
+ public CompoundButton getSwitch() {
return mSwitch;
}
}
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
index 4ef47522fa820e02a612b03f28802dd892906ea6..62e9757b365619757f74e8017948d38695ac348f 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
@@ -25,6 +25,8 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.face.FaceManager;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -32,6 +34,7 @@ import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.CompoundButton;
+import android.widget.ScrollView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -86,6 +89,23 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
}
};
+ final View.OnLayoutChangeListener mSwitchDiversityOnLayoutChangeListener =
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ if (oldBottom == 0 && bottom != 0) {
+ new Handler(Looper.getMainLooper()).post(() -> {
+ final ScrollView scrollView =
+ findViewById(com.google.android.setupdesign.R.id.sud_scroll_view);
+ if (scrollView != null) {
+ scrollView.fullScroll(View.FOCUS_DOWN); // scroll down
+ }
+ if (mSwitchDiversity != null) {
+ mSwitchDiversity.removeOnLayoutChangeListener(
+ this.mSwitchDiversityOnLayoutChangeListener);
+ }
+ });
+ }
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -117,7 +137,8 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
.setText(R.string.skip_label)
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.SKIP)
- .setTheme(R.style.SudGlifButton_Secondary)
+ .setTheme(
+ com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
);
} else {
@@ -126,7 +147,8 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
.setText(R.string.security_settings_face_enroll_introduction_cancel)
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.CANCEL)
- .setTheme(R.style.SudGlifButton_Secondary)
+ .setTheme(
+ com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
);
}
@@ -135,7 +157,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
.setText(R.string.security_settings_face_enroll_education_start)
.setListener(this::onNextButtonClick)
.setButtonType(FooterButton.ButtonType.NEXT)
- .setTheme(R.style.SudGlifButton_Primary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build();
final AccessibilityManager accessibilityManager = getApplicationContext().getSystemService(
@@ -153,6 +175,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
mSwitchDiversity.setChecked(true);
accessibilityButton.setVisibility(View.GONE);
mSwitchDiversity.setVisibility(View.VISIBLE);
+ mSwitchDiversity.addOnLayoutChangeListener(mSwitchDiversityOnLayoutChangeListener);
});
mSwitchDiversity = findViewById(R.id.toggle_diversity);
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
index 68fbe3899f1b37a7f1a053853a95a92b20dc5c1b..472410bdcf36687c7594e936e450662dfd803684 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
@@ -100,7 +100,7 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling {
.setText(R.string.security_settings_face_enroll_enrolling_skip)
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.SKIP)
- .setTheme(R.style.SudGlifButton_Secondary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
);
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollFinish.java b/src/com/android/settings/biometrics/face/FaceEnrollFinish.java
index 6e99cdb344ada505e0c1b43463c5565da4510252..16aeeb0c3609a763f449e1f34048ca761579200f 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollFinish.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollFinish.java
@@ -43,7 +43,7 @@ public class FaceEnrollFinish extends BiometricEnrollBase {
.setText(R.string.security_settings_face_enroll_done)
.setListener(this::onNextButtonClick)
.setButtonType(FooterButton.ButtonType.NEXT)
- .setTheme(R.style.SudGlifButton_Primary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build()
);
}
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
index bea0c3389ec92adddd51e3af443fe9de98676b4a..eb50e0874c013316c07a8b2040ed76260b7ed787 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
@@ -537,7 +537,7 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
.setText(R.string.security_settings_face_enroll_introduction_agree)
.setButtonType(FooterButton.ButtonType.OPT_IN)
.setListener(this::onNextButtonClick)
- .setTheme(R.style.SudGlifButton_Primary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build();
}
return mPrimaryFooterButton;
@@ -551,7 +551,7 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
.setText(R.string.security_settings_face_enroll_introduction_no_thanks)
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.NEXT)
- .setTheme(R.style.SudGlifButton_Primary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build();
}
return mSecondaryFooterButton;
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index 2e9440412eca89459c0ab5d02b9239bd0a8bb194..bebb5c70cab2da7460262a83ed8c5f675fddb64f 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -182,7 +182,7 @@ public class FaceSettings extends DashboardFragment {
mUserId = getActivity().getIntent().getIntExtra(
Intent.EXTRA_USER_ID, UserHandle.myUserId());
- mFaceFeatureProvider = FeatureFactory.getFactory(getContext()).getFaceFeatureProvider();
+ mFaceFeatureProvider = FeatureFactory.getFeatureFactory().getFaceFeatureProvider();
if (mUserManager.getUserInfo(mUserId).isManagedProfile()) {
getActivity().setTitle(
@@ -418,13 +418,9 @@ public class FaceSettings extends DashboardFragment {
}
private boolean isAttentionSupported(Context context) {
- FaceFeatureProvider featureProvider = FeatureFactory.getFactory(
- context).getFaceFeatureProvider();
- boolean isAttentionSupported = false;
- if (featureProvider != null) {
- isAttentionSupported = featureProvider.isAttentionSupported(context);
- }
- return isAttentionSupported;
+ FaceFeatureProvider featureProvider =
+ FeatureFactory.getFeatureFactory().getFaceFeatureProvider();
+ return featureProvider.isAttentionSupported(context);
}
private boolean hasEnrolledBiometrics(Context context) {
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
index 82fa00b864ecce050d12714f037746e372d93a2e..b5e99083a5ffa570d2dc17f14e3c3a376004c62e 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
@@ -23,7 +23,7 @@ import android.hardware.face.FaceManager.SetFeatureCallback;
import android.provider.Settings;
import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.settings.Utils;
@@ -37,7 +37,7 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe
private byte[] mToken;
private FaceManager mFaceManager;
- private SwitchPreference mPreference;
+ private TwoStatePreference mPreference;
private final SetFeatureCallback mSetFeatureCallback = new SetFeatureCallback() {
@Override
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsFooterPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsFooterPreferenceController.java
index 0ce02ab6d0f28bcdc1f7380f2915c00957e7bf6f..e2123f05eff87eb0c9cefd455f14bf5dbb5863c9 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsFooterPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsFooterPreferenceController.java
@@ -49,7 +49,7 @@ public class FaceSettingsFooterPreferenceController extends BasePreferenceContro
public FaceSettingsFooterPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
- mProvider = FeatureFactory.getFactory(context).getFaceFeatureProvider();
+ mProvider = FeatureFactory.getFeatureFactory().getFaceFeatureProvider();
}
@Override
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
index 4b2e3363b6dd1230935c95395824e8e09195431e..797364b18a81d2b374150bddbc7ed130eacd0086 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
@@ -178,7 +178,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
super(context, preferenceKey);
mContext = context;
mFaceManager = context.getSystemService(FaceManager.class);
- mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
mFaceUpdater = new FaceUpdater(context, mFaceManager);
}
diff --git a/src/com/android/settings/biometrics/face/FaceUpdater.java b/src/com/android/settings/biometrics/face/FaceUpdater.java
index 3a1f77c43529b2e7afc6c5d38002f57fc1c69981..57c11953d6abb5346439fa34951538c670b6b6c5 100644
--- a/src/com/android/settings/biometrics/face/FaceUpdater.java
+++ b/src/com/android/settings/biometrics/face/FaceUpdater.java
@@ -50,8 +50,9 @@ public class FaceUpdater {
/** Wrapper around the {@link FaceManager#enroll} method. */
public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
FaceManager.EnrollmentCallback callback, int[] disabledFeatures) {
- mFaceManager.enroll(userId, hardwareAuthToken, cancel,
- new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures);
+ this.enroll(userId, hardwareAuthToken, cancel,
+ new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures,
+ null, false);
}
/** Wrapper around the {@link FaceManager#enroll} method. */
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index a62bd6723ecf44aef713174b2aa85f23bbdc3215..6e908850e8973696bc7417b147108cae4d89190b 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -59,7 +59,6 @@ import android.view.animation.Interpolator;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
-import android.widget.Toast;
import androidx.annotation.IdRes;
import androidx.appcompat.app.AlertDialog;
@@ -69,10 +68,14 @@ 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.fingerprint.feature.SfpsEnrollmentFeature;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.flags.Flags;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.display.DisplayDensityUtils;
import com.airbnb.lottie.LottieAnimationView;
+import com.airbnb.lottie.LottieComposition;
import com.airbnb.lottie.LottieCompositionFactory;
import com.airbnb.lottie.LottieProperty;
import com.airbnb.lottie.model.KeyPath;
@@ -85,6 +88,7 @@ import com.google.android.setupdesign.template.HeaderMixin;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.function.Function;
/**
* Activity which handles the actual enrolling for fingerprint.
@@ -100,27 +104,22 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
private static final int PROGRESS_BAR_MAX = 10000;
- private static final int STAGE_UNKNOWN = -1;
+ public static final int STAGE_UNKNOWN = -1;
private static final int STAGE_CENTER = 0;
private static final int STAGE_GUIDED = 1;
private static final int STAGE_FINGERTIP = 2;
private static final int STAGE_LEFT_EDGE = 3;
private static final int STAGE_RIGHT_EDGE = 4;
- @VisibleForTesting
- protected static final int SFPS_STAGE_NO_ANIMATION = 0;
+ public static final int SFPS_STAGE_NO_ANIMATION = 0;
- @VisibleForTesting
- protected static final int SFPS_STAGE_CENTER = 1;
+ public static final int SFPS_STAGE_CENTER = 1;
- @VisibleForTesting
- protected static final int SFPS_STAGE_FINGERTIP = 2;
+ public static final int SFPS_STAGE_FINGERTIP = 2;
- @VisibleForTesting
- protected static final int SFPS_STAGE_LEFT_EDGE = 3;
+ public static final int SFPS_STAGE_LEFT_EDGE = 3;
- @VisibleForTesting
- protected static final int SFPS_STAGE_RIGHT_EDGE = 4;
+ public static final int SFPS_STAGE_RIGHT_EDGE = 4;
@IntDef({STAGE_UNKNOWN, STAGE_CENTER, STAGE_GUIDED, STAGE_FINGERTIP, STAGE_LEFT_EDGE,
STAGE_RIGHT_EDGE})
@@ -197,6 +196,11 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
private OrientationEventListener mOrientationEventListener;
private int mPreviousRotation = 0;
+ @NonNull
+ private SfpsEnrollmentFeature mSfpsEnrollmentFeature = new EmptySfpsEnrollmentFeature();
+ @Nullable
+ private UdfpsEnrollCalibrator mCalibrator;
+
@VisibleForTesting
protected boolean shouldShowLottie() {
DisplayDensityUtils displayDensity = new DisplayDensityUtils(getApplicationContext());
@@ -204,6 +208,10 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
final int currentDensity = displayDensity.getDefaultDisplayDensityValues()
[currentDensityIndex];
final int defaultDensity = displayDensity.getDefaultDensityForDefaultDisplay();
+
+ if (getResources().getConfiguration().fontScale > 1) {
+ return false;
+ }
return defaultDensity == currentDensity;
}
@@ -217,14 +225,6 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (isInMultiWindowMode()) {
- final Toast splitUnsupportedToast = Toast.makeText(this,
- R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT);
- splitUnsupportedToast.show();
- finish();
- return;
- }
-
if (savedInstanceState != null) {
restoreSavedState(savedInstanceState);
}
@@ -248,7 +248,15 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
setContentView(layout);
setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
+
+ if (Flags.udfpsEnrollCalibration()) {
+ mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
+ .getUdfpsEnrollCalibrator(getApplicationContext(), savedInstanceState,
+ getIntent());
+ }
} else if (mCanAssumeSfps) {
+ mSfpsEnrollmentFeature = FeatureFactory.getFeatureFactory()
+ .getFingerprintFeatureProvider().getSfpsEnrollmentFeature();
setContentView(R.layout.sfps_enroll_enrolling);
setHelpAnimation();
} else {
@@ -282,7 +290,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.SKIP)
- .setTheme(R.style.SudGlifButton_Secondary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
);
@@ -365,6 +373,11 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_STATE_CANCELED, mIsCanceled);
outState.putInt(KEY_STATE_PREVIOUS_ROTATION, mPreviousRotation);
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ mCalibrator.onSaveInstanceState(outState);
+ }
+ }
}
private void restoreSavedState(Bundle savedInstanceState) {
@@ -604,7 +617,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
}
switch (getCurrentSfpsStage()) {
case SFPS_STAGE_NO_ANIMATION:
- setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_NO_ANIMATION));
if (!mHaveShownSfpsNoAnimationLottie && mIllustrationLottie != null) {
mHaveShownSfpsNoAnimationLottie = true;
mIllustrationLottie.setContentDescription(
@@ -613,39 +627,48 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
0
)
);
- configureEnrollmentStage(R.raw.sfps_lottie_no_animation);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_NO_ANIMATION));
}
break;
case SFPS_STAGE_CENTER:
- setHeaderText(R.string.security_settings_sfps_enroll_finger_center_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_CENTER));
if (!mHaveShownSfpsCenterLottie && mIllustrationLottie != null) {
mHaveShownSfpsCenterLottie = true;
- configureEnrollmentStage(R.raw.sfps_lottie_pad_center);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_CENTER));
}
break;
case SFPS_STAGE_FINGERTIP:
- setHeaderText(R.string.security_settings_sfps_enroll_fingertip_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_FINGERTIP));
if (!mHaveShownSfpsTipLottie && mIllustrationLottie != null) {
mHaveShownSfpsTipLottie = true;
- configureEnrollmentStage(R.raw.sfps_lottie_tip);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_FINGERTIP));
}
break;
case SFPS_STAGE_LEFT_EDGE:
- setHeaderText(R.string.security_settings_sfps_enroll_left_edge_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_LEFT_EDGE));
if (!mHaveShownSfpsLeftEdgeLottie && mIllustrationLottie != null) {
mHaveShownSfpsLeftEdgeLottie = true;
- configureEnrollmentStage(R.raw.sfps_lottie_left_edge);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_LEFT_EDGE));
}
break;
case SFPS_STAGE_RIGHT_EDGE:
- setHeaderText(R.string.security_settings_sfps_enroll_right_edge_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_RIGHT_EDGE));
if (!mHaveShownSfpsRightEdgeLottie && mIllustrationLottie != null) {
mHaveShownSfpsRightEdgeLottie = true;
- configureEnrollmentStage(R.raw.sfps_lottie_right_edge);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_RIGHT_EDGE));
}
break;
@@ -670,11 +693,16 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
setDescriptionText("");
}
LottieCompositionFactory.fromRawRes(this, lottie)
- .addListener((c) -> {
- mIllustrationLottie.setComposition(c);
- mIllustrationLottie.setVisibility(View.VISIBLE);
- mIllustrationLottie.playAnimation();
- });
+ .addListener((c) -> onLottieComposition(mIllustrationLottie, c));
+ }
+
+ private void onLottieComposition(LottieAnimationView view, LottieComposition composition) {
+ if (view == null || composition == null) {
+ return;
+ }
+ view.setComposition(composition);
+ view.setVisibility(View.VISIBLE);
+ view.playAnimation();
}
@EnrollStage
@@ -704,17 +732,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
}
final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
- if (progressSteps < getStageThresholdSteps(0)) {
- return SFPS_STAGE_NO_ANIMATION;
- } else if (progressSteps < getStageThresholdSteps(1)) {
- return SFPS_STAGE_CENTER;
- } else if (progressSteps < getStageThresholdSteps(2)) {
- return SFPS_STAGE_FINGERTIP;
- } else if (progressSteps < getStageThresholdSteps(3)) {
- return SFPS_STAGE_LEFT_EDGE;
- } else {
- return SFPS_STAGE_RIGHT_EDGE;
- }
+ return mSfpsEnrollmentFeature
+ .getCurrentSfpsEnrollStage(progressSteps, this::getStageThresholdSteps);
}
private boolean isStageHalfCompleted() {
@@ -745,22 +764,31 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet");
return 1;
}
- return Math.round(mSidecar.getEnrollmentSteps()
- * mFingerprintManager.getEnrollStageThreshold(index));
+ final float threshold = mCanAssumeSfps
+ ? mSfpsEnrollmentFeature.getEnrollStageThreshold(this, index)
+ : mFingerprintManager.getEnrollStageThreshold(index);
+ return Math.round(mSidecar.getEnrollmentSteps() * threshold);
}
@Override
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
- if (!TextUtils.isEmpty(helpString)) {
+ final CharSequence featuredString = mCanAssumeSfps
+ ? mSfpsEnrollmentFeature.getFeaturedVendorString(this, helpMsgId, helpString)
+ : helpString;
+
+ if (!TextUtils.isEmpty(featuredString)) {
if (!(mCanAssumeUdfps || mCanAssumeSfps)) {
mErrorText.removeCallbacks(mTouchAgainRunnable);
}
- showError(helpString);
+ showError(featuredString);
if (mUdfpsEnrollHelper != null) mUdfpsEnrollHelper.onEnrollmentHelp();
}
dismissTouchDialogIfSfps();
+ if (mCanAssumeSfps) {
+ mSfpsEnrollmentFeature.handleOnEnrollmentHelp(helpMsgId, featuredString, () -> this);
+ }
}
@Override
@@ -1175,4 +1203,28 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH;
}
}
-}
\ No newline at end of file
+
+ private static class EmptySfpsEnrollmentFeature implements SfpsEnrollmentFeature {
+ private final String exceptionStr = "Assume sfps but no SfpsEnrollmentFeature impl.";
+
+ @Override
+ public int getCurrentSfpsEnrollStage(int progressSteps, Function mapper) {
+ throw new IllegalStateException(exceptionStr);
+ }
+
+ @Override
+ public int getFeaturedStageHeaderResource(int stage) {
+ throw new IllegalStateException(exceptionStr);
+ }
+
+ @Override
+ public int getSfpsEnrollLottiePerStage(int stage) {
+ throw new IllegalStateException(exceptionStr);
+ }
+
+ @Override
+ public float getEnrollStageThreshold(@NonNull Context context, int index) {
+ 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 aadc9329e4c19c63e145e9b78fe3ec4bbdb49715..059173c198ebb6c4fe7c82290de8c0430dcecb97 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -16,6 +16,8 @@
package com.android.settings.biometrics.fingerprint;
+import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL;
+
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.content.res.Configuration;
@@ -27,7 +29,6 @@ import android.util.Log;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
@@ -38,6 +39,8 @@ import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricEnrollSidecar;
import com.android.settings.biometrics.BiometricUtils;
+import com.android.settings.flags.Flags;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.widget.LottieColorUtils;
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
@@ -74,6 +77,8 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
private ScreenSizeFoldProvider mScreenSizeFoldProvider;
private boolean mIsFolded;
private boolean mIsReverseDefaultRotation;
+ @Nullable
+ private UdfpsEnrollCalibrator mCalibrator;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -95,23 +100,16 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.SKIP)
- .setTheme(R.style.SudGlifButton_Secondary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
);
+ getLayout().getHeaderTextView().setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL);
listenOrientationEvent();
if (mCanAssumeUdfps) {
setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title);
setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message);
- mFooterBarMixin.setPrimaryButton(
- new FooterButton.Builder(this)
- .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
- .setListener(this::onStartButtonClick)
- .setButtonType(FooterButton.ButtonType.NEXT)
- .setTheme(R.style.SudGlifButton_Primary)
- .build()
- );
mIllustrationLottie = findViewById(R.id.illustration_lottie);
AccessibilityManager am = getSystemService(AccessibilityManager.class);
@@ -164,12 +162,22 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
mAnimation = null;
if (mCanAssumeUdfps) {
- mIllustrationLottie.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onStartButtonClick(v);
+ if (Flags.udfpsEnrollCalibration()) {
+ mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
+ .getUdfpsEnrollCalibrator(getApplicationContext(), savedInstanceState,
+ getIntent());
+ if (mCalibrator != null) {
+ mCalibrator.onFindSensorPage(
+ getLifecycle(),
+ getSupportFragmentManager(),
+ this::enableUdfpsLottieAndNextButton
+ );
+ } else {
+ enableUdfpsLottieAndNextButton();
}
- });
+ } else {
+ enableUdfpsLottieAndNextButton();
+ }
} else if (!mCanAssumeSfps) {
View animationView = findViewById(R.id.fingerprint_sensor_location_animation);
if (animationView instanceof FingerprintFindSensorAnimation) {
@@ -178,6 +186,25 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
}
}
+ private void enableUdfpsLottieAndNextButton() {
+ if (isFinishing()) {
+ return;
+ }
+
+ if (mFooterBarMixin.getPrimaryButton() == null) {
+ mFooterBarMixin.setPrimaryButton(new FooterButton.Builder(this)
+ .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
+ .setListener(this::onStartButtonClick)
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+ .build()
+ );
+ }
+ if (mIllustrationLottie != null) {
+ mIllustrationLottie.setOnClickListener(this::onStartButtonClick);
+ }
+ }
+
private int getRotationFromDefault(int rotation) {
if (mIsReverseDefaultRotation) {
return (rotation + 1) % 4;
@@ -255,6 +282,22 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_STATE_IS_NEXT_CLICKED, mNextClicked);
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ mCalibrator.onSaveInstanceState(outState);
+ }
+ }
+ }
+
+ @Override
+ protected Intent getFingerprintEnrollingIntent() {
+ final Intent ret = super.getFingerprintEnrollingIntent();
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ ret.putExtras(mCalibrator.getExtrasForNextIntent(true));
+ }
+ }
+ return ret;
}
@Override
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
index a5d198d8d2bcf8c7cc6ee6415b336b87b692c9c6..722f213ce10500a41472d808b5188e7ad53f6270 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
@@ -79,7 +79,7 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
new FooterButton.Builder(this)
.setText(R.string.fingerprint_enroll_button_add)
.setButtonType(FooterButton.ButtonType.SKIP)
- .setTheme(R.style.SudGlifButton_Secondary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
);
@@ -88,7 +88,7 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
.setText(R.string.security_settings_fingerprint_enroll_done)
.setListener(this::onNextButtonClick)
.setButtonType(FooterButton.ButtonType.NEXT)
- .setTheme(R.style.SudGlifButton_Primary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build()
);
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
index 04063ed29d7803e88882ac9176f8b314e9bb491b..aef3c0633ad1ff9b9039dbaae9b53dae734fe067 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
@@ -45,6 +45,8 @@ import com.android.settings.biometrics.BiometricEnrollIntroduction;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.GatekeeperPasswordProvider;
import com.android.settings.biometrics.MultiBiometricEnrollHelper;
+import com.android.settings.flags.Flags;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -67,6 +69,8 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
private DevicePolicyManager mDevicePolicyManager;
private boolean mCanAssumeUdfps;
+ @Nullable
+ private UdfpsEnrollCalibrator mCalibrator;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -85,6 +89,11 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
+ if (Flags.udfpsEnrollCalibration()) {
+ mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
+ .getUdfpsEnrollCalibrator(getApplicationContext(), savedInstanceState, null);
+ }
+
final ImageView iconFingerprint = findViewById(R.id.icon_fingerprint);
final ImageView iconDeviceLocked = findViewById(R.id.icon_device_locked);
final ImageView iconTrashCan = findViewById(R.id.icon_trash_can);
@@ -127,13 +136,16 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
footerTitle1.setText(getFooterTitle1());
footerTitle2.setText(getFooterTitle2());
- final ScrollView scrollView = findViewById(R.id.sud_scroll_view);
+ final ScrollView scrollView =
+ findViewById(com.google.android.setupdesign.R.id.sud_scroll_view);
scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
final Intent intent = getIntent();
if (mFromSettingsSummary
&& GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)) {
- overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
+ overridePendingTransition(
+ com.google.android.setupdesign.R.anim.sud_slide_next_in,
+ com.google.android.setupdesign.R.anim.sud_slide_next_out);
getNextButton().setEnabled(false);
getChallenge(((sensorId, userId, challenge) -> {
if (isFinishing()) {
@@ -152,6 +164,16 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
}
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ mCalibrator.onSaveInstanceState(outState);
+ }
+ }
+ }
+
@Override
protected void initViews() {
setDescriptionText(getString(
@@ -361,6 +383,11 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
BiometricUtils.getGatekeeperPasswordHandle(getIntent()));
}
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ intent.putExtras(mCalibrator.getExtrasForNextIntent(false));
+ }
+ }
return intent;
}
@@ -406,7 +433,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
.setText(R.string.security_settings_fingerprint_enroll_introduction_agree)
.setListener(this::onNextButtonClick)
.setButtonType(FooterButton.ButtonType.OPT_IN)
- .setTheme(R.style.SudGlifButton_Primary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build();
}
return mPrimaryFooterButton;
@@ -420,7 +447,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
.setText(getNegativeButtonTextId())
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.NEXT)
- .setTheme(R.style.SudGlifButton_Primary)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build();
}
return mSecondaryFooterButton;
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java
index d65e057ab26b3bd6c38e7e57fc55f01ecfbc6ca4..155ced519a39bf0f67066df9f62f0788b41600b5 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java
@@ -30,10 +30,10 @@ import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.android.settings.R;
-import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
/** Fingerprint error dialog, will be shown when an error occurs during fingerprint enrollment. */
@@ -95,7 +95,7 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment {
return dialog;
}
- public static void showErrorDialog(BiometricEnrollBase host, int errMsgId, boolean isSetup) {
+ public static void showErrorDialog(FragmentActivity host, int errMsgId, boolean isSetup) {
if (host.isFinishing()) {
return;
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7702207ad176626cd0c81407ee74e9da1191119
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
@@ -0,0 +1,47 @@
+/*
+ * 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.fingerprint;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
+
+public interface FingerprintFeatureProvider {
+ /**
+ * Gets the feature implementation of SFPS enrollment.
+ * @return the feature implementation
+ */
+ SfpsEnrollmentFeature getSfpsEnrollmentFeature();
+
+
+ /**
+ * Gets calibrator for udfps pre-enroll
+ * @param appContext application context
+ * @param activitySavedInstanceState activity savedInstanceState
+ * @param activityIntent activity intent
+ */
+ @Nullable
+ default UdfpsEnrollCalibrator getUdfpsEnrollCalibrator(@NonNull Context appContext,
+ @Nullable Bundle activitySavedInstanceState, @Nullable Intent activityIntent) {
+ return null;
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..9745ca3fd7e53ce970d1fb2e6a373fe969ad2ee1
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
@@ -0,0 +1,36 @@
+/*
+ * 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.fingerprint;
+
+import androidx.annotation.Nullable;
+
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeatureImpl;
+
+public class FingerprintFeatureProviderImpl implements FingerprintFeatureProvider {
+
+ @Nullable
+ private SfpsEnrollmentFeature mSfpsEnrollmentFeatureImpl = null;
+
+ @Override
+ public SfpsEnrollmentFeature getSfpsEnrollmentFeature() {
+ if (mSfpsEnrollmentFeatureImpl == null) {
+ mSfpsEnrollmentFeatureImpl = new SfpsEnrollmentFeatureImpl();
+ }
+ return mSfpsEnrollmentFeatureImpl;
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index 505fe1c1fc4a66562d7ddc2258a2e5ccf7f1e9e8..69043422d0ffa31cec25a1b9d58b7eea383e2305 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -60,7 +60,7 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
@@ -77,6 +77,7 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -84,6 +85,7 @@ 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;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.TwoTargetPreference;
@@ -91,6 +93,7 @@ import com.android.settingslib.widget.TwoTargetPreference;
import com.google.android.setupdesign.util.DeviceHelper;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -146,12 +149,47 @@ public class FingerprintSettings extends SubSettings {
return manager != null && isHardwareDetected;
}
+
/**
*
*/
+ @SearchIndexable
public static class FingerprintSettingsFragment extends DashboardFragment
implements OnPreferenceChangeListener, FingerprintPreference.OnDeleteClickListener {
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.security_settings_fingerprint) {
+ @Override
+ public List
+ createPreferenceControllers(Context context) {
+ return createThePreferenceControllers(context);
+ }
+ };
+
+ private static List createThePreferenceControllers(Context
+ context) {
+ final List controllers = new ArrayList<>();
+ FingerprintManager manager = Utils.getFingerprintManagerOrNull(context);
+ if (manager == null || !manager.isHardwareDetected()) {
+ return null;
+ }
+ if (manager.isPowerbuttonFps()) {
+ controllers.add(
+ new FingerprintUnlockCategoryController(
+ context,
+ KEY_FINGERPRINT_UNLOCK_CATEGORY
+ ));
+ controllers.add(
+ new FingerprintSettingsRequireScreenOnToAuthPreferenceController(
+ context,
+ KEY_REQUIRE_SCREEN_ON_TO_AUTH
+ ));
+ }
+ controllers.add(new FingerprintsEnrolledCategoryPreferenceController(context,
+ KEY_FINGERPRINTS_ENROLLED_CATEGORY));
+ return controllers;
+ }
+
private static class FooterColumn {
CharSequence mTitle = null;
CharSequence mLearnMoreOverrideText = null;
@@ -615,7 +653,7 @@ public class FingerprintSettings extends SubSettings {
mRequireScreenOnToAuthPreferenceController.isChecked());
mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener(
(preference, newValue) -> {
- final boolean isChecked = ((SwitchPreference) preference).isChecked();
+ final boolean isChecked = ((TwoStatePreference) preference).isChecked();
mRequireScreenOnToAuthPreferenceController.setChecked(!isChecked);
return true;
});
@@ -741,7 +779,7 @@ public class FingerprintSettings extends SubSettings {
// 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());
+ getActivity());
if (getActivity().isInMultiWindowMode() && !isActivityEmbedded) {
BiometricsSplitScreenDialog.newInstance(TYPE_FINGERPRINT).show(
getActivity().getSupportFragmentManager(),
@@ -754,7 +792,7 @@ public class FingerprintSettings extends SubSettings {
if (FeatureFlagUtils.isEnabled(getContext(),
FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT)) {
intent.setClassName(SETTINGS_PACKAGE_NAME,
- FingerprintEnrollmentActivity.class.getName());
+ FingerprintEnrollmentActivity.InternalActivity.class.getName());
intent.putExtra(EnrollmentRequest.EXTRA_SKIP_FIND_SENSOR, true);
} else {
intent.setClassName(SETTINGS_PACKAGE_NAME,
@@ -839,6 +877,8 @@ public class FingerprintSettings extends SubSettings {
@Override
protected List createPreferenceControllers(Context context) {
if (!isFingerprintHardwareDetected(context)) {
+ Log.e(TAG, "Fingerprint hardware is not detected");
+ mControllers = Collections.emptyList();
return null;
}
@@ -847,20 +887,20 @@ public class FingerprintSettings extends SubSettings {
}
private List buildPreferenceControllers(Context context) {
- final List controllers = new ArrayList<>();
+ final List controllers =
+ createThePreferenceControllers(context);
if (isSfps()) {
- mFingerprintUnlockCategoryPreferenceController =
- new FingerprintUnlockCategoryController(
- context,
- KEY_FINGERPRINT_UNLOCK_CATEGORY
- );
- mRequireScreenOnToAuthPreferenceController =
- new FingerprintSettingsRequireScreenOnToAuthPreferenceController(
- context,
- KEY_REQUIRE_SCREEN_ON_TO_AUTH
- );
- controllers.add(mFingerprintUnlockCategoryPreferenceController);
- controllers.add(mRequireScreenOnToAuthPreferenceController);
+ for (AbstractPreferenceController controller : controllers) {
+ if (controller.getPreferenceKey() == KEY_FINGERPRINT_UNLOCK_CATEGORY) {
+ mFingerprintUnlockCategoryPreferenceController =
+ (FingerprintUnlockCategoryController) controller;
+ } else if (controller.getPreferenceKey() == KEY_REQUIRE_SCREEN_ON_TO_AUTH) {
+ mRequireScreenOnToAuthPreferenceController =
+ (FingerprintSettingsRequireScreenOnToAuthPreferenceController)
+ controller;
+ }
+
+ }
}
return controllers;
}
@@ -876,8 +916,9 @@ public class FingerprintSettings extends SubSettings {
final Activity activity = getActivity();
if (activity != null) {
// Apply pending transition for auto adding first fingerprint case
- activity.overridePendingTransition(R.anim.sud_slide_next_in,
- R.anim.sud_slide_next_out);
+ activity.overridePendingTransition(
+ com.google.android.setupdesign.R.anim.sud_slide_next_in,
+ com.google.android.setupdesign.R.anim.sud_slide_next_out);
}
// To have smoother animation, change flow to let next visible activity
@@ -923,7 +964,7 @@ public class FingerprintSettings extends SubSettings {
activity.finish();
}
} else if (requestCode == AUTO_ADD_FIRST_FINGERPRINT_REQUEST) {
- if (resultCode != RESULT_FINISHED || data == null) {
+ if (resultCode != RESULT_FINISHED) {
Log.d(TAG, "Add first fingerprint, fail or null data, result:" + resultCode);
if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) {
// If "Fingerprint Unlock" is closed because of timeout, notify result code
@@ -935,14 +976,19 @@ public class FingerprintSettings extends SubSettings {
return;
}
- mToken = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
+ if (mToken == null && data != null) {
+ mToken = data.getByteArrayExtra(
+ ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
+ }
if (mToken == null) {
Log.w(TAG, "Add first fingerprint, null token");
finish();
return;
}
- mChallenge = data.getLongExtra(EXTRA_KEY_CHALLENGE, -1L);
+ if (mChallenge == -1L && data != null) {
+ mChallenge = data.getLongExtra(EXTRA_KEY_CHALLENGE, -1L);
+ }
if (mChallenge == -1L) {
Log.w(TAG, "Add first fingerprint, invalid challenge");
finish();
@@ -1028,7 +1074,7 @@ public class FingerprintSettings extends SubSettings {
intent.setClassName(SETTINGS_PACKAGE_NAME,
FeatureFlagUtils.isEnabled(getActivity(),
FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT)
- ? FingerprintEnrollmentActivity.class.getName()
+ ? FingerprintEnrollmentActivity.InternalActivity.class.getName()
: FingerprintEnrollIntroductionInternal.class.getName()
);
@@ -1356,12 +1402,14 @@ public class FingerprintSettings extends SubSettings {
super.onBindViewHolder(view);
mView = view.itemView;
mDeleteView = view.itemView.findViewById(R.id.delete_button);
- mDeleteView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mOnDeleteClickListener != null) {
- mOnDeleteClickListener.onDeleteClick(FingerprintPreference.this);
- }
+ if (mFingerprint != null) {
+ mDeleteView.setContentDescription(
+ mDeleteView.getContentDescription()
+ + " " + mFingerprint.getName().toString());
+ }
+ mDeleteView.setOnClickListener(v -> {
+ if (mOnDeleteClickListener != null) {
+ mOnDeleteClickListener.onDeleteClick(FingerprintPreference.this);
}
});
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java
index 87396dd91d52ce3a822b362af9f083e5501371cc..61ec54b1b46acde18a826b413027d0a6686be777 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java
@@ -25,11 +25,14 @@ import androidx.preference.Preference;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.Utils;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
/**
* Preference controller that controls whether a SFPS device is required to be interactive for
* fingerprint authentication to unlock the device.
*/
+@SearchIndexable
public class FingerprintSettingsRequireScreenOnToAuthPreferenceController
extends FingerprintSettingsPreferenceController {
private static final String TAG =
@@ -104,4 +107,10 @@ public class FingerprintSettingsRequireScreenOnToAuthPreferenceController
return UserHandle.of(getUserId()).getIdentifier();
}
+ /**
+ * This feature is not directly searchable.
+ */
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {};
+
}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c54c6b5eba5a5b594ce4b7f199efbae80432659a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
@@ -0,0 +1,18 @@
+package com.android.settings.biometrics.fingerprint
+
+import android.os.Bundle
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.Lifecycle
+
+interface UdfpsEnrollCalibrator {
+
+ fun getExtrasForNextIntent(isEnrolling: Boolean): Bundle
+
+ fun onSaveInstanceState(outState: Bundle)
+
+ fun onFindSensorPage(
+ lifecycle: Lifecycle,
+ fragmentManager: FragmentManager,
+ enableEnrollingRunnable: Runnable
+ )
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
index df2f2f77d3b5a9e36a2bf6981670875bdefde423..d17fa24fede148368991552a2df83ab460f64d36 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
@@ -37,8 +37,8 @@ import androidx.annotation.ColorInt;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
-import com.android.settingslib.udfps.UdfpsOverlayParams;
-import com.android.settingslib.udfps.UdfpsUtils;
+import com.android.systemui.biometrics.UdfpsUtils;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupdesign.GlifLayout;
@@ -78,7 +78,7 @@ public class UdfpsEnrollEnrollingView extends GlifLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mHeaderView = findViewById(R.id.sud_landscape_header_area);
+ mHeaderView = findViewById(com.google.android.setupdesign.R.id.sud_landscape_header_area);
mUdfpsEnrollView = findViewById(R.id.udfps_animation_view);
}
@@ -201,8 +201,10 @@ public class UdfpsEnrollEnrollingView extends GlifLayout {
return false;
};
- findManagedViewById(mIsLandscape ? R.id.sud_landscape_content_area
- : R.id.sud_layout_content).setOnHoverListener(onHoverListener);
+ findManagedViewById(mIsLandscape
+ ? com.google.android.setupdesign.R.id.sud_landscape_content_area
+ : com.google.android.setupdesign.R.id.sud_layout_content
+ ).setOnHoverListener(onHoverListener);
}
private void swapHeaderAndContent() {
@@ -212,7 +214,8 @@ public class UdfpsEnrollEnrollingView extends GlifLayout {
parentView.addView(mHeaderView);
// Hide scroll indicators
- BottomScrollView headerScrollView = mHeaderView.findViewById(R.id.sud_header_scroll_view);
+ BottomScrollView headerScrollView = mHeaderView.findViewById(
+ com.google.android.setupdesign.R.id.sud_header_scroll_view);
headerScrollView.setScrollIndicators(0);
}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
index 70fdbf08bc2eb56273b0d115d7543cca1144b6e3..d3bc97764de4a68a2d3ecd2af8fe2e0e6d851a9e 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
@@ -76,6 +76,8 @@ public class UdfpsEnrollHelper extends InstrumentedFragment {
private int mCenterTouchCount = 0;
+ private int mPace = 1;
+
@Nullable
UdfpsEnrollHelper.Listener mListener;
@@ -157,6 +159,9 @@ public class UdfpsEnrollHelper extends InstrumentedFragment {
}
}
+ if (mRemainingSteps > remaining) {
+ mPace = mRemainingSteps - remaining;
+ }
mRemainingSteps = remaining;
if (mListener != null && mTotalSteps != -1) {
@@ -177,7 +182,7 @@ public class UdfpsEnrollHelper extends InstrumentedFragment {
* Called when a fingerprint image has been acquired, but wasn't processed yet.
*/
public void onAcquired(boolean isAcquiredGood) {
- if (mListener != null && mTotalSteps != -1) {
+ if (mListener != null) {
mListener.onAcquired(isAcquiredGood && animateIfLastStep());
}
}
@@ -258,7 +263,7 @@ public class UdfpsEnrollHelper extends InstrumentedFragment {
return false;
}
- return mRemainingSteps <= 2 && mRemainingSteps >= 0;
+ return mRemainingSteps <= mPace && mRemainingSteps >= 0;
}
private int getStageThresholdSteps(int totalSteps, int stageIndex) {
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java
index 5ded91ec68c2344b358c037285b0a6c3a949d63c..2d392ffbe6debe1b1476c0ad442c5116855d4cf9 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java
@@ -33,7 +33,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
-import com.android.settingslib.udfps.UdfpsOverlayParams;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
/**
* View corresponding with udfps_enroll_view.xml
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1a18e5652e479ce88fcdcda9d01807fc7730fe1
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
@@ -0,0 +1,79 @@
+/*
+ * 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.fingerprint.feature;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public interface SfpsEnrollmentFeature {
+
+ /**
+ * Gets current SFPS enrollment stage.
+ * @param progressSteps current step of enrollment
+ * @param mapper a mapper to map each stage to its threshold
+ * @return current enrollment stage
+ */
+ int getCurrentSfpsEnrollStage(int progressSteps, Function mapper);
+
+ /**
+ * Gets the vendor string by feature.
+ * @param context Context
+ * @param id An integer identifying the error message
+ * @param msg A human-readable string that can be shown in UI
+ * @return A human-readable string of specific feature
+ */
+ default CharSequence getFeaturedVendorString(Context context, int id, CharSequence msg) {
+ return msg;
+ }
+
+ /**
+ * Gets the stage header string by feature.
+ * @param stage the specific stage
+ * @return the resource id of the header text of the specific stage
+ */
+ int getFeaturedStageHeaderResource(int stage);
+
+ /**
+ * Gets the enrollment lottie resource id per stage
+ * @param stage current enrollment stage
+ * @return enrollment lottie resource id
+ */
+ int getSfpsEnrollLottiePerStage(int stage);
+
+ /**
+ * Handles extra stuffs on receiving enrollment help.
+ * @param helpMsgId help message id
+ * @param helpString help message
+ * @param enrollingSupplier supplier of enrolling context
+ */
+ default void handleOnEnrollmentHelp(int helpMsgId, CharSequence helpString,
+ Supplier enrollingSupplier) {}
+
+ /**
+ * Gets the fingerprint enrollment threshold.
+ * @param context context
+ * @param index the enrollment stage index
+ * @return threshold
+ */
+ float getEnrollStageThreshold(@NonNull Context context, int index);
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a975373c0cab70edb0434668e4222f417373ed1
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
@@ -0,0 +1,91 @@
+/*
+ * 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.fingerprint.feature;
+
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_CENTER;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_FINGERTIP;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_LEFT_EDGE;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_NO_ANIMATION;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_RIGHT_EDGE;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.STAGE_UNKNOWN;
+
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+
+import java.util.function.Function;
+
+public class SfpsEnrollmentFeatureImpl implements SfpsEnrollmentFeature {
+
+ @Nullable
+ private FingerprintManager mFingerprintManager = null;
+
+ @Override
+ public int getCurrentSfpsEnrollStage(int progressSteps, Function mapper) {
+ if (mapper == null) {
+ return STAGE_UNKNOWN;
+ }
+ if (progressSteps < mapper.apply(0)) {
+ return SFPS_STAGE_NO_ANIMATION;
+ } else if (progressSteps < mapper.apply(1)) {
+ return SFPS_STAGE_CENTER;
+ } else if (progressSteps < mapper.apply(2)) {
+ return SFPS_STAGE_FINGERTIP;
+ } else if (progressSteps < mapper.apply(3)) {
+ return SFPS_STAGE_LEFT_EDGE;
+ } else {
+ return SFPS_STAGE_RIGHT_EDGE;
+ }
+ }
+
+ @Override
+ public int getFeaturedStageHeaderResource(int stage) {
+ return switch (stage) {
+ case SFPS_STAGE_NO_ANIMATION
+ -> R.string.security_settings_fingerprint_enroll_repeat_title;
+ case SFPS_STAGE_CENTER -> R.string.security_settings_sfps_enroll_finger_center_title;
+ case SFPS_STAGE_FINGERTIP -> R.string.security_settings_sfps_enroll_fingertip_title;
+ case SFPS_STAGE_LEFT_EDGE -> R.string.security_settings_sfps_enroll_left_edge_title;
+ case SFPS_STAGE_RIGHT_EDGE -> R.string.security_settings_sfps_enroll_right_edge_title;
+ default -> throw new IllegalArgumentException("Invalid stage: " + stage);
+ };
+ }
+
+ @Override
+ public int getSfpsEnrollLottiePerStage(int stage) {
+ return switch (stage) {
+ case SFPS_STAGE_NO_ANIMATION -> R.raw.sfps_lottie_no_animation;
+ case SFPS_STAGE_CENTER -> R.raw.sfps_lottie_pad_center;
+ case SFPS_STAGE_FINGERTIP -> R.raw.sfps_lottie_tip;
+ case SFPS_STAGE_LEFT_EDGE -> R.raw.sfps_lottie_left_edge;
+ case SFPS_STAGE_RIGHT_EDGE -> R.raw.sfps_lottie_right_edge;
+ default -> throw new IllegalArgumentException("Invalid stage: " + stage);
+ };
+ }
+
+ @Override
+ public float getEnrollStageThreshold(@NonNull Context context, int index) {
+ if (mFingerprintManager == null) {
+ mFingerprintManager = context.getSystemService(FingerprintManager.class);
+ }
+ return mFingerprintManager.getEnrollStageThreshold(index);
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/OWNERS b/src/com/android/settings/biometrics/fingerprint2/OWNERS
new file mode 100644
index 0000000000000000000000000000000000000000..c58a06d5fa02353cd30ed0cbe7c20d783f7ea2e4
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/OWNERS
@@ -0,0 +1,3 @@
+# Owners for Biometric Fingerprint
+joshmccloskey@google.com
+jbolinger@google.com
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/README.md b/src/com/android/settings/biometrics/fingerprint2/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4a99a2bb9f614bd540c20cac719e781ef7a2c226
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/README.md
@@ -0,0 +1,10 @@
+### Fingerprint Settings Enrollment
+
+#### Flows
+
+* FingerprintSettings (ui.settings.fragment.FingerprintSettingsV2Fragment)
+* FingerprintEnrollment (ui.enrollment.activity.FingerprintEnrollmentV2Activity)
+
+#### Style
+
+* Please use [kfmt](https://plugins.jetbrains.com/plugin/14912-ktfmt)
\ 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
new file mode 100644
index 0000000000000000000000000000000000000000..58ef50978344113a29e8b75459baaf5e8b1dfe55
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.conversion
+
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED
+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
+
+object Util {
+ fun EnrollReason.toOriginalReason(): Int {
+ return when (this) {
+ EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
+ EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
+ }
+ }
+
+ fun Int.toEnrollError(isSetupWizard: Boolean): FingerEnrollState.EnrollError {
+ val errTitle =
+ when (this) {
+ FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
+ R.string.security_settings_fingerprint_enroll_error_dialog_title
+ FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
+ R.string.security_settings_fingerprint_bad_calibration_title
+ else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
+ }
+ val errString =
+ if (isSetupWizard) {
+ when (this) {
+ FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
+ R.string.security_settings_fingerprint_enroll_error_dialog_title
+ FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
+ R.string.security_settings_fingerprint_bad_calibration_title
+ else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
+ }
+ } else {
+ when (this) {
+ // This message happens when the underlying crypto layer
+ // decides to revoke the enrollment auth token
+ FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
+ R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message
+ FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
+ R.string.security_settings_fingerprint_bad_calibration
+ FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS ->
+ R.string.security_settings_fingerprint_enroll_error_unable_to_process_message
+ // There's nothing specific to tell the user about. Ask them to try again.
+ else -> R.string.security_settings_fingerprint_enroll_error_generic_dialog_message
+ }
+ }
+
+ return FingerEnrollState.EnrollError(
+ errTitle,
+ errString,
+ this == FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+ this == FINGERPRINT_ERROR_CANCELED,
+ )
+ }
+
+}
+
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
similarity index 50%
rename from src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
rename to src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
index 2fbdedfc7e4f3a5f914691911f141ecd93b16404..984d04cb44ea46b330290ae5bf41aaacd6e7725a 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
@@ -21,73 +21,45 @@ import android.content.Intent
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.CancellationSignal
import android.util.Log
import com.android.settings.biometrics.GatekeeperPasswordProvider
-import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+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.password.ChooseLockSettingsHelper
+import com.android.systemui.biometrics.shared.model.toFingerprintSensor
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.onFailure
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
private const val TAG = "FingerprintManagerInteractor"
-/** Encapsulates business logic related to managing fingerprints. */
-interface FingerprintManagerInteractor {
- /** Returns the list of current fingerprints. */
- val enrolledFingerprints: Flow>
-
- /** Returns the max enrollable fingerprints, note during SUW this might be 1 */
- val maxEnrollableFingerprints: Flow
-
- /** Runs [FingerprintManager.authenticate] */
- suspend fun authenticate(): FingerprintAuthAttemptViewModel
-
- /**
- * Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
- * challenge and challenge token. This info can be used for secure operations such as
- * [FingerprintManager.enroll]
- *
- * @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm
- * @return A [Pair] of the challenge and challenge token
- */
- suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair
-
- /** Returns true if a user can enroll a fingerprint false otherwise. */
- fun canEnrollFingerprints(numFingerprints: Int): Flow
-
- /**
- * Removes the given fingerprint, returning true if it was successfully removed and false
- * otherwise
- */
- suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean
-
- /** Renames the given fingerprint if one exists */
- suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String)
-
- /** 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
-
- /** Retrieves the sensor properties of a device */
- suspend fun sensorPropertiesInternal(): List
-}
-
class FingerprintManagerInteractorImpl(
applicationContext: Context,
private val backgroundDispatcher: CoroutineDispatcher,
private val fingerprintManager: FingerprintManager,
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
- private val pressToAuthProvider: () -> Boolean,
+ private val pressToAuthProvider: PressToAuthProvider,
+ private val fingerprintFlow: FingerprintFlow,
) : FingerprintManagerInteractor {
private val maxFingerprints =
@@ -96,6 +68,8 @@ class FingerprintManagerInteractorImpl(
)
private val applicationContext = applicationContext.applicationContext
+ private val enrollRequestOutstanding = MutableStateFlow(false)
+
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair =
suspendCoroutine {
val callback = GenerateChallengeCallback { _, userId, challenge ->
@@ -111,22 +85,101 @@ class FingerprintManagerInteractorImpl(
fingerprintManager.generateChallenge(applicationContext.userId, callback)
}
- override val enrolledFingerprints: Flow> = flow {
+ override val enrolledFingerprints: Flow> = flow {
emit(
fingerprintManager
.getEnrolledFingerprints(applicationContext.userId)
- .map { (FingerprintViewModel(it.name.toString(), it.biometricId, it.deviceId)) }
+ .map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
.toList()
)
}
- override fun canEnrollFingerprints(numFingerprints: Int): Flow = flow {
- emit(numFingerprints < maxFingerprints)
+ override val canEnrollFingerprints: Flow = flow {
+ emit(
+ fingerprintManager.getEnrolledFingerprints(applicationContext.userId).size < maxFingerprints
+ )
+ }
+
+ override val sensorPropertiesInternal = flow {
+ val sensorPropertiesInternal = fingerprintManager.sensorPropertiesInternal
+ emit(
+ if (sensorPropertiesInternal.isEmpty()) null
+ else sensorPropertiesInternal.first().toFingerprintSensor()
+ )
}
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
- override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine {
+ override suspend fun enroll(
+ hardwareAuthToken: ByteArray?,
+ enrollReason: EnrollReason,
+ ): Flow = callbackFlow {
+ // TODO (b/308456120) Improve this logic
+ if (enrollRequestOutstanding.value) {
+ Log.d(TAG, "Outstanding enroll request, waiting 150ms")
+ delay(150)
+ if (enrollRequestOutstanding.value) {
+ Log.e(TAG, "Request still present, continuing")
+ }
+ }
+
+ enrollRequestOutstanding.update { true }
+
+ var streamEnded = false
+ var totalSteps: Int? = null
+ val enrollmentCallback =
+ object : FingerprintManager.EnrollmentCallback() {
+ override fun onEnrollmentProgress(remaining: Int) {
+ // This is sort of an implementation detail, but unfortunately the API isn't
+ // very expressive. If anything we should look at changing the FingerprintManager API.
+ if (totalSteps == null) {
+ totalSteps = remaining + 1
+ }
+
+ trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure {
+ error ->
+ Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
+ }
+
+ if (remaining == 0) {
+ streamEnded = true
+ enrollRequestOutstanding.update { false }
+ }
+ }
+
+ 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") }
+ }
+
+ override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
+ 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 }
+ }
+ }
+
+ val cancellationSignal = CancellationSignal()
+ fingerprintManager.enroll(
+ hardwareAuthToken,
+ cancellationSignal,
+ applicationContext.userId,
+ enrollmentCallback,
+ enrollReason.toOriginalReason(),
+ )
+ awaitClose {
+ // If the stream has not been ended, and the user has stopped collecting the flow
+ // before it was over, send cancel.
+ if (!streamEnded) {
+ Log.e(TAG, "Cancel is sent from settings for enroll()")
+ cancellationSignal.cancel()
+ }
+ }
+ }
+
+ override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
val callback =
object : RemovalCallback() {
override fun onRemovalError(
@@ -151,7 +204,7 @@ class FingerprintManagerInteractorImpl(
)
}
- override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+ override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
withContext(backgroundDispatcher) {
fingerprintManager.rename(fp.fingerId, applicationContext.userId, newName)
}
@@ -162,16 +215,11 @@ class FingerprintManagerInteractorImpl(
}
override suspend fun pressToAuthEnabled(): Boolean = suspendCancellableCoroutine {
- it.resume(pressToAuthProvider())
+ it.resume(pressToAuthProvider.isEnabled)
}
- override suspend fun sensorPropertiesInternal(): List =
- suspendCancellableCoroutine {
- it.resume(fingerprintManager.sensorPropertiesInternal)
- }
-
- override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
- suspendCancellableCoroutine { c: CancellableContinuation ->
+ override suspend fun authenticate(): FingerprintAuthAttemptModel =
+ suspendCancellableCoroutine { c: CancellableContinuation ->
val authenticationCallback =
object : FingerprintManager.AuthenticationCallback() {
@@ -181,7 +229,7 @@ class FingerprintManagerInteractorImpl(
Log.d(TAG, "framework sent down onAuthError after finish")
return
}
- c.resume(FingerprintAuthAttemptViewModel.Error(errorCode, errString.toString()))
+ c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
@@ -190,7 +238,7 @@ class FingerprintManagerInteractorImpl(
Log.d(TAG, "framework sent down onAuthError after finish")
return
}
- c.resume(FingerprintAuthAttemptViewModel.Success(result.fingerprint?.biometricId ?: -1))
+ c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt b/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt
new file mode 100644
index 0000000000000000000000000000000000000000..38c5335ab0e390561bc87d942470da1607bec6d6
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt
@@ -0,0 +1,51 @@
+/*
+ * 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/shared/Android.bp b/src/com/android/settings/biometrics/fingerprint2/shared/Android.bp
new file mode 100644
index 0000000000000000000000000000000000000000..145f3d682ea586eb17f9a6641dace49aeb201957
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/Android.bp
@@ -0,0 +1,14 @@
+// This library is mainly used for fakes which will be shared across are various types of tests
+// unit/robo/screenshot etc.
+//
+// This library shouldn't have many dependencies.
+android_library {
+ name: "FingerprintManagerInteractor",
+ srcs: [
+ "**/*.kt"
+ ],
+ static_libs: [
+ "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/shared/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e2c97fc1fac1e4b9fa52fc71ae2095d37e171bbd
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/AndroidManifest.xml
@@ -0,0 +1,18 @@
+
+
+
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt b/src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e776b9ab3e3f9aff04dd8a52b07c000c4843503f
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.shared.data.repository
+
+/**
+ * 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
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..94afa49da99a1daf4444c9ff33679ca5b9b21e5f
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.shared.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.systemui.biometrics.shared.model.FingerprintSensor
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interface to obtain the necessary data for FingerprintEnrollment/Settings
+ *
+ * Note that this interface should not have dependencies on heavyweight libraries such as the
+ * framework, hidl/aidl, etc. This makes it much easier to test and create fakes for.
+ */
+interface FingerprintManagerInteractor {
+ /** Returns the list of current fingerprints. */
+ val enrolledFingerprints: Flow>
+
+ /** Returns the max enrollable fingerprints, note during SUW this might be 1 */
+ val maxEnrollableFingerprints: Flow
+
+ /** Returns true if a user can enroll a fingerprint false otherwise. */
+ val canEnrollFingerprints: Flow
+
+ /** Retrieves the sensor properties of a device */
+ val sensorPropertiesInternal: Flow
+
+ /** Runs the authenticate flow */
+ suspend fun authenticate(): FingerprintAuthAttemptModel
+
+ /**
+ * Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
+ * challenge and challenge token. This info can be used for secure operations such as enrollment
+ *
+ * @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm
+ * @return A [Pair] of the challenge and challenge token
+ */
+ suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair
+
+ /**
+ * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
+ * enrollment. Returning the [FingerEnrollState] that represents this fingerprint
+ * enrollment state.
+ */
+ suspend fun enroll(
+ hardwareAuthToken: ByteArray?,
+ enrollReason: EnrollReason,
+ ): Flow
+
+ /**
+ * Removes the given fingerprint, returning true if it was successfully removed and false
+ * otherwise
+ */
+ suspend fun removeFingerprint(fp: FingerprintData): Boolean
+
+ /** Renames the given fingerprint if one exists */
+ suspend fun renameFingerprint(fp: FingerprintData, newName: String)
+
+ /** 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/shared/model/EnrollReason.kt
new file mode 100644
index 0000000000000000000000000000000000000000..47a0af0de9749020d839b5542d5af2639d4bb0bc
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReason.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.shared.model
+
+/** The reason for enrollment */
+enum class EnrollReason {
+ /**
+ * The enroll happens on education screen. This is to support legacy flows where we require the
+ * user to touch the sensor before going ahead to the EnrollEnrolling flow
+ */
+ FindSensor,
+ /** The enroll happens on enrolling screen. */
+ EnrollEnrolling
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4766d599814c0ff7101c4826d1e9f8dd231d975d
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.shared.model
+
+import android.annotation.StringRes
+
+/**
+ * Represents a fingerprint enrollment state. See [FingerprintManager.EnrollmentCallback] for more
+ * information
+ */
+sealed class FingerEnrollState {
+ /**
+ * Represents an enrollment step progress.
+ *
+ * Progress is obtained by (totalStepsRequired - remainingSteps) / totalStepsRequired
+ */
+ 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()
+
+ /** Represents that an unrecoverable error has been encountered and the operation is complete. */
+ data class EnrollError(
+ @StringRes val errTitle: Int,
+ @StringRes val errString: Int,
+ val shouldRetryEnrollment: Boolean,
+ val isCancelled: Boolean,
+ ) : FingerEnrollState()
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintData.kt
similarity index 61%
rename from src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintData.kt
index 1df0e34f060f2094a7c306ef202430080ab88c76..b2aa25c56a89b85a4fa1f50b46242aa028aa9f6f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintData.kt
@@ -14,30 +14,21 @@
* limitations under the License.
*/
-package com.android.settings.biometrics.fingerprint2.ui.viewmodel
+package com.android.settings.biometrics.fingerprint2.shared.model
-/** Represents the fingerprint data nad the relevant state. */
-data class FingerprintStateViewModel(
- val fingerprintViewModels: List,
- val canEnroll: Boolean,
- val maxFingerprints: Int,
- val hasSideFps: Boolean,
- val pressToAuth: Boolean,
-)
-
-data class FingerprintViewModel(
+data class FingerprintData(
val name: String,
val fingerId: Int,
val deviceId: Long,
)
-sealed class FingerprintAuthAttemptViewModel {
+sealed class FingerprintAuthAttemptModel {
data class Success(
val fingerId: Int,
- ) : FingerprintAuthAttemptViewModel()
+ ) : FingerprintAuthAttemptModel()
data class Error(
val error: Int,
val message: String,
- ) : FingerprintAuthAttemptViewModel()
+ ) : FingerprintAuthAttemptModel()
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt
new file mode 100644
index 0000000000000000000000000000000000000000..93c75770d63d36fd4bad01409f5d03afa1c37f8f
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.shared.model
+
+/**
+ * The [FingerprintFlow] for fingerprint enrollment indicates information on how the flow should behave.
+ */
+sealed class FingerprintFlow
+
+/** The default enrollment experience, typically called from Settings */
+data object Default : FingerprintFlow()
+
+/** SetupWizard/Out of box experience (OOBE) enrollment type. */
+data object SetupWizard : FingerprintFlow()
+
+/** Unicorn enrollment type */
+data object Unicorn : FingerprintFlow()
+
+/** Flow to specify settings type */
+data object Settings : FingerprintFlow()
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/README.md b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8469f59222a5d7cc0ed75f9c0ac1acf91bb9973f
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/README.md
@@ -0,0 +1,23 @@
+### Fingerprint Settings Enrollment
+
+#### Entry Points (To FingerprintEnrollment)
+
+* FingerprintSettings (which launches the below intent)
+* Intent -> ".biometrics.fingerprint2.ui.enrollment.activity.FingerprintEnrollmentV2Activity")
+
+#### General Architecture
+
+The code should follow the MVVM architecture.
+
+**In addition, one activity (FingerprintEnrollmentV2Activity) should**
+
+* Control a list of fragments which correspond to enrollment steps
+* Be responsible for navigation events between fragments
+* Be responsible for navigation events to other activities if need be (
+ ConfirmDeviceCredentialActivity)
+* Be the controller of the viewmodels
+
+#### Style
+
+* Please use [kfmt](https://plugins.jetbrains.com/plugin/14912-ktfmt)
+
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
new file mode 100644
index 0000000000000000000000000000000000000000..de2a1eef3c123adcd25ce8b61f62cf718df2d4f5
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
@@ -0,0 +1,343 @@
+/*
+ * 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.activity
+
+import android.app.Activity
+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
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import com.android.internal.widget.LockPatternUtils
+import com.android.settings.R
+import com.android.settings.SetupWizardUtils
+import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
+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.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.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.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.FingerprintEnrollViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
+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.password.ChooseLockGeneric
+import com.android.settings.password.ChooseLockSettingsHelper
+import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
+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
+
+private const val TAG = "FingerprintEnrollmentV2Activity"
+
+/**
+ * This is the activity that controls the entire Fingerprint Enrollment experience through its
+ * children fragments.
+ */
+class FingerprintEnrollmentV2Activity : FragmentActivity() {
+ private lateinit var fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel
+ private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
+ 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 fingerprintScrollViewModel: FingerprintScrollViewModel
+ private lateinit var backgroundViewModel: BackgroundViewModel
+ private val coroutineDispatcher = Dispatchers.Default
+
+ /** Result listener for ChooseLock activity flow. */
+ private val confirmDeviceResultListener =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ val resultCode = result.resultCode
+ val data = result.data
+ onConfirmDevice(resultCode, data)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == CONFIRM_REQUEST) {
+ onConfirmDevice(resultCode, data)
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ if (!isChangingConfigurations) {
+ backgroundViewModel.wentToBackground()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ backgroundViewModel.inForeground()
+ }
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ foldStateViewModel.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 {
+ gatekeeperViewModel.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.fingerprint_v2_enroll_main)
+
+ setTheme(SetupWizardUtils.getTheme(applicationContext, intent))
+ ThemeHelper.trySetDynamicColor(applicationContext)
+
+ val backgroundDispatcher = Dispatchers.IO
+
+ val context = applicationContext
+ val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager
+ val isAnySuw = WizardManagerHelper.isAnySetupWizard(intent)
+ val enrollType =
+ if (isAnySuw) {
+ SetupWizard
+ } else {
+ Default
+ }
+
+ backgroundViewModel =
+ ViewModelProvider(this, BackgroundViewModel.BackgroundViewModelFactory())[
+ BackgroundViewModel::class.java]
+
+
+ val interactor =
+ FingerprintManagerInteractorImpl(
+ context,
+ backgroundDispatcher,
+ fingerprintManager,
+ GatekeeperPasswordProvider(LockPatternUtils(context)),
+ PressToAuthProviderImpl(context),
+ enrollType,
+ )
+
+ 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)
+
+ gatekeeperViewModel =
+ ViewModelProvider(
+ this,
+ FingerprintGatekeeperViewModel.FingerprintGatekeeperViewModelFactory(
+ gatekeeperInfo,
+ interactor,
+ )
+ )[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)
+
+ // Initialize FingerprintViewModel
+ fingerprintEnrollViewModel =
+ ViewModelProvider(
+ this,
+ FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
+ interactor,
+ gatekeeperViewModel,
+ navigationViewModel,
+ )
+ )[FingerprintEnrollViewModel::class.java]
+
+ // Initialize scroll view model
+ fingerprintScrollViewModel =
+ 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
+ )
+ )[FingerprintEnrollEnrollingViewModel::class.java]
+
+ // Initialize FingerprintEnrollFindSensorViewModel
+ ViewModelProvider(
+ this,
+ FingerprintEnrollFindSensorViewModel.FingerprintEnrollFindSensorViewModelFactory(
+ navigationViewModel,
+ fingerprintEnrollViewModel,
+ gatekeeperViewModel,
+ backgroundViewModel,
+ accessibilityViewModel,
+ foldStateViewModel,
+ orientationStateViewModel
+ )
+ )[FingerprintEnrollFindSensorViewModel::class.java]
+
+ // Initialize RFPS View Model
+ ViewModelProvider(
+ this,
+ RFPSViewModel.RFPSViewModelFactory(fingerprintEnrollEnrollingViewModel)
+ )[RFPSViewModel::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
+ is Enrollment -> {
+ when (sensorType) {
+ FingerprintSensorType.REAR -> RFPSEnrollFragment::class.java as Class
+ else -> FingerprintEnrollEnrollingV2Fragment::class.java as Class
+ }
+ }
+ Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class
+ else -> null
+ }
+
+ if (theClass != null) {
+ supportFragmentManager.fragments.onEach { fragment ->
+ supportFragmentManager.beginTransaction().remove(fragment).commit()
+ }
+
+ supportFragmentManager
+ .beginTransaction()
+ .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)
+ }
+ }
+ }
+ }
+
+ val fromSettingsSummary =
+ intent.getBooleanExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, false)
+ if (
+ fromSettingsSummary && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)
+ ) {
+ overridePendingTransition(
+ com.google.android.setupdesign.R.anim.sud_slide_next_in,
+ com.google.android.setupdesign.R.anim.sud_slide_next_out
+ )
+ }
+ }
+
+ private fun launchConfirmOrChooseLock(userId: Int) {
+ val activity = this
+ lifecycleScope.launch(coroutineDispatcher) {
+ val intent = Intent()
+ val builder = ChooseLockSettingsHelper.Builder(activity)
+ val launched =
+ builder
+ .setRequestCode(CONFIRM_REQUEST)
+ .setTitle(getString(R.string.security_settings_fingerprint_preference_title))
+ .setRequestGatekeeperPasswordHandle(true)
+ .setUserId(userId)
+ .setForegroundOnly(true)
+ .setReturnCredentials(true)
+ .show()
+ if (!launched) {
+ intent.setClassName(SETTINGS_PACKAGE_NAME, ChooseLockGeneric::class.java.name)
+ intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true)
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true)
+ intent.putExtra(Intent.EXTRA_USER_ID, userId)
+ confirmDeviceResultListener.launch(intent)
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..b12491f3afd924079f41104a29a23d6b6470e093
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollConfirmationV2Fragment.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.fragment
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
+
+/**
+ * A fragment to indicate that fingerprint enrollment has been completed.
+ *
+ * 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() {
+
+ 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/FingerprintEnrollEnrollingV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0140d57a2e127d720cd086505d8f1add4f2899a5
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.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
new file mode 100644
index 0000000000000000000000000000000000000000..bfd426435a3682696af79ef33c69efe0c6c0c307
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
@@ -0,0 +1,211 @@
+/*
+ * 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.fragment
+
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import com.airbnb.lottie.LottieAnimationView
+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
+import com.google.android.setupdesign.GlifLayout
+import kotlinx.coroutines.launch
+
+private const val TAG = "FingerprintEnrollFindSensorV2Fragment"
+
+/**
+ * A fragment that is used to educate the user about the fingerprint sensor on this device.
+ *
+ * If the sensor is not a udfps sensor, this fragment listens to fingerprint enrollment for
+ * proceeding to the enroll enrolling.
+ *
+ * The main goals of this page are
+ * 1. Inform the user where the fingerprint sensor is on their device
+ * 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
+ * will work.
+ */
+class FingerprintEnrollFindSensorV2Fragment : Fragment() {
+ // 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]
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+
+ val sensorType =
+ ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java].sensorTypeCached
+
+ contentLayoutId =
+ when (sensorType) {
+ FingerprintSensorType.UDFPS_OPTICAL,
+ FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
+ FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
+ 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) } }
+
+ // 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))
+ }
+ }
+ 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.showErrorDialog.collect { (errMsgId, isSetup) ->
+ // TODO: Covert error dialog kotlin as well
+ FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup)
+ }
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ animation?.stopAnimation()
+ super.onDestroy()
+ }
+
+ private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
+ 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()
+ }
+ }
+ .setButtonType(FooterButton.ButtonType.SKIP)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
+ .build()
+ }
+
+ private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) {
+ footerBarMixin.primaryButton =
+ FooterButton.Builder(requireActivity())
+ .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
+ .setListener {
+ run {
+ Log.d(TAG, "onStartButtonClick")
+ viewModel.proceedToEnrolling()
+ }
+ }
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+ .build()
+ }
+
+ private fun setupLottie(
+ view: View,
+ lottieAnimation: Int,
+ lottieClickListener: View.OnClickListener? = null
+ ) {
+ val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
+ illustrationLottie?.setAnimation(lottieAnimation)
+ illustrationLottie?.playAnimation()
+ illustrationLottie?.setOnClickListener(lottieClickListener)
+ illustrationLottie?.visibility = View.VISIBLE
+ }
+
+ private fun setTexts(sensorType: FingerprintSensorType, view: GlifLayout) {
+ when (sensorType) {
+ FingerprintSensorType.UDFPS_OPTICAL,
+ FingerprintSensorType.UDFPS_ULTRASONIC -> {
+ view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title)
+ view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message)
+ }
+ FingerprintSensorType.POWER_BUTTON -> {
+ view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
+ view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message)
+ }
+ else -> {
+ view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title)
+ view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message)
+ }
+ }
+ }
+
+ private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int {
+ val animation: Int
+ when (rotation) {
+ Surface.ROTATION_90 ->
+ animation =
+ (if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_left
+ else R.raw.fingerprint_edu_lottie_portrait_top_left)
+ Surface.ROTATION_180 ->
+ animation =
+ (if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_left
+ else R.raw.fingerprint_edu_lottie_landscape_bottom_left)
+ Surface.ROTATION_270 ->
+ animation =
+ (if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_right
+ else R.raw.fingerprint_edu_lottie_portrait_bottom_right)
+ else ->
+ animation =
+ (if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_right
+ else R.raw.fingerprint_edu_lottie_landscape_top_right)
+ }
+ return animation
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..32d201d8c3d0d67e74a0b5a65c4d3a9213171687
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
@@ -0,0 +1,315 @@
+/*
+ * 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.fragment
+
+import android.annotation.NonNull
+import android.annotation.StringRes
+import android.graphics.Color
+import android.graphics.PorterDuff
+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
+import android.widget.ImageView
+import android.widget.ScrollView
+import android.widget.TextView
+import androidx.annotation.VisibleForTesting
+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.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.google.android.setupcompat.template.FooterBarMixin
+import com.google.android.setupcompat.template.FooterButton
+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.launch
+
+private const val TAG = "FingerprintEnrollmentIntroV2Fragment"
+
+/** This class represents the customizable text for FingerprintEnrollIntroduction. */
+private data class TextModel(
+ @StringRes val footerMessageTwo: Int,
+ @StringRes val footerMessageThree: Int,
+ @StringRes val footerMessageFour: Int,
+ @StringRes val footerMessageFive: Int,
+ @StringRes val footerMessageSix: Int,
+ @StringRes val negativeButton: Int,
+ @StringRes val footerTitleOne: Int,
+ @StringRes val footerTitleTwo: Int,
+ @StringRes val headerText: Int,
+ @StringRes val descriptionText: Int,
+)
+
+/**
+ * The introduction fragment that is used to inform the user the basics of what a fingerprint sensor
+ * is and how it will be used.
+ *
+ * The main gaols of this page are
+ * 1. Inform the user what the fingerprint sensor is and does
+ * 2. How the data will be stored
+ * 3. How the user can access and remove their data
+ */
+class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enroll_introduction) {
+
+ /** 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 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 fingerprintScrollViewModel: FingerprintScrollViewModel by lazy {
+ viewModelProvider[FingerprintScrollViewModel::class.java]
+ }
+ private val gateKeeperViewModel: FingerprintGatekeeperViewModel by lazy {
+ viewModelProvider[FingerprintGatekeeperViewModel::class.java]
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ 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)
+ }
+ .collect { (enrollType, sensorType) ->
+ textModel =
+ when (enrollType) {
+ Unicorn -> getUnicornTextModel()
+ else -> getNormalTextModel()
+ }
+
+ setupFooterBarAndScrollView(view)
+
+ val layout = view as GlifLayout
+
+ layout.setHeaderText(textModel.headerText)
+ layout.setDescriptionText(textModel.descriptionText)
+
+ // Set color filter for the following icons.
+ val colorFilter = getIconColorFilter()
+ listOf(
+ R.id.icon_fingerprint,
+ R.id.icon_device_locked,
+ R.id.icon_trash_can,
+ R.id.icon_info,
+ R.id.icon_shield,
+ R.id.icon_link
+ )
+ .forEach { icon ->
+ view.requireViewById(icon).drawable.colorFilter = colorFilter
+ }
+
+ // Set the text for the footer text views.
+ listOf(
+ R.id.footer_message_2 to textModel.footerMessageTwo,
+ R.id.footer_message_3 to textModel.footerMessageThree,
+ R.id.footer_message_4 to textModel.footerMessageFour,
+ R.id.footer_message_5 to textModel.footerMessageFive,
+ R.id.footer_message_6 to textModel.footerMessageSix,
+ )
+ .forEach { pair -> view.requireViewById(pair.first).setText(pair.second) }
+
+ setFooterLink(view)
+
+ val iconShield: ImageView = view.requireViewById(R.id.icon_shield)
+ val footerMessage6: TextView = view.requireViewById(R.id.footer_message_6)
+ when (sensorType) {
+ FingerprintSensorType.UDFPS_ULTRASONIC,
+ FingerprintSensorType.UDFPS_OPTICAL -> {
+ footerMessage6.visibility = View.VISIBLE
+ iconShield.visibility = View.VISIBLE
+ }
+ else -> {
+ footerMessage6.visibility = View.GONE
+ iconShield.visibility = View.GONE
+ }
+ }
+
+ view.requireViewById(R.id.footer_title_1).setText(textModel.footerTitleOne)
+ view.requireViewById(R.id.footer_title_2).setText(textModel.footerTitleOne)
+ }
+ }
+ return view
+ }
+
+ /**
+ * 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
+ )
+ }
+
+ 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()
+ }
+
+ val layout: GlifLayout = view.findViewById(R.id.setup_wizard_layout)!!
+ footerBarMixin = layout.getMixin(FooterBarMixin::class.java)
+ footerBarMixin.primaryButton =
+ FooterButton.Builder(requireContext())
+ .setText(R.string.security_settings_face_enroll_introduction_more)
+ .setListener(onNextButtonClick)
+ .setButtonType(FooterButton.ButtonType.OPT_IN)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+ .build()
+
+ footerBarMixin.setSecondaryButton(
+ FooterButton.Builder(requireContext())
+ .setText(textModel.negativeButton)
+ .setListener({ Log.d(TAG, "prevClicked") })
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+ .build(),
+ true /* usePrimaryStyle */
+ )
+
+ val primaryButton = footerBarMixin.primaryButton
+ val secondaryButton = footerBarMixin.secondaryButton
+
+ secondaryButton.visibility = View.INVISIBLE
+
+ val requireScrollMixin = layout.getMixin(RequireScrollMixin::class.java)
+ requireScrollMixin.requireScrollWithButton(
+ requireContext(),
+ primaryButton,
+ R.string.security_settings_face_enroll_introduction_more,
+ onNextButtonClick
+ )
+
+ requireScrollMixin.setOnRequireScrollStateChangedListener { scrollNeeded: Boolean ->
+ // Show secondary button once scroll is completed.
+ if (!scrollNeeded) {
+ fingerprintScrollViewModel.userConsented()
+ }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ fingerprintScrollViewModel.hasReadConsentScreen.collect { consented ->
+ if (consented) {
+ primaryButton.setText(
+ requireContext(),
+ R.string.security_settings_fingerprint_enroll_introduction_agree
+ )
+ secondaryButton.visibility = View.VISIBLE
+ } else {
+ secondaryButton.visibility = View.INVISIBLE
+ }
+ }
+ }
+
+ footerBarMixin.getButtonContainer()?.setBackgroundColor(Color.TRANSPARENT)
+
+ // I think I should remove this, and make the challenge a pre-requisite of launching
+ // the flow. For instance if someone launches the activity with an invalid challenge, it
+ // either 1) Fails or 2) Launched confirmDeviceCredential
+ primaryButton.isEnabled = false
+ viewLifecycleOwner.lifecycleScope.launch {
+ gateKeeperViewModel.hasValidGatekeeperInfo.collect { primaryButton.isEnabled = it }
+ }
+ }
+
+ private fun getNormalTextModel() =
+ TextModel(
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2,
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3,
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4,
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5,
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_6,
+ R.string.security_settings_fingerprint_enroll_introduction_no_thanks,
+ R.string.security_settings_fingerprint_enroll_introduction_footer_title_1,
+ R.string.security_settings_fingerprint_enroll_introduction_footer_title_2,
+ R.string.security_settings_fingerprint_enroll_introduction_title,
+ R.string.security_settings_fingerprint_enroll_introduction_v3_message,
+ )
+
+ private fun getUnicornTextModel() =
+ TextModel(
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_2,
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_3,
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_4,
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_5,
+ R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_6,
+ R.string.security_settings_fingerprint_enroll_introduction_no_thanks,
+ R.string.security_settings_fingerprint_enroll_introduction_footer_title_consent_1,
+ R.string.security_settings_fingerprint_enroll_introduction_footer_title_2,
+ R.string.security_settings_fingerprint_enroll_consent_introduction_title,
+ R.string.security_settings_fingerprint_enroll_introduction_v3_message,
+ )
+
+ @NonNull
+ private fun getIconColorFilter(): PorterDuffColorFilter {
+ return PorterDuffColorFilter(
+ DynamicColorPalette.getColor(context, DynamicColorPalette.ColorType.ACCENT),
+ PorterDuff.Mode.SRC_IN
+ )
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/README.md b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..dfb959820006b5561839415100a7e1b5ac4841ea
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/README.md
@@ -0,0 +1,26 @@
+# Module enrollment
+
+### Fingerprint Settings Enrollment Modules
+
+This directory is responsible for containing the enrollment modules, each enrollment module is
+responsible for the actual enrolling portion of FingerprintEnrollment.
+The modules should be split out into udfps, rfps, and sfps.
+
+[comment]: <> This file structure print out has been generated with the tree command.
+
+```
+├── enrolling
+│ └── rfps
+│ ├── data
+│ ├── domain
+│ │ └── RFPSInteractor.kt
+│ ├── README.md
+│ └── ui
+│ ├── fragment
+│ │ └── RFPSEnrollFragment.kt
+│ ├── viewmodel
+│ │ └── RFPSViewModel.kt
+│ └── widget
+│ └── RFPSProgressIndicator.kt
+└── README.md
+```
\ No newline at end of file
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
new file mode 100644
index 0000000000000000000000000000000000000000..d8c2f5a2a03707d057fc8fa8385e2bc95fd67110
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.modules.enrolling.rfps.ui.fragment
+
+import android.graphics.Color
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.AnimationUtils
+import android.view.animation.Interpolator
+import android.widget.TextView
+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.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.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.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) {
+
+ private lateinit var linearOutSlowInInterpolator: Interpolator
+ private lateinit var fastOutLinearInInterpolator: Interpolator
+ private lateinit var textView: TextView
+ private lateinit var progressBar: RFPSProgressBar
+
+ private val iconTouchViewModel: RFPSIconTouchViewModel by lazy {
+ ViewModelProvider(requireActivity())[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 backgroundViewModel: BackgroundViewModel by lazy {
+ ViewModelProvider(requireActivity())[BackgroundViewModel::class.java]
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = super.onCreateView(inflater, container, savedInstanceState)!!
+ val fragment = this
+ val context = requireContext()
+ val glifLayout = view.requireViewById(R.id.setup_wizard_layout) as GlifLayout
+ glifLayout.setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message)
+ glifLayout.setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title)
+
+ fastOutLinearInInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in)
+ linearOutSlowInInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in)
+
+ textView = view.requireViewById(R.id.text) as TextView
+ progressBar = view.requireViewById(R.id.fingerprint_progress_bar) as RFPSProgressBar
+
+ val footerBarMixin = glifLayout.getMixin(FooterBarMixin::class.java)
+ footerBarMixin.secondaryButton =
+ FooterButton.Builder(context)
+ .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
+ .setListener { Log.e(TAG, "skip enrollment!") }
+ .setButtonType(FooterButton.ButtonType.SKIP)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
+ .build()
+ footerBarMixin.buttonContainer.setBackgroundColor(Color.TRANSPARENT)
+
+ progressBar.setOnTouchListener { _, motionEvent ->
+ if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
+ iconTouchViewModel.userTouchedFingerprintIcon()
+ }
+ true
+ }
+
+ // On any orientation event, dismiss dialogs.
+ viewLifecycleOwner.lifecycleScope.launch {
+ orientationViewModel.orientation.collect { dismissDialogs() }
+ }
+
+ // Signal we are ready for enrollment.
+ rfpsViewModel.readyForEnrollment()
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ // Icon animation update
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.shouldAnimateIcon.collect { animate ->
+ progressBar.updateIconAnimation(animate)
+ }
+ }
+
+ // Flow to show a dialog.
+ viewLifecycleOwner.lifecycleScope.launch {
+ iconTouchViewModel.shouldShowDialog.collectLatest { showDialog ->
+ if (showDialog) {
+ try {
+ IconTouchDialog.showInstance(fragment)
+ } catch (exception: Exception) {
+ Log.d(TAG, "Dialog dismissed due to $exception")
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // If we go to the background, then finish enrollment. This should be permanent finish,
+ // and shouldn't be reset until we explicitly tell the view model we want to retry
+ // enrollment.
+ viewLifecycleOwner.lifecycleScope.launch {
+ backgroundViewModel.background
+ .filter { inBackground -> inBackground }
+ .collect { rfpsViewModel.stopEnrollment() }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.progress.filterNotNull().collect { progress -> handleEnrollProgress(progress) }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.helpMessage.filterNotNull().collect { help ->
+ textView.text = help.helpString
+ textView.visibility = View.VISIBLE
+ textView.translationY =
+ resources.getDimensionPixelSize(R.dimen.fingerprint_error_text_appear_distance).toFloat()
+ textView.alpha = 0f
+ textView
+ .animate()
+ .alpha(1f)
+ .translationY(0f)
+ .setDuration(200)
+ .setInterpolator(linearOutSlowInInterpolator)
+ .start()
+
+ }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.errorMessage.filterNotNull().collect { error -> handleEnrollError(error) }
+ }
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.textViewIsVisible.collect {
+ textView.visibility = if (it) View.VISIBLE else View.INVISIBLE
+ }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ rfpsViewModel.clearHelpMessage.collect {
+ textView
+ .animate()
+ .alpha(0f)
+ .translationY(
+ resources
+ .getDimensionPixelSize(R.dimen.fingerprint_error_text_disappear_distance)
+ .toFloat()
+ )
+ .setDuration(100)
+ .setInterpolator(fastOutLinearInInterpolator)
+ .withEndAction { rfpsViewModel.setVisibility(false) }
+ .start()
+ }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.DESTROYED) {
+ rfpsViewModel.stopEnrollment()
+ dismissDialogs()
+ }
+ }
+ return view
+ }
+
+ private fun handleEnrollError(error: FingerEnrollState.EnrollError) {
+ val fragment = this
+ viewLifecycleOwner.lifecycleScope.launch {
+ try {
+ val shouldRestartEnrollment = FingerprintErrorDialog.showInstance(error, fragment)
+ } 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) {
+ if (frag is InstrumentedDialogFragment) {
+ Log.d(TAG, "removing dialog settings fragment $frag")
+ frag.dismiss()
+ transaction.remove(frag)
+ }
+ }
+ transaction.commitAllowingStateLoss()
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..c16e65cc9f2182fc7e631f40b1fcbb710179ef8f
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.modules.enrolling.rfps.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.flow.update
+
+private const val touchesToShowDialog = 3
+/**
+ * This class is responsible for counting the number of touches on the fingerprint icon, and if this
+ * number reaches a threshold it will produce an action via [shouldShowDialog] to indicate the ui
+ * should show a dialog.
+ */
+class RFPSIconTouchViewModel : ViewModel() {
+
+ /** Keeps the number of times a user has touches the fingerprint icon. */
+ 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.
+ */
+ val shouldShowDialog: Flow =
+ _touches
+ .transform { numTouches -> emit((numTouches % touchesToShowDialog) == 0) }
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 0)
+
+ /** Indicates a user has tapped on the fingerprint icon. */
+ fun userTouchedFingerprintIcon() {
+ _touches.update { _touches.value + 1 }
+ }
+
+ class RFPSIconTouchViewModelFactory : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return RFPSIconTouchViewModel() as T
+ }
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..58d604e4407521d782126fa7bbe1cba75cfcf8a0
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.modules.enrolling.rfps.ui.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.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
+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.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.flow.update
+
+/** View Model used by the rear fingerprint enrollment fragment. */
+class RFPSViewModel(
+ private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
+) : ViewModel() {
+
+ /** Value to indicate if the text view is visible or not **/
+ private val _textViewIsVisible = MutableStateFlow(false)
+ val textViewIsVisible: Flow = _textViewIsVisible.asStateFlow()
+
+ /** Indicates if the icon should be animating or not */
+ val shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning
+
+ private val enrollFlow: Flow = fingerprintEnrollViewModel.enrollFLow
+
+ /**
+ * Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
+ * recent state (this is useful for things like screen rotation)
+ */
+ val progress: Flow =
+ enrollFlow
+ .filterIsInstance()
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 1)
+
+ /** Clear help message on enroll progress */
+ val clearHelpMessage: Flow = progress.map { it != null }
+
+ /** Enroll help message that is only displayed once */
+ val helpMessage: Flow =
+ enrollFlow
+ .filterIsInstance()
+ .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
+ * to re-show the error message.
+ */
+ val errorMessage: Flow =
+ enrollFlow
+ .filterIsInstance()
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 0)
+
+ /** Indicates if the consumer is ready for enrollment */
+ fun readyForEnrollment() {
+ fingerprintEnrollViewModel.canEnroll()
+ }
+
+ /** Indicates if enrollment should stop */
+ fun stopEnrollment() {
+ fingerprintEnrollViewModel.stopEnroll()
+ }
+
+ fun setVisibility(isVisible: Boolean) {
+ _textViewIsVisible.update { isVisible }
+ }
+
+ class RFPSViewModelFactory(
+ private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
+ ) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(
+ modelClass: Class,
+ ): T {
+ return RFPSViewModel(fingerprintEnrollEnrollingViewModel) as T
+ }
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..b9c628ed41264c7130a128890c9d7138d8a92222
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.modules.enrolling.rfps.ui.widget
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.app.settings.SettingsEnums
+import android.content.DialogInterface
+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.core.instrumentation.InstrumentedDialogFragment
+import kotlin.coroutines.resume
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "FingerprintErrorDialog"
+
+/** A Dialog used for fingerprint enrollment when an error occurs. */
+class FingerprintErrorDialog : InstrumentedDialogFragment() {
+ private lateinit var onContinue: DialogInterface.OnClickListener
+ private lateinit var onTryAgain: DialogInterface.OnClickListener
+ private lateinit var onCancelListener: DialogInterface.OnCancelListener
+
+ override fun onCancel(dialog: DialogInterface) {
+ Log.d(TAG, "onCancel $dialog")
+ onCancelListener.onCancel(dialog)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ Log.d(TAG, "onCreateDialog $this")
+ val errorString = requireArguments().getInt(KEY_MESSAGE)
+ val errorTitle = requireArguments().getInt(KEY_TITLE)
+ val builder = AlertDialog.Builder(requireContext())
+ val shouldShowTryAgain = requireArguments().getBoolean(KEY_SHOULD_TRY_AGAIN)
+ builder.setTitle(errorTitle).setMessage(errorString).setCancelable(false)
+
+ if (shouldShowTryAgain) {
+ builder
+ .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_try_again) {
+ dialog,
+ which ->
+ dialog.dismiss()
+ onTryAgain.onClick(dialog, which)
+ }
+ .setNegativeButton(R.string.security_settings_fingerprint_enroll_dialog_ok) { dialog, which
+ ->
+ dialog.dismiss()
+ onContinue.onClick(dialog, which)
+ }
+ } else {
+ builder.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) {
+ dialog,
+ which ->
+ dialog.dismiss()
+ onContinue.onClick(dialog, which)
+ }
+ }
+
+ val dialog = builder.create()
+ dialog.setCanceledOnTouchOutside(false)
+ return dialog
+ }
+
+ override fun getMetricsCategory(): Int {
+ return SettingsEnums.DIALOG_FINGERPINT_ERROR
+ }
+
+ companion object {
+ private const val KEY_MESSAGE = "fingerprint_message"
+ 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) }
+
+ dialog.onContinue = DialogInterface.OnClickListener { _, _ -> continuation.resume(false) }
+
+ dialog.onCancelListener =
+ DialogInterface.OnCancelListener {
+ Log.d(TAG, "onCancelListener clicked $dialog")
+ continuation.resume(null)
+ }
+
+ 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())
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/IconTouchDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/IconTouchDialog.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c0863432afc48e0b920b020d2d2b86e3f051aa29
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/IconTouchDialog.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.modules.enrolling.rfps.ui.widget
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.app.settings.SettingsEnums
+import android.content.DialogInterface
+import android.os.Bundle
+import android.util.Log
+import androidx.fragment.app.Fragment
+import com.android.settings.R
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import kotlin.coroutines.resume
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "IconTouchDialog"
+
+/** Dialog shown when the user taps the Progress bar a certain amount of times. */
+class IconTouchDialog : InstrumentedDialogFragment() {
+ lateinit var onDismissListener: DialogInterface.OnClickListener
+ lateinit var onCancelListener: DialogInterface.OnCancelListener
+
+ override fun onCancel(dialog: DialogInterface) {
+ Log.d(TAG, "onCancel $dialog")
+ onCancelListener.onCancel(dialog)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val builder: AlertDialog.Builder = AlertDialog.Builder(activity, R.style.Theme_AlertDialog)
+ builder
+ .setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
+ .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
+ .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) { dialog, which ->
+ dialog.dismiss()
+ onDismissListener.onClick(dialog, which)
+ }
+ .setOnCancelListener { onCancelListener.onCancel(it) }
+ return builder.create()
+ }
+
+ override fun getMetricsCategory(): Int {
+ return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH
+ }
+
+ companion object {
+ suspend fun showInstance(fragment: Fragment) = suspendCancellableCoroutine { continuation ->
+ val dialog = IconTouchDialog()
+ dialog.onDismissListener =
+ DialogInterface.OnClickListener { _, _ -> continuation.resume("Done") }
+ dialog.onCancelListener =
+ DialogInterface.OnCancelListener { _ -> continuation.resume("OnCancel") }
+
+ continuation.invokeOnCancellation { Log.d(TAG, "invokeOnCancellation $dialog") }
+
+ dialog.show(fragment.parentFragmentManager, IconTouchDialog::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
new file mode 100644
index 0000000000000000000000000000000000000000..fe6268107d320e1b99d72fec5fba1696ffcbbf36
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.modules.enrolling.rfps.ui.widget
+
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.util.AttributeSet
+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) {
+
+ private val fastOutSlowInInterpolator: Interpolator
+
+ private val iconAnimationDrawable: AnimatedVectorDrawable
+ private val iconBackgroundBlinksDrawable: AnimatedVectorDrawable
+
+ private val maxProgress: Int
+
+ private var progressAnimation: ObjectAnimator? = null
+
+ private var shouldAnimateInternal: Boolean = true
+
+ init {
+ val fingerprintDrawable = background as LayerDrawable
+ iconAnimationDrawable =
+ fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation)
+ as AnimatedVectorDrawable
+ 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?) {
+ super.onAnimationEnd(drawable)
+ if (shouldAnimateInternal) {
+ animateIconAnimationInternal()
+ }
+ }
+ }
+ )
+ animateIconAnimationInternal()
+
+ progressBackgroundTintMode = PorterDuff.Mode.SRC
+
+ val attributes =
+ context.obtainStyledAttributes(R.style.RingProgressBarStyle, intArrayOf(android.R.attr.max))
+
+ maxProgress = attributes.getInt(0, -1)
+
+ attributes.recycle()
+ }
+
+ /** Indicates if the progress animation should be running */
+ fun updateIconAnimation(shouldAnimate: Boolean) {
+ if (shouldAnimate && !shouldAnimateInternal) {
+ animateIconAnimationInternal()
+ }
+
+ 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()
+ iconBackgroundBlinksDrawable.start()
+
+ progressAnimation?.isRunning?.let { progressAnimation!!.cancel() }
+
+ progressAnimation = ObjectAnimator.ofInt(this, "progress", getProgress(), progress)
+
+ progressAnimation?.interpolator = fastOutSlowInInterpolator
+ progressAnimation?.setDuration(250)
+ progressAnimation?.start()
+ }
+
+ private fun animateIconAnimationInternal() {
+ iconAnimationDrawable.start()
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/AccessibilityViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/AccessibilityViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a86ad5d99425c04eee4fb3864e6adb50101bba5c
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/AccessibilityViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.view.accessibility.AccessibilityManager
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+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 accessibility state. */
+class AccessibilityViewModel(accessibilityManager: AccessibilityManager) : ViewModel() {
+ /** A flow that contains whether or not accessibility is enabled */
+ val isAccessibilityEnabled: Flow =
+ callbackFlow {
+ val listener =
+ 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
+ false
+ )
+
+ class AccessibilityViewModelFactory(private val accessibilityManager: AccessibilityManager) :
+ ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun create(
+ modelClass: Class,
+ ): T {
+ return AccessibilityViewModel(accessibilityManager) as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/BackgroundViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/BackgroundViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2b53a530f4b1348ce4bd21b9b0033c7527116d4a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/BackgroundViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * 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 kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+/** A class for determining if the application is in the background or not. */
+class BackgroundViewModel : ViewModel() {
+
+ private val _background = MutableStateFlow(false)
+ /** When true, the application is in background, else false */
+ val background = _background.asStateFlow()
+
+ /** Indicates that the application has been put in the background. */
+ fun wentToBackground() {
+ _background.update { true }
+ }
+
+ /** Indicates that the application has been brought to the foreground. */
+ fun inForeground() {
+ _background.update { false }
+ }
+
+ class BackgroundViewModelFactory : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return BackgroundViewModel() as T
+ }
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..7ab315e7201f341f5368671f295423c244798be7
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
@@ -0,0 +1,85 @@
+/*
+ * 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 kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+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.
+ */
+class FingerprintEnrollEnrollingViewModel(
+ private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+ backgroundViewModel: BackgroundViewModel,
+) : ViewModel() {
+
+ private val _didTryEnrollment = MutableStateFlow(false)
+ private val _userDidEnroll = MutableStateFlow(false)
+ /** Indicates if the enrollment flow should be running. */
+ val enrollFlowShouldBeRunning: Flow =
+ _userDidEnroll.combine(backgroundViewModel.background) { shouldEnroll, isInBackground ->
+ if (isInBackground) {
+ false
+ } else {
+ shouldEnroll
+ }
+ }
+
+ /**
+ * Used to indicate the consumer of the view model is ready for an enrollment. Note that this does
+ * not necessarily try an enrollment.
+ */
+ fun canEnroll() {
+ // Update _consumerShouldEnroll after updating the other values.
+ if (!_didTryEnrollment.value) {
+ _didTryEnrollment.update { true }
+ _userDidEnroll.update { true }
+ }
+ }
+
+ /** Used to indicate to stop the enrollment. */
+ fun stopEnroll() {
+ _userDidEnroll.update { false }
+ }
+
+ /** Collects the enrollment flow based on [enrollFlowShouldBeRunning] */
+ val enrollFLow =
+ enrollFlowShouldBeRunning.transformLatest {
+ if (it) {
+ fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) }
+ }
+ }
+
+ class FingerprintEnrollEnrollingViewModelFactory(
+ private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+ private val backgroundViewModel: BackgroundViewModel
+ ) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ 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
new file mode 100644
index 0000000000000000000000000000000000000000..7722a46fc7c615675800b754785768dc1c10284c
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
@@ -0,0 +1,198 @@
+/*
+ * 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 androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
+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 fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+ private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ backgroundViewModel: BackgroundViewModel,
+ accessibilityViewModel: AccessibilityViewModel,
+ foldStateViewModel: FoldStateViewModel,
+ orientationStateViewModel: OrientationStateViewModel
+) : ViewModel() {
+ /** Represents the stream of sensor type. */
+ val sensorType: Flow =
+ fingerprintEnrollViewModel.sensorType.shareIn(
+ viewModelScope,
+ SharingStarted.WhileSubscribed(),
+ 1
+ )
+ private val _isUdfps: Flow =
+ sensorType.map {
+ it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC
+ }
+ private val _isSfps: Flow = sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
+ private val _isRearSfps: Flow = sensorType.map { it == FingerprintSensorType.REAR }
+
+ /** Represents the stream of showing primary button. */
+ val showPrimaryButton: Flow = _isUdfps.filter { it }
+
+ private val _showSfpsLottie = _isSfps.filter { it }
+ /** Represents the stream of showing sfps lottie and the information Pair(isFolded, rotation). */
+ val sfpsLottieInfo: Flow> =
+ combineTransform(
+ _showSfpsLottie,
+ foldStateViewModel.isFolded,
+ orientationStateViewModel.rotation,
+ ) { _, isFolded, rotation ->
+ emit(Pair(isFolded, rotation))
+ }
+
+ 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) {
+ _,
+ isAccessibilityEnabled ->
+ isAccessibilityEnabled
+ }
+
+ /** Represents the stream of showing rfps animation. */
+ val showRfpsAnimation: Flow = _isRearSfps.filter { it }
+
+ private val _showErrorDialog: MutableStateFlow?> = MutableStateFlow(null)
+ /** Represents the stream of showing error dialog. */
+ val showErrorDialog = _showErrorDialog.filterNotNull()
+
+ private var _didTryEducation = false
+ private var _education: MutableStateFlow = MutableStateFlow(false)
+ /** Indicates if the education flow should be running. */
+ private val educationFlowShouldBeRunning: Flow =
+ _education.combine(backgroundViewModel.background) { shouldRunEducation, isInBackground ->
+ !isInBackground && shouldRunEducation
+ }
+
+ init {
+ // Start or end enroll flow
+ viewModelScope.launch {
+ combine(
+ fingerprintEnrollViewModel.sensorType,
+ gatekeeperViewModel.hasValidGatekeeperInfo,
+ gatekeeperViewModel.gatekeeperInfo,
+ navigationViewModel.navigationViewModel
+ ) { sensorType, hasValidGatekeeperInfo, gatekeeperInfo, navigationViewModel ->
+ val shouldStartEnroll =
+ navigationViewModel.currStep == Education &&
+ sensorType != FingerprintSensorType.UDFPS_OPTICAL &&
+ sensorType != FingerprintSensorType.UDFPS_ULTRASONIC &&
+ hasValidGatekeeperInfo
+ if (shouldStartEnroll) (gatekeeperInfo as GatekeeperInfo.GatekeeperPasswordInfo).token
+ else null
+ }
+ .collect { token ->
+ if (token != null) {
+ canStartEducation()
+ } else {
+ stopEducation()
+ }
+ }
+ }
+
+ // Enroll progress flow
+ viewModelScope.launch {
+ educationFlowShouldBeRunning.collect {
+ // Only collect the flow when we should be running.
+ if (it) {
+ combine(
+ navigationViewModel.fingerprintFlow,
+ fingerprintEnrollViewModel.educationEnrollFlow.filterNotNull(),
+ ) { enrollType, educationFlow ->
+ Pair(enrollType, educationFlow)
+ }
+ .collect { (enrollType, educationFlow) ->
+ 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) }
+ }
+ }
+ is FingerEnrollState.EnrollHelp -> {}
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Indicates if education can begin */
+ private fun canStartEducation() {
+ if (!_didTryEducation) {
+ _didTryEducation = true
+ _education.update { true }
+ }
+ }
+
+ /** Indicates that education has finished */
+ private fun stopEducation() {
+ _education.update { false }
+ }
+
+ /** Proceed to EnrollEnrolling page. */
+ fun proceedToEnrolling() {
+ navigationViewModel.nextStep()
+ }
+
+ class FingerprintEnrollFindSensorViewModelFactory(
+ private val navigationViewModel: FingerprintEnrollNavigationViewModel,
+ private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+ private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ private val backgroundViewModel: BackgroundViewModel,
+ private val accessibilityViewModel: AccessibilityViewModel,
+ private val foldStateViewModel: FoldStateViewModel,
+ private val orientationStateViewModel: OrientationStateViewModel
+ ) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return FingerprintEnrollFindSensorViewModel(
+ navigationViewModel,
+ fingerprintEnrollViewModel,
+ gatekeeperViewModel,
+ backgroundViewModel,
+ accessibilityViewModel,
+ foldStateViewModel,
+ orientationStateViewModel
+ )
+ as T
+ }
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..c7a1071c6a65c506f4ea9faeccf39f621c53a73c
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
@@ -0,0 +1,127 @@
+/*
+ * 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 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.systemui.biometrics.shared.model.FingerprintSensorType
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.transformLatest
+
+/** Represents all of the fingerprint information needed for a fingerprint enrollment process. */
+class FingerprintEnrollViewModel(
+ private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+ gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ navigationViewModel: FingerprintEnrollNavigationViewModel,
+) : ViewModel() {
+
+ /**
+ * Cached value of [FingerprintSensorType]
+ *
+ * This is typically used by fragments that change their layout/behavior based on this
+ * information. This value should be set before any fragment is created.
+ */
+ var sensorTypeCached: FingerprintSensorType? = null
+ private var _enrollReason: Flow =
+ navigationViewModel.navigationViewModel.map {
+ when (it.currStep) {
+ is Enrollment -> EnrollReason.EnrollEnrolling
+ is Education -> EnrollReason.FindSensor
+ else -> null
+ }
+ }
+
+ /** Represents the stream of [FingerprintSensorType] */
+ val sensorType: Flow =
+ fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
+
+ /**
+ * A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
+ * an enrollment process
+ *
+ * This flow should be the only flow which calls enroll().
+ */
+ val _enrollFlow: Flow =
+ combine(gatekeeperViewModel.gatekeeperInfo, _enrollReason) { hardwareAuthToken, enrollReason,
+ ->
+ Pair(hardwareAuthToken, enrollReason)
+ }
+ .transformLatest {
+ /** [transformLatest] is used as we want to make sure to cancel previous API call. */
+ (hardwareAuthToken, enrollReason) ->
+ if (hardwareAuthToken is GatekeeperInfo.GatekeeperPasswordInfo && enrollReason != null) {
+ fingerprintManagerInteractor.enroll(hardwareAuthToken.token, enrollReason).collect {
+ emit(it)
+ }
+ }
+ }
+ .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
+
+ /**
+ * This flow will kick off education when
+ * 1) There is an active subscriber to this flow
+ * 2) shouldEnroll is true and we are on the FindSensor step
+ */
+ val educationEnrollFlow: Flow =
+ _enrollReason.filterNotNull().transformLatest { enrollReason ->
+ if (enrollReason == EnrollReason.FindSensor) {
+ _enrollFlow.collect { event -> emit(event) }
+ } else {
+ emit(null)
+ }
+ }
+
+ /**
+ * This flow will kick off enrollment when
+ * 1) There is an active subscriber to this flow
+ * 2) shouldEnroll is true and we are on the EnrollEnrolling step
+ */
+ val enrollFlow: Flow =
+ _enrollReason.filterNotNull().transformLatest { enrollReason ->
+ if (enrollReason == EnrollReason.EnrollEnrolling) {
+ _enrollFlow.collect { event -> emit(event) }
+ } else {
+ emit(null)
+ }
+ }
+
+ class FingerprintEnrollViewModelFactory(
+ val interactor: FingerprintManagerInteractor,
+ val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ val navigationViewModel: FingerprintEnrollNavigationViewModel,
+ ) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ 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
new file mode 100644
index 0000000000000000000000000000000000000000..2e5dce0939ee91ca2beef5c5db7b4f8a2eee77b4
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
@@ -0,0 +1,141 @@
+/*
+ * 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/FingerprintGatekeeperViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..db93d022266f2a7cfffc2b0f10ecc8f976f9e194
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.os.CountDownTimer
+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 kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+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
+}
+
+/**
+ * This class is responsible for maintaining the gatekeeper information including things like
+ * timeouts.
+ *
+ * Please note, that this class can't fully support timeouts of the gatekeeper password handle due
+ * to the fact that a handle may have been generated earlier in the settings enrollment and passed
+ * in as a parameter to this class.
+ */
+class FingerprintGatekeeperViewModel(
+ theGatekeeperInfo: GatekeeperInfo?,
+ private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+) : ViewModel() {
+
+ private var _gatekeeperInfo: MutableStateFlow =
+ MutableStateFlow(theGatekeeperInfo)
+
+ /** The gatekeeper info for fingerprint enrollment. */
+ val gatekeeperInfo: Flow = _gatekeeperInfo.asStateFlow()
+
+ /** Indicates if the gatekeeper info is valid. */
+ val hasValidGatekeeperInfo: Flow =
+ gatekeeperInfo.map { it is GatekeeperInfo.GatekeeperPasswordInfo }
+
+ private var _credentialConfirmed: MutableStateFlow = MutableStateFlow(null)
+ val credentialConfirmed: Flow = _credentialConfirmed.asStateFlow()
+
+ private var countDownTimer: CountDownTimer? = null
+
+ /** Timeout of 15 minutes for a generated challenge */
+ private val TIMEOUT: Long = 15 * 60 * 1000
+
+ /** Called after a confirm device credential attempt has been made. */
+ fun onConfirmDevice(wasSuccessful: Boolean, theGatekeeperPasswordHandle: Long?) {
+ if (!wasSuccessful) {
+ Log.d(TAG, "confirmDevice failed")
+ _gatekeeperInfo.update { GatekeeperInfo.Invalid }
+ _credentialConfirmed.update { false }
+ } else {
+ viewModelScope.launch {
+ val res = fingerprintManagerInteractor.generateChallenge(theGatekeeperPasswordHandle!!)
+ _gatekeeperInfo.update { GatekeeperInfo.GatekeeperPasswordInfo(res.second, res.first) }
+ _credentialConfirmed.update { true }
+ startTimeout()
+ }
+ }
+ }
+
+ private fun startTimeout() {
+ countDownTimer?.cancel()
+ countDownTimer =
+ object : CountDownTimer(TIMEOUT, 1000) {
+ override fun onFinish() {
+ _gatekeeperInfo.update { GatekeeperInfo.Timeout }
+ }
+
+ override fun onTick(millisUntilFinished: Long) {}
+ }
+ }
+
+ companion object {
+ /**
+ * A function that checks if the challenge and token are valid, in which case a
+ * [GatekeeperInfo.GatekeeperPasswordInfo] is provided, else [GatekeeperInfo.Invalid]
+ */
+ fun toGateKeeperInfo(challenge: Long?, token: ByteArray?): GatekeeperInfo {
+ Log.d(TAG, "toGateKeeperInfo(${challenge == null}, ${token == null})")
+ if (challenge == null || token == null) {
+ return GatekeeperInfo.Invalid
+ }
+ 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/FingerprintScrollViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintScrollViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..989d4d32372ada6338a47a093212e77b98164686
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintScrollViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * 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 kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+/** This class is responsible for ensuring a users consent to use FingerprintEnrollment. */
+class FingerprintScrollViewModel : ViewModel() {
+
+ private val _hasReadConsentScreen: MutableStateFlow = MutableStateFlow(false)
+
+ /** Indicates if a user has consented to FingerprintEnrollment */
+ val hasReadConsentScreen: Flow = _hasReadConsentScreen.asStateFlow()
+
+ /** Indicates that a user has consented to FingerprintEnrollment */
+ fun userConsented() {
+ _hasReadConsentScreen.update { true }
+ }
+
+ class FingerprintScrollViewModelFactory : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ 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
new file mode 100644
index 0000000000000000000000000000000000000000..a4c7ff22c57a349d18fa9664cbb95cc9e8cb4b4a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FoldStateViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * 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
new file mode 100644
index 0000000000000000000000000000000000000000..b68f6d63abbcb645d3c37da46de8886a17d319c7
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt
@@ -0,0 +1,104 @@
+/*
+ * 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/enrollment/viewmodel/OrientationStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/OrientationStateViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2e5f7341743574e159d164169cf90d058541b50e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/OrientationStateViewModel.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.view.OrientationEventListener
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.internal.R
+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() {
+
+ /** A flow that contains the orientation info */
+ val orientation: Flow