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