diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5d4487c42ca..6605be2c8da 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1022,7 +1022,7 @@
Fingerprint setup timed out
- Try again now or set up your fingerprint later in Settings
+ You can set up your fingerprint later in Settings.
Fingerprint enrollment didn\'t work. Try again or use a different finger.
@@ -10265,6 +10265,8 @@
auto, fill, autofill, password
data, passkey, password
+
+ auto, fill, autofill, data, passkey, password
@@ -10277,6 +10279,20 @@
]]>
+
+
+ Use %1$s\?
+
+
+ %1$s uses what\'s on
+ your screen to determine what can be autofilled. New passwords, passkeys and other info will be saved here from now on.
+ ]]>
+
+
+
+ Passwords, passkeys and data services
+
Turn off %1$s\?
diff --git a/res/xml/accounts_dashboard_settings_credman.xml b/res/xml/accounts_dashboard_settings_credman.xml
index 4f278b659b2..83c16a2df30 100644
--- a/res/xml/accounts_dashboard_settings_credman.xml
+++ b/res/xml/accounts_dashboard_settings_credman.xml
@@ -27,10 +27,10 @@
android:title="@string/credman_chosen_app_title">
+ settings:keywords="@string/credman_autofill_keywords">
diff --git a/res/xml/accounts_personal_dashboard_settings_credman.xml b/res/xml/accounts_personal_dashboard_settings_credman.xml
index 891bb7c1d22..b87e866700b 100644
--- a/res/xml/accounts_personal_dashboard_settings_credman.xml
+++ b/res/xml/accounts_personal_dashboard_settings_credman.xml
@@ -28,10 +28,10 @@
android:title="@string/credman_chosen_app_title">
+ settings:keywords="@string/credman_autofill_keywords">
diff --git a/res/xml/accounts_work_dashboard_settings_credman.xml b/res/xml/accounts_work_dashboard_settings_credman.xml
index 0d4b742c366..15299208a4e 100644
--- a/res/xml/accounts_work_dashboard_settings_credman.xml
+++ b/res/xml/accounts_work_dashboard_settings_credman.xml
@@ -28,8 +28,8 @@
android:title="@string/credman_chosen_app_title">
+
+
+
diff --git a/src/com/android/settings/accounts/AccountDashboardFragment.java b/src/com/android/settings/accounts/AccountDashboardFragment.java
index f59de46428d..e4c20313a54 100644
--- a/src/com/android/settings/accounts/AccountDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountDashboardFragment.java
@@ -30,6 +30,8 @@ import android.provider.SearchIndexableResource;
import com.android.settings.R;
import com.android.settings.applications.autofill.PasswordsPreferenceController;
import com.android.settings.applications.credentials.CredentialManagerPreferenceController;
+import com.android.settings.applications.credentials.DefaultCombinedPreferenceController;
+import com.android.settings.applications.credentials.DefaultWorkCombinedPreferenceController;
import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController;
import com.android.settings.applications.defaultapps.DefaultWorkAutofillPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
@@ -76,7 +78,14 @@ public class AccountDashboardFragment extends DashboardFragment {
CredentialManagerPreferenceController cmpp =
use(CredentialManagerPreferenceController.class);
CredentialManagerPreferenceController.Delegate delegate =
- result -> getActivity().setResult(result);
+ new CredentialManagerPreferenceController.Delegate() {
+ public void setActivityResult(int resultCode) {
+ getActivity().setResult(resultCode);
+ }
+ public void forceDelegateRefresh() {
+ forceUpdatePreferences();
+ }
+ };
cmpp.init(this, getFragmentManager(), getIntent(), delegate);
} else {
getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
@@ -99,8 +108,13 @@ public class AccountDashboardFragment extends DashboardFragment {
static void buildAutofillPreferenceControllers(
Context context, List controllers) {
- controllers.add(new DefaultAutofillPreferenceController(context));
- controllers.add(new DefaultWorkAutofillPreferenceController(context));
+ if (CredentialManager.isServiceEnabled(context)) {
+ controllers.add(new DefaultCombinedPreferenceController(context));
+ controllers.add(new DefaultWorkCombinedPreferenceController(context));
+ } else {
+ controllers.add(new DefaultAutofillPreferenceController(context));
+ controllers.add(new DefaultWorkAutofillPreferenceController(context));
+ }
}
private static void buildAccountPreferenceControllers(
diff --git a/src/com/android/settings/accounts/AccountDetailDashboardFragment.java b/src/com/android/settings/accounts/AccountDetailDashboardFragment.java
index 0668c62b758..8e1d627d922 100644
--- a/src/com/android/settings/accounts/AccountDetailDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountDetailDashboardFragment.java
@@ -120,7 +120,7 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
@Override
public int getMetricsCategory() {
- return SettingsEnums.ACCOUNT;
+ return SettingsEnums.ACCOUNT_DETAIL;
}
@Override
diff --git a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java
index a87eb7dd774..32c3c13e4ba 100644
--- a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java
@@ -42,7 +42,7 @@ public class AccountPersonalDashboardFragment extends DashboardFragment {
@Override
public int getMetricsCategory() {
- return SettingsEnums.ACCOUNT;
+ return SettingsEnums.ACCOUNT_PERSONAL;
}
@Override
@@ -70,7 +70,14 @@ public class AccountPersonalDashboardFragment extends DashboardFragment {
CredentialManagerPreferenceController cmpp =
use(CredentialManagerPreferenceController.class);
CredentialManagerPreferenceController.Delegate delegate =
- result -> getActivity().setResult(result);
+ new CredentialManagerPreferenceController.Delegate() {
+ public void setActivityResult(int resultCode) {
+ getActivity().setResult(resultCode);
+ }
+ public void forceDelegateRefresh() {
+ forceUpdatePreferences();
+ }
+ };
cmpp.init(this, getFragmentManager(), getIntent(), delegate);
} else {
getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
diff --git a/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java b/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java
index 445aced18d0..39146c75a7c 100644
--- a/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java
@@ -70,7 +70,14 @@ public class AccountWorkProfileDashboardFragment extends DashboardFragment {
CredentialManagerPreferenceController cmpp =
use(CredentialManagerPreferenceController.class);
CredentialManagerPreferenceController.Delegate delegate =
- result -> getActivity().setResult(result);
+ new CredentialManagerPreferenceController.Delegate() {
+ public void setActivityResult(int resultCode) {
+ getActivity().setResult(resultCode);
+ }
+ public void forceDelegateRefresh() {
+ forceUpdatePreferences();
+ }
+ };
cmpp.init(this, getFragmentManager(), getIntent(), delegate);
} else {
getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
new file mode 100644
index 00000000000..af06a0144b9
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
@@ -0,0 +1,265 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ServiceInfo;
+import android.credentials.CredentialProviderInfo;
+import android.graphics.drawable.Drawable;
+import android.service.autofill.AutofillServiceInfo;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Holds combined autofill and credential manager data grouped by package name. Contains backing
+ * logic for each row in settings.
+ */
+public final class CombinedProviderInfo {
+ private final List mCredentialProviderInfos;
+ private final @Nullable AutofillServiceInfo mAutofillServiceInfo;
+ private final boolean mIsDefaultAutofillProvider;
+ private final boolean mIsDefaultCredmanProvider;
+
+ /** Constructs an information instance from both autofill and credential provider. */
+ public CombinedProviderInfo(
+ @Nullable List cpis,
+ @Nullable AutofillServiceInfo asi,
+ boolean isDefaultAutofillProvider,
+ boolean isDefaultCredmanProvider) {
+ mCredentialProviderInfos = new ArrayList<>(cpis);
+ mAutofillServiceInfo = asi;
+ mIsDefaultAutofillProvider = isDefaultAutofillProvider;
+ mIsDefaultCredmanProvider = isDefaultCredmanProvider;
+ }
+
+ /** Returns the credential provider info. */
+ @Nullable
+ public List getCredentialProviderInfos() {
+ return mCredentialProviderInfos;
+ }
+
+ /** Returns the autofill provider info. */
+ @Nullable
+ public AutofillServiceInfo getAutofillServiceInfo() {
+ return mAutofillServiceInfo;
+ }
+
+ /** Returns the application info. */
+ public @Nullable ApplicationInfo getApplicationInfo() {
+ if (!mCredentialProviderInfos.isEmpty()) {
+ return mCredentialProviderInfos.get(0).getServiceInfo().applicationInfo;
+ }
+ return mAutofillServiceInfo.getServiceInfo().applicationInfo;
+ }
+
+ /** Returns the app icon. */
+ @Nullable
+ public Drawable getAppIcon(@NonNull Context context) {
+ Drawable icon = null;
+ ServiceInfo brandingService = getBrandingService();
+ if (brandingService != null) {
+ icon = brandingService.loadIcon(context.getPackageManager());
+ }
+
+ // If the branding service gave us a icon then use that.
+ if (icon != null) {
+ return icon;
+ }
+
+ // Otherwise fallback to the app label and then the package name.
+ return getApplicationInfo().loadIcon(context.getPackageManager());
+ }
+
+ /** Returns the app name. */
+ @Nullable
+ public CharSequence getAppName(@NonNull Context context) {
+ CharSequence name = "";
+ ServiceInfo brandingService = getBrandingService();
+ if (brandingService != null) {
+ name = brandingService.loadLabel(context.getPackageManager());
+ }
+
+ // If the branding service gave us a name then use that.
+ if (!TextUtils.isEmpty(name)) {
+ return name;
+ }
+
+ // Otherwise fallback to the app label and then the package name.
+ name = getApplicationInfo().loadLabel(context.getPackageManager());
+ if (TextUtils.isEmpty(name)) {
+ name = getApplicationInfo().packageName;
+ }
+ return name;
+ }
+
+ /** Gets the service to use for branding (name, icons). */
+ public @Nullable ServiceInfo getBrandingService() {
+ // If the app has an autofill service then use that.
+ if (mAutofillServiceInfo != null) {
+ return mAutofillServiceInfo.getServiceInfo();
+ }
+
+ // If there are no credman providers then stop here.
+ if (mCredentialProviderInfos.isEmpty()) {
+ return null;
+ }
+
+ // Build a list of credential providers and sort them by component names
+ // alphabetically to ensure we are deterministic when picking the provider.
+ Map flattenedNamesToServices = new HashMap<>();
+ List flattenedNames = new ArrayList<>();
+ for (CredentialProviderInfo cpi : mCredentialProviderInfos) {
+ final String flattenedName = cpi.getComponentName().flattenToString();
+ flattenedNamesToServices.put(flattenedName, cpi.getServiceInfo());
+ flattenedNames.add(flattenedName);
+ }
+
+ Collections.sort(flattenedNames);
+ return flattenedNamesToServices.get(flattenedNames.get(0));
+ }
+
+ /** Returns whether the provider is the default autofill provider. */
+ public boolean isDefaultAutofillProvider() {
+ return mIsDefaultAutofillProvider;
+ }
+
+ /** Returns whether the provider is the default credman provider. */
+ public boolean isDefaultCredmanProvider() {
+ return mIsDefaultCredmanProvider;
+ }
+
+ /** Returns the settings subtitle. */
+ @Nullable
+ public String getSettingsSubtitle() {
+ List subtitles = new ArrayList<>();
+ for (CredentialProviderInfo cpi : mCredentialProviderInfos) {
+ // Convert from a CharSequence.
+ String subtitle = String.valueOf(cpi.getSettingsSubtitle());
+ if (subtitle != null && !TextUtils.isEmpty(subtitle) && !subtitle.equals("null")) {
+ subtitles.add(subtitle);
+ }
+ }
+
+ if (subtitles.size() == 0) {
+ return "";
+ }
+
+ return String.join(", ", subtitles);
+ }
+
+ /** Returns the autofill component name string. */
+ @Nullable
+ public String getAutofillServiceString() {
+ if (mAutofillServiceInfo != null) {
+ return mAutofillServiceInfo.getServiceInfo().getComponentName().flattenToString();
+ }
+ return null;
+ }
+
+ /** Returns the provider that gets the top spot. */
+ public static @Nullable CombinedProviderInfo getTopProvider(
+ List providers) {
+ // If there is an autofill provider then it should be the
+ // top app provider.
+ for (CombinedProviderInfo cpi : providers) {
+ if (cpi.isDefaultAutofillProvider()) {
+ return cpi;
+ }
+ }
+
+ // TODO(280454916): Add logic here.
+ return null;
+ }
+
+ public static List buildMergedList(
+ List asiList,
+ List cpiList,
+ @Nullable String defaultAutofillProvider) {
+ ComponentName defaultAutofillProviderComponent =
+ (defaultAutofillProvider == null)
+ ? null
+ : ComponentName.unflattenFromString(defaultAutofillProvider);
+
+ // Index the autofill providers by package name.
+ Set packageNames = new HashSet<>();
+ Map> autofillServices = new HashMap<>();
+ for (AutofillServiceInfo asi : asiList) {
+ final String packageName = asi.getServiceInfo().packageName;
+ if (!autofillServices.containsKey(packageName)) {
+ autofillServices.put(packageName, new ArrayList<>());
+ }
+
+ autofillServices.get(packageName).add(asi);
+ packageNames.add(packageName);
+ }
+
+ // Index the credman providers by package name.
+ Map> credmanServices = new HashMap<>();
+ for (CredentialProviderInfo cpi : cpiList) {
+ String packageName = cpi.getServiceInfo().packageName;
+ if (!credmanServices.containsKey(packageName)) {
+ credmanServices.put(packageName, new ArrayList<>());
+ }
+
+ credmanServices.get(packageName).add(cpi);
+ packageNames.add(packageName);
+ }
+
+ // Now go through and build the joint datasets.
+ List cmpi = new ArrayList<>();
+ for (String packageName : packageNames) {
+ List asi = autofillServices.get(packageName);
+ List cpi = credmanServices.get(packageName);
+
+ // If there are multiple autofill services then pick the first one.
+ AutofillServiceInfo selectedAsi = null;
+ if (asi != null && !asi.isEmpty()) {
+ selectedAsi = asi.get(0);
+ }
+
+ // Check if we are the default autofill provider.
+ boolean isDefaultAutofillProvider = false;
+ if (defaultAutofillProviderComponent != null
+ && defaultAutofillProviderComponent.getPackageName().equals(packageName)) {
+ isDefaultAutofillProvider = true;
+ }
+
+ // Check if we have any enabled cred man services.
+ boolean isDefaultCredmanProvider = false;
+ if (!cpi.isEmpty()) {
+ isDefaultCredmanProvider = cpi.get(0).isEnabled();
+ }
+
+ cmpi.add(
+ new CombinedProviderInfo(
+ cpi, selectedAsi, isDefaultAutofillProvider, isDefaultCredmanProvider));
+ }
+
+ return cmpi;
+ }
+}
diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
index f19fb91fccc..069336e7000 100644
--- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
@@ -41,11 +41,9 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.OutcomeReceiver;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
-import com.android.settingslib.utils.ThreadUtils;
-import com.android.internal.content.PackageMonitor;
import android.util.IconDrawableFactory;
import android.util.Log;
@@ -81,6 +79,15 @@ import java.util.concurrent.Executor;
public class CredentialManagerPreferenceController extends BasePreferenceController
implements LifecycleObserver {
public static final String ADD_SERVICE_DEVICE_CONFIG = "credential_manager_service_search_uri";
+
+ /**
+ * In the settings logic we should hide the list of additional credman providers if there is no
+ * provider selected at the top. The current logic relies on checking whether the autofill
+ * provider is set which won't work for cred-man only providers. Therefore when a CM only
+ * provider is set we will set the autofill setting to be this placeholder.
+ */
+ public static final String AUTOFILL_CREDMAN_ONLY_PROVIDER_PLACEHOLDER = "credential-provider";
+
private static final String TAG = "CredentialManagerPreferenceController";
private static final String ALTERNATE_INTENT = "android.settings.SYNC_SETTINGS";
private static final String PRIMARY_INTENT = "android.settings.CREDENTIAL_PROVIDER";
@@ -101,6 +108,8 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
private @Nullable String mFlagOverrideForTest = null;
private @Nullable PreferenceScreen mPreferenceScreen = null;
+ private boolean mVisibility = false;
+
public CredentialManagerPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mPm = context.getPackageManager();
@@ -127,6 +136,23 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
return null;
}
+ @Override
+ public int getAvailabilityStatus() {
+ if (mCredentialManager == null) {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+
+ if (!mVisibility) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ if (mServices.isEmpty()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ return AVAILABLE;
+ }
+
@VisibleForTesting
public boolean isConnected() {
return mCredentialManager != null;
@@ -256,7 +282,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
setAvailableServices(
mCredentialManager.getCredentialProviderServices(
- getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY),
+ getUser(), CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS),
null);
}
@@ -266,12 +292,26 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
if (mPreferenceScreen != null) {
displayPreference(mPreferenceScreen);
}
+
+ if (mDelegate != null) {
+ mDelegate.forceDelegateRefresh();
+ }
+ }
+
+ private void setVisibility(boolean newVisibility) {
+ if (newVisibility == mVisibility) {
+ return;
+ }
+
+ mVisibility = newVisibility;
+ if (mDelegate != null) {
+ mDelegate.forceDelegateRefresh();
+ }
}
@VisibleForTesting
void setAvailableServices(
- List availableServices,
- String flagOverrideForTest) {
+ List availableServices, String flagOverrideForTest) {
mFlagOverrideForTest = flagOverrideForTest;
mServices.clear();
mServices.addAll(availableServices);
@@ -291,11 +331,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
}
}
- @Override
- public int getAvailabilityStatus() {
- return mServices.isEmpty() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
- }
-
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
@@ -305,39 +340,10 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
mPreferenceScreen = screen;
PreferenceGroup group = screen.findPreference(getPreferenceKey());
+ group.removeAll();
+
Context context = screen.getContext();
mPrefs.putAll(buildPreferenceList(context, group));
-
- // Add the "add service" button only when there are no providers.
- if (mPrefs.isEmpty()) {
- String searchUri = getAddServiceUri(context);
- if (!TextUtils.isEmpty(searchUri)) {
- group.addPreference(newAddServicePreference(searchUri, context));
- }
- }
- }
-
- /**
- * Returns the "add service" URI to show the play store. It will first try and use the
- * credential manager specific search URI and if that is null it will fallback to the autofill
- * one.
- */
- public @NonNull String getAddServiceUri(@NonNull Context context) {
- // Check the credential manager gflag for a link.
- String searchUri =
- DeviceConfig.getString(
- DeviceConfig.NAMESPACE_CREDENTIAL,
- ADD_SERVICE_DEVICE_CONFIG,
- mFlagOverrideForTest);
- if (!TextUtils.isEmpty(searchUri)) {
- return searchUri;
- }
-
- // If not fall back on autofill.
- return Settings.Secure.getStringForUser(
- context.getContentResolver(),
- Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
- getUser());
}
/**
@@ -372,52 +378,59 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
@VisibleForTesting
public Map buildPreferenceList(
Context context, PreferenceGroup group) {
- // Group the services by package name.
- Map> groupedInfos = new HashMap<>();
- for (CredentialProviderInfo cpi : mServices) {
- String packageName = cpi.getServiceInfo().packageName;
- if (isProviderHiddenBecauseOfAutofill(packageName)) {
+ // Get the selected autofill provider. If it is the placeholder then replace it with an
+ // empty string.
+ String selectedAutofillProvider =
+ DefaultCombinedPicker.getSelectedAutofillProvider(mContext, getUser());
+ if (TextUtils.equals(
+ selectedAutofillProvider, AUTOFILL_CREDMAN_ONLY_PROVIDER_PLACEHOLDER)) {
+ selectedAutofillProvider = "";
+ }
+
+ // Get the list of combined providers.
+ List providers =
+ CombinedProviderInfo.buildMergedList(
+ AutofillServiceInfo.getAvailableServices(context, getUser()),
+ mServices,
+ selectedAutofillProvider);
+
+ // Get the provider that is displayed at the top. If there is none then hide
+ // everything.
+ CombinedProviderInfo topProvider = CombinedProviderInfo.getTopProvider(providers);
+ if (topProvider == null) {
+ setVisibility(false);
+ return new HashMap<>();
+ }
+
+ Map output = new HashMap<>();
+ for (CombinedProviderInfo combinedInfo : providers) {
+ final String packageName = combinedInfo.getApplicationInfo().packageName;
+
+ // If this provider is displayed at the top then we should not show it.
+ if (topProvider != null
+ && topProvider.getApplicationInfo().packageName.equals(packageName)) {
continue;
}
- if (!groupedInfos.containsKey(packageName)) {
- groupedInfos.put(packageName, new ArrayList<>());
+ // If this is an autofill provider then don't show it here.
+ if (combinedInfo.getCredentialProviderInfos().isEmpty()) {
+ continue;
}
- groupedInfos.get(packageName).add(cpi);
- }
-
- // Build the pref list.
- Map output = new HashMap<>();
- for (String packageName : groupedInfos.keySet()) {
- List infos = groupedInfos.get(packageName);
- CredentialProviderInfo firstInfo = infos.get(0);
- ServiceInfo firstServiceInfo = firstInfo.getServiceInfo();
- CharSequence title = firstInfo.getLabel(context);
- Drawable icon = firstInfo.getServiceIcon(context);
-
- if (infos.size() > 1) {
- // If there is more than one then group them under the package.
- ApplicationInfo appInfo = firstServiceInfo.applicationInfo;
- if (appInfo.nonLocalizedLabel != null) {
- title = appInfo.loadLabel(mPm);
- }
- icon = mIconFactory.getBadgedIcon(appInfo, getUser());
- }
-
- // If there is no title then show the package manager.
- if (TextUtils.isEmpty(title)) {
- title = firstServiceInfo.packageName;
- }
+ Drawable icon = combinedInfo.getAppIcon(context);
+ CharSequence title = combinedInfo.getAppName(context);
// Build the pref and add it to the output & group.
SwitchPreference pref =
addProviderPreference(
- context, title, icon, packageName, firstInfo.getSettingsSubtitle());
+ context, title, icon, packageName, combinedInfo.getSettingsSubtitle());
output.put(packageName, pref);
group.addPreference(pref);
}
+ // Set the visibility if we have services.
+ setVisibility(!output.isEmpty());
+
return output;
}
@@ -583,23 +596,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
return new NewProviderConfirmationDialogFragment(host, packageName, appName);
}
- /** If the provider is also the autofill provider then hide it. */
- @VisibleForTesting
- public boolean isProviderHiddenBecauseOfAutofill(String packageName) {
- final String autofillService = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.AUTOFILL_SERVICE,
- getUser());
- if (autofillService == null || TextUtils.isEmpty(autofillService)) {
- return false;
- }
- if (packageName == null || TextUtils.isEmpty(packageName)) {
- return false;
- }
-
- return autofillService.startsWith(packageName);
- }
-
@VisibleForTesting
void completeEnableProviderDialogBox(
int whichButton, String packageName, boolean setActivityResult) {
@@ -682,27 +678,31 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
/** Called to send messages back to the parent fragment. */
public static interface Delegate {
void setActivityResult(int resultCode);
+
+ void forceDelegateRefresh();
}
/**
- * Monitor coming and going credman services and calls {@link #update()} when necessary
+ * Monitor coming and going credman services and calls {@link #DefaultCombinedPicker} when
+ * necessary
*/
- private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() {
- @Override
- public void onPackageAdded(String packageName, int uid) {
- ThreadUtils.postOnMainThread(() -> update());
- }
+ private final PackageMonitor mSettingsPackageMonitor =
+ new PackageMonitor() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ ThreadUtils.postOnMainThread(() -> updateFromExternal());
+ }
- @Override
- public void onPackageModified(String packageName) {
- ThreadUtils.postOnMainThread(() -> update());
- }
+ @Override
+ public void onPackageModified(String packageName) {
+ ThreadUtils.postOnMainThread(() -> updateFromExternal());
+ }
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- ThreadUtils.postOnMainThread(() -> update());
- }
- };
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ ThreadUtils.postOnMainThread(() -> updateFromExternal());
+ }
+ };
/** Dialog fragment parent class. */
private abstract static class CredentialManagerDialogFragment extends DialogFragment
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
new file mode 100644
index 00000000000..cfaf7a211db
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
@@ -0,0 +1,374 @@
+/*
+ * 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.annotation.Nullable;
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.credentials.CredentialManager;
+import android.credentials.CredentialProviderInfo;
+import android.credentials.SetEnabledProvidersException;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.autofill.AutofillServiceInfo;
+import android.text.Html;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.core.content.ContextCompat;
+import androidx.preference.Preference;
+
+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;
+import java.util.List;
+
+public class DefaultCombinedPicker extends DefaultAppPickerFragment {
+
+ private static final String TAG = "DefaultCombinedPicker";
+
+ public static final String AUTOFILL_SETTING = Settings.Secure.AUTOFILL_SERVICE;
+ public static final String CREDENTIAL_SETTING = Settings.Secure.CREDENTIAL_SERVICE;
+
+ /** Extra set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */
+ public static final String EXTRA_PACKAGE_NAME = "package_name";
+
+ /** Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */
+ private DialogInterface.OnClickListener mCancelListener;
+
+ private CredentialManager mCredentialManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Activity activity = getActivity();
+ if (activity != null && activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME) != null) {
+ mCancelListener =
+ (d, w) -> {
+ activity.setResult(Activity.RESULT_CANCELED);
+ activity.finish();
+ };
+ // If mCancelListener is not null, fragment is started from
+ // ACTION_REQUEST_SET_AUTOFILL_SERVICE and we should always use the calling uid.
+ mUserId = UserHandle.myUserId();
+ }
+
+ mSettingsPackageMonitor.register(activity, activity.getMainLooper(), false);
+ update();
+ }
+
+ @Override
+ protected DefaultAppPickerFragment.ConfirmationDialogFragment newConfirmationDialogFragment(
+ String selectedKey, CharSequence confirmationMessage) {
+ final AutofillPickerConfirmationDialogFragment fragment =
+ new AutofillPickerConfirmationDialogFragment();
+ fragment.init(this, selectedKey, confirmationMessage);
+ return fragment;
+ }
+
+ /**
+ * Custom dialog fragment that has a cancel listener used to propagate the result back to caller
+ * (for the cases where the picker is launched by {@code
+ * android.settings.REQUEST_SET_AUTOFILL_SERVICE}.
+ */
+ public static class AutofillPickerConfirmationDialogFragment
+ extends DefaultAppPickerFragment.ConfirmationDialogFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ final DefaultCombinedPicker target = (DefaultCombinedPicker) getTargetFragment();
+ setCancelListener(target.mCancelListener);
+ super.onCreate(savedInstanceState);
+ }
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.default_credman_picker;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DEFAULT_AUTOFILL_PICKER;
+ }
+
+ @Override
+ protected boolean shouldShowItemNone() {
+ return true;
+ }
+
+ /** Monitor coming and going auto fill services and calls {@link #update()} when necessary */
+ private final PackageMonitor mSettingsPackageMonitor =
+ new PackageMonitor() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ ThreadUtils.postOnMainThread(() -> update());
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ ThreadUtils.postOnMainThread(() -> update());
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ ThreadUtils.postOnMainThread(() -> update());
+ }
+ };
+
+ /** Update the data in this UI. */
+ private void update() {
+ updateCandidates();
+ addAddServicePreference();
+ }
+
+ @Override
+ public void onDestroy() {
+ mSettingsPackageMonitor.unregister();
+ super.onDestroy();
+ }
+
+ /**
+ * Gets the preference that allows to add a new autofill service.
+ *
+ * @return The preference or {@code null} if no service can be added
+ */
+ private Preference newAddServicePreferenceOrNull() {
+ final String searchUri =
+ Settings.Secure.getStringForUser(
+ getActivity().getContentResolver(),
+ Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
+ mUserId);
+ if (TextUtils.isEmpty(searchUri)) {
+ return null;
+ }
+
+ final Intent addNewServiceIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
+ final Context context = getPrefContext();
+ final Preference preference = new Preference(context);
+ preference.setOnPreferenceClickListener(
+ p -> {
+ context.startActivityAsUser(addNewServiceIntent, UserHandle.of(mUserId));
+ return true;
+ });
+ preference.setTitle(R.string.print_menu_item_add_service);
+ preference.setIcon(R.drawable.ic_add_24dp);
+ preference.setOrder(Integer.MAX_VALUE - 1);
+ preference.setPersistent(false);
+ return preference;
+ }
+
+ /**
+ * Add a preference that allows the user to add a service if the market link for that is
+ * configured.
+ */
+ private void addAddServicePreference() {
+ final Preference addNewServicePreference = newAddServicePreferenceOrNull();
+ if (addNewServicePreference != null) {
+ getPreferenceScreen().addPreference(addNewServicePreference);
+ }
+ }
+
+ /**
+ * Get the Credential Manager service if we haven't already got it. We need to get the service
+ * later because if we do it in onCreate it will fail.
+ */
+ private @Nullable CredentialManager getCredentialProviderService() {
+ if (mCredentialManager == null) {
+ mCredentialManager = getContext().getSystemService(CredentialManager.class);
+ }
+ return mCredentialManager;
+ }
+
+ private List getAllProviders() {
+ final Context context = getContext();
+ final List autofillProviders =
+ AutofillServiceInfo.getAvailableServices(context, mUserId);
+
+ final CredentialManager service = getCredentialProviderService();
+ final List credManProviders = new ArrayList<>();
+ if (service != null) {
+ credManProviders.addAll(
+ service.getCredentialProviderServices(
+ mUserId, CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY));
+ }
+
+ final String selectedAutofillProvider = getSelectedAutofillProvider(context, mUserId);
+ return CombinedProviderInfo.buildMergedList(
+ autofillProviders, credManProviders, selectedAutofillProvider);
+ }
+
+ public static String getSelectedAutofillProvider(Context context, int userId) {
+ return Settings.Secure.getStringForUser(
+ context.getContentResolver(), AUTOFILL_SETTING, userId);
+ }
+
+ protected List getCandidates() {
+ final Context context = getContext();
+ final List allProviders = getAllProviders();
+ final List candidates = new ArrayList<>();
+
+ for (CombinedProviderInfo cpi : allProviders) {
+ ServiceInfo brandingService = cpi.getBrandingService();
+ if (brandingService == null) {
+ candidates.add(
+ new DefaultAppInfo(
+ context,
+ mPm,
+ mUserId,
+ cpi.getApplicationInfo(),
+ cpi.getSettingsSubtitle(),
+ true));
+ } else {
+ candidates.add(
+ new DefaultAppInfo(
+ context,
+ mPm,
+ mUserId,
+ brandingService,
+ cpi.getSettingsSubtitle(),
+ true));
+ }
+ }
+
+ return candidates;
+ }
+
+ @Override
+ protected String getDefaultKey() {
+ final CombinedProviderInfo topProvider =
+ CombinedProviderInfo.getTopProvider(getAllProviders());
+ return topProvider == null ? "" : topProvider.getApplicationInfo().packageName;
+ }
+
+ @Override
+ protected CharSequence getConfirmationMessage(CandidateInfo appInfo) {
+ if (appInfo == null) {
+ return null;
+ }
+ final CharSequence appName = appInfo.loadLabel();
+ final String message =
+ getContext()
+ .getString(
+ R.string.credman_autofill_confirmation_message,
+ Html.escapeHtml(appName));
+ return Html.fromHtml(message);
+ }
+
+ @Override
+ protected boolean setDefaultKey(String key) {
+ // Get the list of providers and see if any match the key (package name).
+ final List allProviders = getAllProviders();
+ CombinedProviderInfo matchedProvider = null;
+ for (CombinedProviderInfo cpi : allProviders) {
+ if (cpi.getApplicationInfo().packageName.equals(key)) {
+ matchedProvider = cpi;
+ break;
+ }
+ }
+
+ // If there were none then clear the stored providers.
+ if (matchedProvider == null) {
+ setProviders(null, new ArrayList<>());
+ return true;
+ }
+
+ // Get the component names and save them.
+ final List credManComponents = new ArrayList<>();
+ for (CredentialProviderInfo pi : matchedProvider.getCredentialProviderInfos()) {
+ credManComponents.add(pi.getServiceInfo().getComponentName().flattenToString());
+ }
+
+ String autofillValue = null;
+ if (matchedProvider.getAutofillServiceInfo() != null) {
+ autofillValue =
+ matchedProvider
+ .getAutofillServiceInfo()
+ .getServiceInfo()
+ .getComponentName()
+ .flattenToString();
+ }
+
+ setProviders(autofillValue, credManComponents);
+
+ // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE
+ // intent, and set proper result if so...
+ final Activity activity = getActivity();
+ if (activity != null) {
+ final String packageName = activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME);
+ if (packageName != null) {
+ final int result =
+ key != null && key.startsWith(packageName)
+ ? Activity.RESULT_OK
+ : Activity.RESULT_CANCELED;
+ activity.setResult(result);
+ activity.finish();
+ }
+ }
+
+ // TODO: Notify the rest
+
+ return true;
+ }
+
+ private void setProviders(String autofillProvider, List credManProviders) {
+ if (TextUtils.isEmpty(autofillProvider)) {
+ if (credManProviders.size() > 0) {
+ autofillProvider =
+ CredentialManagerPreferenceController
+ .AUTOFILL_CREDMAN_ONLY_PROVIDER_PLACEHOLDER;
+ }
+ }
+
+ Settings.Secure.putStringForUser(
+ getContext().getContentResolver(), AUTOFILL_SETTING, autofillProvider, mUserId);
+
+ CredentialManager service = getCredentialProviderService();
+ if (service == null) {
+ return;
+ }
+
+ service.setEnabledProviders(
+ credManProviders,
+ mUserId,
+ ContextCompat.getMainExecutor(getContext()),
+ new OutcomeReceiver() {
+ @Override
+ public void onResult(Void result) {
+ Log.i(TAG, "setEnabledProviders success");
+ }
+
+ @Override
+ public void onError(SetEnabledProvidersException e) {
+ Log.e(TAG, "setEnabledProviders error: " + e.toString());
+ }
+ });
+ }
+}
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
new file mode 100644
index 00000000000..ca049bcdd48
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
@@ -0,0 +1,182 @@
+/*
+ * 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.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;
+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 com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
+import com.android.settingslib.applications.DefaultAppInfo;
+
+import java.util.List;
+
+public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController {
+
+ private static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE);
+ private static final String TAG = "DefaultCombinedPreferenceController";
+
+ private final AutofillManager mAutofillManager;
+ private final CredentialManager mCredentialManager;
+
+ public DefaultCombinedPreferenceController(Context context) {
+ super(context);
+
+ mAutofillManager = mContext.getSystemService(AutofillManager.class);
+
+ if (CredentialManager.isServiceEnabled(context)) {
+ mCredentialManager = mContext.getSystemService(CredentialManager.class);
+ } else {
+ mCredentialManager = null;
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mAutofillManager != null
+ && mCredentialManager != null
+ && mAutofillManager.hasAutofillFeature()
+ && mAutofillManager.isAutofillSupported();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "default_credman_autofill_main";
+ }
+
+ @Override
+ protected Intent getSettingIntent(DefaultAppInfo info) {
+ if (info == null) {
+ return null;
+ }
+ final AutofillSettingIntentProvider intentProvider =
+ new AutofillSettingIntentProvider(mContext, mUserId, info.getKey());
+ return intentProvider.getIntent();
+ }
+
+ @Override
+ protected DefaultAppInfo getDefaultAppInfo() {
+ List providers = getAllProviders(mUserId);
+ CombinedProviderInfo topProvider = CombinedProviderInfo.getTopProvider(providers);
+ if (topProvider != null) {
+ ServiceInfo brandingService = topProvider.getBrandingService();
+ if (brandingService == null) {
+ return new DefaultAppInfo(
+ mContext,
+ mPackageManager,
+ mUserId,
+ topProvider.getApplicationInfo(),
+ topProvider.getSettingsSubtitle(),
+ true);
+ } else {
+ return new DefaultAppInfo(
+ mContext,
+ mPackageManager,
+ mUserId,
+ brandingService,
+ topProvider.getSettingsSubtitle(),
+ true);
+ }
+ }
+ return null;
+ }
+
+ private List getAllProviders(int userId) {
+ final List autofillProviders =
+ AutofillServiceInfo.getAvailableServices(mContext, userId);
+ final List credManProviders =
+ mCredentialManager.getCredentialProviderServices(
+ userId, CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
+ final String selectedAutofillProvider =
+ Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ DefaultCombinedPicker.AUTOFILL_SETTING,
+ userId);
+
+ return CombinedProviderInfo.buildMergedList(
+ autofillProviders, credManProviders, selectedAutofillProvider);
+ }
+
+ @Override
+ protected boolean showLabelAsTitle() {
+ return true;
+ }
+
+ @Override
+ protected boolean showAppSummary() {
+ return true;
+ }
+
+ /** Provides Intent to setting activity for the specified autofill service. */
+ static final class AutofillSettingIntentProvider {
+
+ private final String mSelectedKey;
+ private final Context mContext;
+ private final int mUserId;
+
+ public AutofillSettingIntentProvider(Context context, int userId, String key) {
+ mSelectedKey = 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;
+ final String flattenKey =
+ new ComponentName(serviceInfo.packageName, serviceInfo.name)
+ .flattenToString();
+ if (TextUtils.equals(mSelectedKey, flattenKey)) {
+ final String settingsActivity;
+ try {
+ settingsActivity =
+ new AutofillServiceInfo(mContext, serviceInfo)
+ .getSettingsActivity();
+ } catch (SecurityException e) {
+ // Service does not declare the proper permission, ignore it.
+ Log.w(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;
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
new file mode 100644
index 00000000000..0990f3b2f9f
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
@@ -0,0 +1,84 @@
+/*
+ * 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.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.settingslib.applications.DefaultAppInfo;
+
+public class DefaultWorkCombinedPreferenceController extends DefaultCombinedPreferenceController {
+ private final UserHandle mUserHandle;
+
+ public DefaultWorkCombinedPreferenceController(Context context) {
+ super(context);
+ mUserHandle = Utils.getManagedProfile(mUserManager);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ if (mUserHandle == null) {
+ return false;
+ }
+ return super.isAvailable();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "default_autofill_work";
+ }
+
+ @Override
+ protected DefaultAppInfo getDefaultAppInfo() {
+ final String flattenComponent =
+ Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ DefaultCombinedPicker.AUTOFILL_SETTING,
+ mUserHandle.getIdentifier());
+ if (!TextUtils.isEmpty(flattenComponent)) {
+ DefaultAppInfo appInfo =
+ new DefaultAppInfo(
+ mContext,
+ mPackageManager,
+ mUserHandle.getIdentifier(),
+ ComponentName.unflattenFromString(flattenComponent));
+ return appInfo;
+ }
+ return null;
+ }
+
+ @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/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
index d962692e2df..30c79734025 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
@@ -18,6 +18,7 @@ package com.android.settings.applications.defaultapps;
import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -65,16 +66,21 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC
((TwoTargetPreference) preference).setIconSize(ICON_SIZE_MEDIUM);
}
if (!TextUtils.isEmpty(defaultAppLabel)) {
- if (showLabelAsTitle()) {
+ if (showLabelAsTitle() && showAppSummary()) {
+ preference.setTitle(defaultAppLabel);
+ preference.setSummary(getDefaultAppSummary());
+ } else if (showLabelAsTitle()) {
preference.setTitle(defaultAppLabel);
} else {
preference.setSummary(defaultAppLabel);
}
+
preference.setIcon(Utils.getSafeIcon(getDefaultAppIcon()));
} else {
Log.d(TAG, "No default app");
if (showLabelAsTitle()) {
preference.setTitle(R.string.app_list_preference_none);
+ preference.setSummary(null);
} else {
preference.setSummary(R.string.app_list_preference_none);
}
@@ -89,8 +95,7 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC
}
final Intent settingIntent = getSettingIntent(app);
if (settingIntent != null) {
- ((GearPreference) preference).setOnGearClickListener(
- p -> startActivity(settingIntent));
+ ((GearPreference) preference).setOnGearClickListener(p -> startActivity(settingIntent));
} else {
((GearPreference) preference).setOnGearClickListener(null);
}
@@ -102,21 +107,22 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC
protected abstract DefaultAppInfo getDefaultAppInfo();
- /**
- * Returns an optional intent that will be launched when clicking "gear" icon.
- */
+ /** Returns an optional intent that will be launched when clicking "gear" icon. */
protected Intent getSettingIntent(DefaultAppInfo info) {
- //By default return null. It's up to subclasses to provide logic.
+ // By default return null. It's up to subclasses to provide logic.
return null;
}
- /**
- * Whether to show the default app label as the title, instead of as the summary.
- */
+ /** Whether to show the default app label as the title, instead of as the summary. */
protected boolean showLabelAsTitle() {
return false;
}
+ /** Whether to show the app summary. */
+ protected boolean showAppSummary() {
+ return false;
+ }
+
public Drawable getDefaultAppIcon() {
if (!isAvailable()) {
return null;
@@ -138,4 +144,15 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC
}
return null;
}
+
+ private @Nullable CharSequence getDefaultAppSummary() {
+ if (!isAvailable()) {
+ return null;
+ }
+ final DefaultAppInfo app = getDefaultAppInfo();
+ if (app != null) {
+ return app.getSummary();
+ }
+ return null;
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index 62e191e5284..bf83cb15f21 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -259,10 +259,10 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
if (mCanAssumeUdfps) {
int rotation = getApplicationContext().getDisplay().getRotation();
+ final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate(
+ R.layout.udfps_enroll_enrolling, null, false);
switch (rotation) {
case Surface.ROTATION_90:
- final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate(
- R.layout.udfps_enroll_enrolling, null, false);
final LinearLayout layoutContainer = layout.findViewById(
R.id.layout_container);
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
@@ -287,53 +287,56 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
case Surface.ROTATION_0:
case Surface.ROTATION_180:
- case Surface.ROTATION_270:
- default:
- final GlifLayout defaultLayout = (GlifLayout) getLayoutInflater().inflate(
- R.layout.udfps_enroll_enrolling, null, false);
if (FeatureFlagUtils.isEnabled(getApplicationContext(),
FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) {
final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(props.get(0));
- if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- // In the portrait mode, set layout_container's height 0, so it's
- // always shown at the bottom of the screen.
- // Add udfps enroll view into layout_container instead of
- // udfps_enroll_enrolling, so that when the content is too long to
- // make udfps_enroll_enrolling larger than the screen, udfps enroll
- // view could still be set to right position by setting bottom margin to
- // its parent view (layout_container) because it's always at the
- // bottom of the screen.
- final FrameLayout portraitLayoutContainer = defaultLayout.findViewById(
- R.id.layout_container);
- final ViewGroup.LayoutParams containerLp =
- portraitLayoutContainer.getLayoutParams();
- containerLp.height = 0;
+ // In the portrait mode, set layout_container's height 0, so it's
+ // always shown at the bottom of the screen.
+ // Add udfps enroll view into layout_container instead of
+ // udfps_enroll_enrolling, so that when the content is too long to
+ // make udfps_enroll_enrolling larger than the screen, udfps enroll
+ // view could still be set to right position by setting bottom margin to
+ // its parent view (layout_container) because it's always at the
+ // bottom of the screen.
+ final FrameLayout portraitLayoutContainer = layout.findViewById(
+ R.id.layout_container);
+ final ViewGroup.LayoutParams containerLp =
+ portraitLayoutContainer.getLayoutParams();
+ containerLp.height = 0;
- // In the portrait mode, the title and lottie animation view may
- // overlap when title needs three lines, so adding some paddings
- // between them, and adjusting the fp progress view here accordingly.
- final int layoutLottieAnimationPadding = (int) getResources()
- .getDimension(R.dimen.udfps_lottie_padding_top);
- portraitLayoutContainer.setPadding(0,
- layoutLottieAnimationPadding, 0, 0);
- final ImageView progressView = udfpsEnrollView.findViewById(
- R.id.udfps_enroll_animation_fp_progress_view);
- progressView.setPadding(0, -(layoutLottieAnimationPadding),
- 0, layoutLottieAnimationPadding);
- final ImageView fingerprintView = udfpsEnrollView.findViewById(
- R.id.udfps_enroll_animation_fp_view);
- fingerprintView.setPadding(0, -layoutLottieAnimationPadding,
- 0, layoutLottieAnimationPadding);
+ // In the portrait mode, the title and lottie animation view may
+ // overlap when title needs three lines, so adding some paddings
+ // between them, and adjusting the fp progress view here accordingly.
+ final int layoutLottieAnimationPadding = (int) getResources()
+ .getDimension(R.dimen.udfps_lottie_padding_top);
+ portraitLayoutContainer.setPadding(0,
+ layoutLottieAnimationPadding, 0, 0);
+ final ImageView progressView = udfpsEnrollView.findViewById(
+ R.id.udfps_enroll_animation_fp_progress_view);
+ progressView.setPadding(0, -(layoutLottieAnimationPadding),
+ 0, layoutLottieAnimationPadding);
+ final ImageView fingerprintView = udfpsEnrollView.findViewById(
+ R.id.udfps_enroll_animation_fp_view);
+ fingerprintView.setPadding(0, -layoutLottieAnimationPadding,
+ 0, layoutLottieAnimationPadding);
- portraitLayoutContainer.addView(udfpsEnrollView);
- setOnHoverListener(false, defaultLayout, udfpsEnrollView);
- } else if (rotation == Surface.ROTATION_270) {
- defaultLayout.addView(udfpsEnrollView);
- setOnHoverListener(true, defaultLayout, udfpsEnrollView);
- }
+ portraitLayoutContainer.addView(udfpsEnrollView);
+ setOnHoverListener(false, layout, udfpsEnrollView);
}
- setContentView(defaultLayout);
+ setContentView(layout);
+ break;
+
+ case Surface.ROTATION_270:
+ default:
+ if (FeatureFlagUtils.isEnabled(getApplicationContext(),
+ FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) {
+ final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(props.get(0));
+ layout.addView(udfpsEnrollView);
+ setOnHoverListener(true, layout, udfpsEnrollView);
+ }
+
+ setContentView(layout);
break;
}
setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
@@ -514,7 +517,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
// showErrorDialog() will cause onWindowFocusChanged(false), set mIsCanceled to false
// before showErrorDialog() to prevent that another error dialog is triggered again.
mIsCanceled = true;
- FingerprintErrorDialog.showErrorDialog(this, errorMsgId, mCanAssumeUdfps);
+ FingerprintErrorDialog.showErrorDialog(this, errorMsgId);
cancelEnrollment();
stopIconAnimation();
stopListenOrientationEvent();
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index 89bda20cd7a..e54403170eb 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -329,7 +329,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements
if (mNextClicked && errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
proceedToEnrolling(false /* cancelEnrollment */);
} else {
- FingerprintErrorDialog.showErrorDialog(this, errMsgId, mCanAssumeUdfps);
+ FingerprintErrorDialog.showErrorDialog(this, errMsgId);
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java
index 8f604e21605..f0a87a4a889 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java
@@ -18,13 +18,10 @@ package com.android.settings.biometrics.fingerprint;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
-import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.KEY_STATE_CANCELED;
import android.app.Activity;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
-import android.content.DialogInterface;
-import android.content.Intent;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
@@ -50,7 +47,6 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment {
final CharSequence errorString = getArguments().getCharSequence(KEY_ERROR_MSG);
final CharSequence errorTitle = getArguments().getCharSequence(KEY_ERROR_TITLE);
final int errMsgId = getArguments().getInt(KEY_ERROR_ID);
- final boolean canAssumeUdfps = getArguments().getBoolean(KEY_UDFPS, false);
final boolean wasTimeout = errMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
builder.setTitle(errorTitle)
@@ -58,53 +54,22 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment {
.setCancelable(false)
.setPositiveButton(
R.string.security_settings_fingerprint_enroll_dialog_ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- final Activity activity = getActivity();
- if (wasTimeout && !canAssumeUdfps) {
- activity.setResult(RESULT_TIMEOUT);
- } else {
- activity.setResult(RESULT_FINISHED);
- }
- activity.finish();
+ (dialog, which) -> {
+ dialog.dismiss();
+ final Activity activity = getActivity();
+ if (wasTimeout) {
+ activity.setResult(RESULT_TIMEOUT);
+ } else {
+ activity.setResult(RESULT_FINISHED);
}
+ activity.finish();
});
- if (wasTimeout && canAssumeUdfps) {
- builder.setPositiveButton(
- R.string.security_settings_fingerprint_enroll_dialog_try_again,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- final Activity activity = getActivity();
- final Intent intent = activity.getIntent();
- intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
- intent.putExtra(KEY_STATE_CANCELED, false);
- activity.startActivity(intent);
- activity.finish();
- }
- })
- .setNegativeButton(
- R.string.security_settings_fingerprint_enroll_dialog_ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- final Activity activity = getActivity();
- activity.setResult(RESULT_TIMEOUT);
- activity.finish();
- }
- });
- }
final AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
- public static void showErrorDialog(BiometricEnrollBase host, int errMsgId,
- boolean canAssumeUdfps) {
+ public static void showErrorDialog(BiometricEnrollBase host, int errMsgId) {
if (host.isFinishing()) {
return;
}
@@ -114,12 +79,7 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment {
}
CharSequence errMsg = host.getText(getErrorMessage(errMsgId));
final CharSequence errTitle = host.getText(getErrorTitle(errMsgId));
- if (!canAssumeUdfps
- && errMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
- errMsg = host.getText(getErrorMessage(BiometricConstants.BIOMETRIC_ERROR_CANCELED));
- }
- final FingerprintErrorDialog dialog = newInstance(errMsg, errTitle,
- errMsgId, canAssumeUdfps);
+ final FingerprintErrorDialog dialog = newInstance(errMsg, errTitle, errMsgId);
dialog.show(fragmentManager, FingerprintErrorDialog.class.getName());
}
@@ -154,13 +114,12 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment {
}
private static FingerprintErrorDialog newInstance(CharSequence msg, CharSequence title,
- int msgId, boolean canAssumeUdfps) {
+ int msgId) {
final FingerprintErrorDialog dialog = new FingerprintErrorDialog();
final Bundle args = new Bundle();
args.putCharSequence(KEY_ERROR_MSG, msg);
args.putCharSequence(KEY_ERROR_TITLE, title);
args.putInt(KEY_ERROR_ID, msgId);
- args.putBoolean(KEY_UDFPS, canAssumeUdfps);
dialog.setArguments(args);
return dialog;
}
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java b/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java
index da6e1789b0c..947c5acc83b 100644
--- a/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java
+++ b/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java
@@ -100,6 +100,8 @@ public class StylusDeviceUpdater implements InputManager.InputDeviceListener,
@Override
public void onInputDeviceAdded(int deviceId) {
InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+ if (inputDevice == null) return;
+
if (inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)
&& !inputDevice.isExternal()) {
try {
@@ -121,7 +123,10 @@ public class StylusDeviceUpdater implements InputManager.InputDeviceListener,
@Override
public void onInputDeviceChanged(int deviceId) {
- if (mInputManager.getInputDevice(deviceId).supportsSource(InputDevice.SOURCE_STYLUS)) {
+ InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+ if (inputDevice == null) return;
+
+ if (inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)) {
forceUpdate();
}
}
@@ -189,6 +194,8 @@ public class StylusDeviceUpdater implements InputManager.InputDeviceListener,
boolean hasConnectedBluetoothStylusDevice() {
for (int deviceId : mInputManager.getInputDeviceIds()) {
InputDevice device = mInputManager.getInputDevice(deviceId);
+ if (device == null) continue;
+
if (device.supportsSource(InputDevice.SOURCE_STYLUS)
&& mInputManager.getInputDeviceBluetoothAddress(deviceId) != null) {
return true;
diff --git a/src/com/android/settings/network/telephony/NullAlgorithmsPreferenceController.java b/src/com/android/settings/network/telephony/NullAlgorithmsPreferenceController.java
index be5b01ee83b..7306aaf25f5 100644
--- a/src/com/android/settings/network/telephony/NullAlgorithmsPreferenceController.java
+++ b/src/com/android/settings/network/telephony/NullAlgorithmsPreferenceController.java
@@ -59,7 +59,7 @@ public class NullAlgorithmsPreferenceController extends TelephonyTogglePreferenc
}
if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CELLULAR_SECURITY,
- TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, false)) {
+ TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, true)) {
Log.i(LOG_TAG, "Null cipher toggle is disabled by DeviceConfig");
return CONDITIONALLY_UNAVAILABLE;
}
diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java
index 2672906d25e..12272a7523a 100644
--- a/src/com/android/settings/slices/SettingsSliceProvider.java
+++ b/src/com/android/settings/slices/SettingsSliceProvider.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.READ_SEARCH_INDEXABLES;
import static android.app.slice.Slice.HINT_PARTIAL;
import android.app.PendingIntent;
+import android.app.settings.SettingsEnums;
import android.app.slice.SliceManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -162,6 +163,13 @@ public class SettingsSliceProvider extends SliceProvider {
Log.d(TAG, "onSlicePinned: " + sliceUri);
mFirstSlicePinned = true;
}
+ FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
+ .action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_SETTINGS_SLICE_REQUESTED,
+ SettingsEnums.PAGE_UNKNOWN,
+ sliceUri.getLastPathSegment(),
+ 0);
+
if (CustomSliceRegistry.isValidUri(sliceUri)) {
final Context context = getContext();
final CustomSliceable sliceable = FeatureFactory.getFactory(context)
diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java
index 8d5a0b4d8c7..6b69540ace1 100644
--- a/src/com/android/settings/slices/SliceBuilderUtils.java
+++ b/src/com/android/settings/slices/SliceBuilderUtils.java
@@ -24,7 +24,6 @@ import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
import android.annotation.ColorInt;
import android.app.PendingIntent;
-import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -51,7 +50,6 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SliderPreferenceController;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.core.TogglePreferenceController;
-import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.Arrays;
@@ -78,12 +76,6 @@ public class SliceBuilderUtils {
public static Slice buildSlice(Context context, SliceData sliceData) {
Log.d(TAG, "Creating slice for: " + sliceData.getPreferenceController());
final BasePreferenceController controller = getPreferenceController(context, sliceData);
- FeatureFactory.getFactory(context).getMetricsFeatureProvider()
- .action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_SLICE_REQUESTED,
- SettingsEnums.PAGE_UNKNOWN,
- sliceData.getKey(),
- 0);
if (!controller.isAvailable()) {
// Cannot guarantee setting page is accessible, let the presenter handle error case.
diff --git a/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt b/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt
index c1a308f2dce..6b3535be353 100644
--- a/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt
@@ -17,18 +17,17 @@
package com.android.settings.spa.app.appinfo
import android.app.settings.SettingsEnums
-import android.content.Intent;
+import android.content.Intent
import android.content.om.OverlayManager
import android.content.pm.ApplicationInfo
import android.os.UserHandle
import android.os.UserManager
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Delete
-
-import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAdd
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
import com.android.settings.R
-import com.android.settings.spa.SpaActivity
import com.android.settings.Utils
+import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAdd
import com.android.settingslib.spa.widget.button.ActionButton
import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
import com.android.settingslib.spaprivileged.model.app.hasFlag
@@ -41,6 +40,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
private val overlayManager = context.getSystemService(OverlayManager::class.java)!!
private val userManager = context.getSystemService(UserManager::class.java)!!
+ @Composable
fun getActionButton(app: ApplicationInfo): ActionButton? {
if (app.isSystemApp || app.isInstantApp) return null
return uninstallButton(app = app, enabled = isUninstallButtonEnabled(app))
@@ -89,10 +89,11 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
isResourceOverlay &&
overlayManager.getOverlayInfo(packageName, userHandle)?.isEnabled == true
+ @Composable
private fun uninstallButton(app: ApplicationInfo, enabled: Boolean) = ActionButton(
text = if (isCloneApp(app)) context.getString(R.string.delete) else
context.getString(R.string.uninstall_text),
- imageVector = Icons.Outlined.Delete,
+ imageVector = ImageVector.vectorResource(R.drawable.ic_settings_delete),
enabled = enabled,
) { onUninstallClicked(app) }
@@ -100,7 +101,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
if (appButtonRepository.isUninstallBlockedByAdmin(app)) {
return
} else if (app.isActiveAdmin(context)) {
- var uninstallDaIntent = Intent(context, DeviceAdminAdd::class.java)
+ val uninstallDaIntent = Intent(context, DeviceAdminAdd::class.java)
uninstallDaIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME,
app.packageName)
packageInfoPresenter.logAction(
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java
index 7fb17e2eb2c..afdbb8beba2 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
import android.content.Intent;
@@ -108,6 +109,15 @@ public class StylusDeviceUpdaterTest {
any());
}
+ @Test
+ public void onInputDeviceAdded_null_doesNothing() {
+ doReturn(null).when(mInputManager).getInputDevice(0);
+ mStylusDeviceUpdater.onInputDeviceAdded(0);
+
+ verify(mInputManager).getInputDevice(0);
+ verifyNoMoreInteractions(mInputManager);
+ }
+
@Test
public void onInputDeviceAdded_internalStylus_registersBatteryListener() {
mStylusDeviceUpdater.onInputDeviceAdded(1);
@@ -124,6 +134,15 @@ public class StylusDeviceUpdaterTest {
any());
}
+ @Test
+ public void onInputDeviceChanged_null_doesNothing() {
+ doReturn(null).when(mInputManager).getInputDevice(0);
+ mStylusDeviceUpdater.onInputDeviceChanged(0);
+
+ verify(mInputManager).getInputDevice(0);
+ verifyNoMoreInteractions(mInputManager);
+ }
+
@Test
public void click_usiPreference_launchUsiDetailsPage() {
doReturn(mSettingsActivity).when(mDashboardFragment).getContext();
diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
index 2941ca0acd5..45fea570970 100644
--- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
@@ -20,9 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -37,10 +35,8 @@ import androidx.slice.SliceProvider;
import androidx.slice.core.SliceAction;
import androidx.slice.widget.SliceLiveData;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
-import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.FakeInvalidSliderController;
import com.android.settings.testutils.FakeSliderController;
import com.android.settings.testutils.FakeToggleController;
@@ -70,12 +66,10 @@ public class SliceBuilderUtilsTest {
private final Class CONTEXT_CONTROLLER = FakeContextOnlyPreferenceController.class;
private Context mContext;
- private FakeFeatureFactory mFeatureFactory;
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
- mFeatureFactory = FakeFeatureFactory.setupForTest();
// Set-up specs for SliceMetadata.
SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
}
@@ -93,12 +87,6 @@ public class SliceBuilderUtilsTest {
final SliceData mockData = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH);
final Slice slice = SliceBuilderUtils.buildSlice(mContext, mockData);
- verify(mFeatureFactory.metricsFeatureProvider)
- .action(SettingsEnums.PAGE_UNKNOWN,
- MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED,
- SettingsEnums.PAGE_UNKNOWN,
- mockData.getKey(),
- 0);
SliceTester.testSettingsToggleSlice(mContext, slice, mockData);
}
@@ -107,12 +95,6 @@ public class SliceBuilderUtilsTest {
final SliceData data = getMockData(SLIDER_CONTROLLER, SliceData.SliceType.SLIDER);
final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
- verify(mFeatureFactory.metricsFeatureProvider)
- .action(SettingsEnums.PAGE_UNKNOWN,
- MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED,
- SettingsEnums.PAGE_UNKNOWN,
- data.getKey(),
- 0);
SliceTester.testSettingsSliderSlice(mContext, slice, data);
}
@@ -298,18 +280,11 @@ public class SliceBuilderUtilsTest {
final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
- verify(mFeatureFactory.metricsFeatureProvider)
- .action(SettingsEnums.PAGE_UNKNOWN,
- MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED,
- SettingsEnums.PAGE_UNKNOWN,
- data.getKey(),
- 0);
-
SliceTester.testSettingsUnavailableSlice(mContext, slice, data);
}
@Test
- public void testConditionallyUnavailableSlice_validTitleSummary() {
+ public void testConditionallyUnavailableSlice_sliceShouldBeNull() {
final SliceData data = getMockData(FakeUnavailablePreferenceController.class,
SliceData.SliceType.SWITCH);
Settings.Global.putInt(mContext.getContentResolver(),
@@ -318,12 +293,7 @@ public class SliceBuilderUtilsTest {
final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
- verify(mFeatureFactory.metricsFeatureProvider)
- .action(SettingsEnums.PAGE_UNKNOWN,
- MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED,
- SettingsEnums.PAGE_UNKNOWN,
- data.getKey(),
- 0);
+ assertThat(slice).isNull();
}
@Test
diff --git a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java
index 3145cce1ac6..e369f527d85 100644
--- a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java
@@ -86,13 +86,12 @@ public class CredentialManagerPreferenceControllerTest {
mCredentialsPreferenceCategory.setKey("credentials_test");
mScreen.addPreference(mCredentialsPreferenceCategory);
mReceivedResultCode = Optional.empty();
- mDelegate =
- new CredentialManagerPreferenceController.Delegate() {
- @Override
- public void setActivityResult(int resultCode) {
- mReceivedResultCode = Optional.of(resultCode);
- }
- };
+ mDelegate = new CredentialManagerPreferenceController.Delegate() {
+ public void setActivityResult(int resultCode) {
+ mReceivedResultCode = Optional.of(resultCode);
+ }
+ public void forceDelegateRefresh() {}
+ };
}
@Test
@@ -122,64 +121,6 @@ public class CredentialManagerPreferenceControllerTest {
assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
- @Test
- public void verifyHiddenIfAutofillSelectedProvider() {
- CredentialManagerPreferenceController controller =
- createControllerWithServices(Collections.emptyList());
-
- // Set the autofill provider.
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.AUTOFILL_SERVICE, "com.example.test/AutofillClass",
- UserHandle.myUserId());
-
- // Verify the error cases
- assertThat(controller.isProviderHiddenBecauseOfAutofill(null)).isFalse();
- assertThat(controller.isProviderHiddenBecauseOfAutofill("")).isFalse();
- assertThat(controller.isProviderHiddenBecauseOfAutofill("test")).isFalse();
-
- // Verify the example.
- assertThat(controller.isProviderHiddenBecauseOfAutofill("com.example.test")).isTrue();
- }
-
- @Test
- public void displayPreference_noServices_noPreferencesAdded_useAutofillUri() {
- Settings.Secure.putStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
- "test",
- UserHandle.myUserId());
-
- CredentialManagerPreferenceController controller =
- createControllerWithServices(Collections.emptyList());
- controller.displayPreference(mScreen);
- assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1);
-
- Preference pref = mCredentialsPreferenceCategory.getPreference(0);
- assertThat(pref.getTitle()).isEqualTo("Add service");
-
- assertThat(controller.getAddServiceUri(mContext)).isEqualTo("test");
- }
-
- @Test
- public void displayPreference_noServices_noPreferencesAdded_useCredManUri() {
- Settings.Secure.putStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
- "test",
- UserHandle.myUserId());
-
- CredentialManagerPreferenceController controller =
- createControllerWithServicesAndAddServiceOverride(
- Collections.emptyList(), "credman");
- controller.displayPreference(mScreen);
- assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1);
-
- Preference pref = mCredentialsPreferenceCategory.getPreference(0);
- assertThat(pref.getTitle()).isEqualTo("Add service");
-
- assertThat(controller.getAddServiceUri(mContext)).isEqualTo("credman");
- }
-
@Test
public void displayPreference_withServices_preferencesAdded() {
CredentialManagerPreferenceController controller =