Snap for 10060019 from 570943637c to udc-release
Change-Id: Iaa787056d6283ac3def12f895ae090d654b76264
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
20
res/xml/default_credman_picker.xml
Normal file
20
res/xml/default_credman_picker.xml
Normal 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" />
|
||||
@@ -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(
|
||||
|
||||
@@ -120,7 +120,7 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.ACCOUNT;
|
||||
return SettingsEnums.ACCOUNT_DETAIL;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user