Snap for 10060019 from 570943637c to udc-release

Change-Id: Iaa787056d6283ac3def12f895ae090d654b76264
This commit is contained in:
Android Build Coastguard Worker
2023-05-03 23:30:15 +00:00
26 changed files with 1230 additions and 344 deletions

View File

@@ -1022,7 +1022,7 @@
<!-- Dialog message for dialog which shows when finger cannot be enrolled. [CHAR LIMIT=45] -->
<string name="security_settings_fingerprint_enroll_error_dialog_title">Fingerprint setup timed out</string>
<!-- Dialog message for dialog which shows when finger cannot be enrolled due to being idle too long. -->
<string name="security_settings_fingerprint_enroll_error_timeout_dialog_message">Try again now or set up your fingerprint later in Settings</string>
<string name="security_settings_fingerprint_enroll_error_timeout_dialog_message">You can set up your fingerprint later in Settings.</string>
<!-- Dialog message for dialog which shows when finger cannot be enrolled due to an internal error or fingerprint can't be read. -->
<string name="security_settings_fingerprint_enroll_error_generic_dialog_message">Fingerprint enrollment didn\'t work. Try again or use a different finger.</string>
<!-- Button text shown at the end of enrollment that allows the user to add another fingerprint -->
@@ -10265,6 +10265,8 @@
<string name="autofill_keywords">auto, fill, autofill, password</string>
<!-- Keywords for the credman feature. [CHAR LIMIT=NONE] -->
<string name="credman_keywords">data, passkey, password</string>
<!-- Keywords for the credman feature. [CHAR LIMIT=NONE] -->
<string name="credman_autofill_keywords">auto, fill, autofill, data, passkey, password</string>
<!-- Message of the warning dialog for setting the auto-fill app. [CHAR_LIMIT=NONE] -->
<string name="autofill_confirmation_message">
@@ -10277,6 +10279,20 @@
]]>
</string>
<!-- Message of the warning dialog for setting the auto-fill/credman app. [CHAR_LIMIT=NONE] -->
<string name="credman_autofill_confirmation_message">
<![CDATA[
<b>Use <xliff:g id="app_name" example="Provider">%1$s</xliff:g>\?</b>
<br/>
<br/>
<xliff:g id="app_name" example="Provider">%1$s</xliff:g> 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.
]]>
</string>
<!-- Title of the screen where the user picks a provider. [CHAR_LIMIT=NONE] -->
<string name="credman_picker_title">Passwords, passkeys and data services</string>
<!-- Title of the warning dialog for disabling the credential provider. [CHAR_LIMIT=NONE] -->
<string name="credman_confirmation_message_title">Turn off %1$s\?</string>

View File

@@ -27,10 +27,10 @@
android:title="@string/credman_chosen_app_title">
<com.android.settings.widget.GearPreference
android:fragment="com.android.settings.applications.defaultapps.DefaultAutofillPicker"
android:key="default_autofill_main"
android:fragment="com.android.settings.applications.credentials.DefaultCombinedPicker"
android:key="default_credman_autofill_main"
android:title="@string/credman_chosen_app_title"
settings:keywords="@string/autofill_keywords">
settings:keywords="@string/credman_autofill_keywords">
<extra
android:name="for_work"
android:value="false" />

View File

@@ -28,10 +28,10 @@
android:title="@string/credman_chosen_app_title">
<com.android.settings.widget.GearPreference
android:fragment="com.android.settings.applications.defaultapps.DefaultAutofillPicker"
android:key="default_autofill_main"
android:fragment="com.android.settings.applications.credentials.DefaultCombinedPicker"
android:key="default_credman_autofill_main"
android:title="@string/credman_chosen_app_title"
settings:keywords="@string/autofill_keywords">
settings:keywords="@string/credman_autofill_keywords">
<extra
android:name="for_work"
android:value="false" />

View File

@@ -28,8 +28,8 @@
android:title="@string/credman_chosen_app_title">
<com.android.settings.widget.GearPreference
android:fragment="com.android.settings.applications.defaultapps.DefaultAutofillPicker"
android:key="default_autofill_work"
android:fragment="com.android.settings.applications.credentials.DefaultCombinedPicker"
android:key="default_credman_autofill_main"
android:title="@string/credman_chosen_app_title"
settings:searchable="false">
<extra

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/credman_picker_title" />

View File

@@ -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<AbstractPreferenceController> 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(

View File

@@ -120,7 +120,7 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCOUNT;
return SettingsEnums.ACCOUNT_DETAIL;
}
@Override

View File

@@ -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));

View File

@@ -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));

View File

@@ -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<CredentialProviderInfo> 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<CredentialProviderInfo> 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<CredentialProviderInfo> 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<String, ServiceInfo> flattenedNamesToServices = new HashMap<>();
List<String> 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<String> 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<CombinedProviderInfo> 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<CombinedProviderInfo> buildMergedList(
List<AutofillServiceInfo> asiList,
List<CredentialProviderInfo> cpiList,
@Nullable String defaultAutofillProvider) {
ComponentName defaultAutofillProviderComponent =
(defaultAutofillProvider == null)
? null
: ComponentName.unflattenFromString(defaultAutofillProvider);
// Index the autofill providers by package name.
Set<String> packageNames = new HashSet<>();
Map<String, List<AutofillServiceInfo>> 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<String, List<CredentialProviderInfo>> 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<CombinedProviderInfo> cmpi = new ArrayList<>();
for (String packageName : packageNames) {
List<AutofillServiceInfo> asi = autofillServices.get(packageName);
List<CredentialProviderInfo> 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;
}
}

View File

@@ -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<CredentialProviderInfo> availableServices,
String flagOverrideForTest) {
List<CredentialProviderInfo> 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<String, SwitchPreference> buildPreferenceList(
Context context, PreferenceGroup group) {
// Group the services by package name.
Map<String, List<CredentialProviderInfo>> 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<CombinedProviderInfo> 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<String, SwitchPreference> 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<String, SwitchPreference> output = new HashMap<>();
for (String packageName : groupedInfos.keySet()) {
List<CredentialProviderInfo> 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

View File

@@ -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<CombinedProviderInfo> getAllProviders() {
final Context context = getContext();
final List<AutofillServiceInfo> autofillProviders =
AutofillServiceInfo.getAvailableServices(context, mUserId);
final CredentialManager service = getCredentialProviderService();
final List<CredentialProviderInfo> 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<DefaultAppInfo> getCandidates() {
final Context context = getContext();
final List<CombinedProviderInfo> allProviders = getAllProviders();
final List<DefaultAppInfo> 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<CombinedProviderInfo> 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<String> 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<String> 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<Void, SetEnabledProvidersException>() {
@Override
public void onResult(Void result) {
Log.i(TAG, "setEnabledProviders success");
}
@Override
public void onError(SetEnabledProvidersException e) {
Log.e(TAG, "setEnabledProviders error: " + e.toString());
}
});
}
}

View File

@@ -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<CombinedProviderInfo> 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<CombinedProviderInfo> getAllProviders(int userId) {
final List<AutofillServiceInfo> autofillProviders =
AutofillServiceInfo.getAvailableServices(mContext, userId);
final List<CredentialProviderInfo> 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<ResolveInfo> 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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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.

View File

@@ -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(

View File

@@ -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();

View File

@@ -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

View File

@@ -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 =