| /* |
| * Copyright (C) 2022 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 static androidx.lifecycle.Lifecycle.Event.ON_CREATE; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.Activity; |
| import android.app.Dialog; |
| import android.content.ComponentName; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ServiceInfo; |
| import android.content.res.Resources; |
| import android.credentials.CredentialManager; |
| import android.credentials.CredentialProviderInfo; |
| import android.credentials.SetEnabledProvidersException; |
| import android.database.ContentObserver; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.OutcomeReceiver; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.service.autofill.AutofillServiceInfo; |
| import android.text.TextUtils; |
| import android.util.IconDrawableFactory; |
| import android.util.Log; |
| |
| import androidx.appcompat.app.AlertDialog; |
| import androidx.core.content.ContextCompat; |
| import androidx.fragment.app.DialogFragment; |
| import androidx.fragment.app.FragmentManager; |
| import androidx.lifecycle.LifecycleObserver; |
| import androidx.lifecycle.LifecycleOwner; |
| import androidx.lifecycle.OnLifecycleEvent; |
| import androidx.preference.Preference; |
| import androidx.preference.PreferenceGroup; |
| import androidx.preference.PreferenceScreen; |
| import androidx.preference.SwitchPreference; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.content.PackageMonitor; |
| import com.android.settings.R; |
| import com.android.settings.Utils; |
| import com.android.settings.core.BasePreferenceController; |
| import com.android.settings.dashboard.DashboardFragment; |
| import com.android.settingslib.utils.ThreadUtils; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.Executor; |
| |
| /** Queries available credential manager providers and adds preferences for them. */ |
| 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"; |
| private static final int MAX_SELECTABLE_PROVIDERS = 5; |
| |
| private final PackageManager mPm; |
| private final IconDrawableFactory mIconFactory; |
| private final List<CredentialProviderInfo> mServices; |
| private final Set<String> mEnabledPackageNames; |
| private final @Nullable CredentialManager mCredentialManager; |
| private final Executor mExecutor; |
| private final Map<String, SwitchPreference> mPrefs = new HashMap<>(); // key is package name |
| private final List<ServiceInfo> mPendingServiceInfos = new ArrayList<>(); |
| private final Handler mHandler = new Handler(); |
| |
| private @Nullable FragmentManager mFragmentManager = null; |
| private @Nullable Delegate mDelegate = null; |
| 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(); |
| mIconFactory = IconDrawableFactory.newInstance(mContext); |
| mServices = new ArrayList<>(); |
| mEnabledPackageNames = new HashSet<>(); |
| mExecutor = ContextCompat.getMainExecutor(mContext); |
| mCredentialManager = |
| getCredentialManager(context, preferenceKey.equals("credentials_test")); |
| new SettingContentObserver(mHandler).register(context.getContentResolver()); |
| } |
| |
| private @Nullable CredentialManager getCredentialManager(Context context, boolean isTest) { |
| if (isTest) { |
| return null; |
| } |
| |
| Object service = context.getSystemService(Context.CREDENTIAL_SERVICE); |
| |
| if (service != null && CredentialManager.isServiceEnabled(context)) { |
| return (CredentialManager) service; |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Initializes the controller with the parent fragment and adds the controller to observe its |
| * lifecycle. Also stores the fragment manager which is used to open dialogs. |
| * |
| * @param fragment the fragment to use as the parent |
| * @param fragmentManager the fragment manager to use |
| * @param intent the intent used to start the activity |
| * @param delegate the delegate to send results back to |
| */ |
| public void init( |
| DashboardFragment fragment, |
| FragmentManager fragmentManager, |
| @Nullable Intent launchIntent, |
| @NonNull Delegate delegate) { |
| fragment.getSettingsLifecycle().addObserver(this); |
| mFragmentManager = fragmentManager; |
| setDelegate(delegate); |
| verifyReceivedIntent(launchIntent); |
| } |
| |
| /** |
| * Parses and sets the package component name. Returns a boolean as to whether this was |
| * successful. |
| */ |
| @VisibleForTesting |
| boolean verifyReceivedIntent(Intent launchIntent) { |
| if (launchIntent == null || launchIntent.getAction() == null) { |
| return false; |
| } |
| |
| final String action = launchIntent.getAction(); |
| final boolean isCredProviderAction = TextUtils.equals(action, PRIMARY_INTENT); |
| final boolean isExistingAction = TextUtils.equals(action, ALTERNATE_INTENT); |
| final boolean isValid = isCredProviderAction || isExistingAction; |
| |
| if (!isValid) { |
| return false; |
| } |
| |
| // After this point we have received a set credential manager provider intent |
| // so we should return a cancelled result if the data we got is no good. |
| if (launchIntent.getData() == null) { |
| setActivityResult(Activity.RESULT_CANCELED); |
| return false; |
| } |
| |
| String packageName = launchIntent.getData().getSchemeSpecificPart(); |
| if (packageName == null) { |
| setActivityResult(Activity.RESULT_CANCELED); |
| return false; |
| } |
| |
| mPendingServiceInfos.clear(); |
| for (CredentialProviderInfo cpi : mServices) { |
| final ServiceInfo serviceInfo = cpi.getServiceInfo(); |
| if (serviceInfo.packageName.equals(packageName)) { |
| mPendingServiceInfos.add(serviceInfo); |
| } |
| } |
| |
| // Don't set the result as RESULT_OK here because we should wait for the user to |
| // enable the provider. |
| if (!mPendingServiceInfos.isEmpty()) { |
| return true; |
| } |
| |
| setActivityResult(Activity.RESULT_CANCELED); |
| return false; |
| } |
| |
| @VisibleForTesting |
| void setDelegate(Delegate delegate) { |
| mDelegate = delegate; |
| } |
| |
| private void setActivityResult(int resultCode) { |
| if (mDelegate == null) { |
| Log.e(TAG, "Missing delegate"); |
| return; |
| } |
| mDelegate.setActivityResult(resultCode); |
| } |
| |
| private void handleIntent() { |
| List<ServiceInfo> pendingServiceInfos = new ArrayList<>(mPendingServiceInfos); |
| mPendingServiceInfos.clear(); |
| if (pendingServiceInfos.isEmpty()) { |
| return; |
| } |
| |
| ServiceInfo serviceInfo = pendingServiceInfos.get(0); |
| ApplicationInfo appInfo = serviceInfo.applicationInfo; |
| CharSequence appName = ""; |
| if (appInfo.nonLocalizedLabel != null) { |
| appName = appInfo.loadLabel(mPm); |
| } |
| |
| // Stop if there is no name. |
| if (TextUtils.isEmpty(appName)) { |
| return; |
| } |
| |
| NewProviderConfirmationDialogFragment fragment = |
| newNewProviderConfirmationDialogFragment( |
| serviceInfo.packageName, appName, /* setActivityResult= */ true); |
| if (fragment == null || mFragmentManager == null) { |
| return; |
| } |
| |
| fragment.show(mFragmentManager, NewProviderConfirmationDialogFragment.TAG); |
| } |
| |
| @OnLifecycleEvent(ON_CREATE) |
| void onCreate(LifecycleOwner lifecycleOwner) { |
| update(); |
| } |
| |
| private void update() { |
| if (mCredentialManager == null) { |
| return; |
| } |
| |
| setAvailableServices( |
| mCredentialManager.getCredentialProviderServices( |
| getUser(), CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS), |
| null); |
| } |
| |
| private void updateFromExternal() { |
| update(); |
| |
| 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) { |
| mFlagOverrideForTest = flagOverrideForTest; |
| mServices.clear(); |
| mServices.addAll(availableServices); |
| |
| // If there is a pending dialog then show it. |
| handleIntent(); |
| |
| mEnabledPackageNames.clear(); |
| for (CredentialProviderInfo cpi : availableServices) { |
| if (cpi.isEnabled() && !cpi.isPrimary()) { |
| mEnabledPackageNames.add(cpi.getServiceInfo().packageName); |
| } |
| } |
| |
| for (String packageName : mPrefs.keySet()) { |
| mPrefs.get(packageName).setChecked(mEnabledPackageNames.contains(packageName)); |
| } |
| } |
| |
| @Override |
| public void displayPreference(PreferenceScreen screen) { |
| super.displayPreference(screen); |
| |
| // Since the UI is being cleared, clear any refs. |
| mPrefs.clear(); |
| |
| mPreferenceScreen = screen; |
| PreferenceGroup group = screen.findPreference(getPreferenceKey()); |
| group.removeAll(); |
| |
| Context context = screen.getContext(); |
| mPrefs.putAll(buildPreferenceList(context, group)); |
| } |
| |
| /** |
| * Gets the preference that allows to add a new cred man service. |
| * |
| * @return the pref to be added |
| */ |
| @VisibleForTesting |
| public Preference newAddServicePreference(String searchUri, Context context) { |
| final Intent addNewServiceIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); |
| final Preference preference = new Preference(context); |
| preference.setOnPreferenceClickListener( |
| p -> { |
| context.startActivityAsUser(addNewServiceIntent, UserHandle.of(getUser())); |
| return true; |
| }); |
| preference.setTitle(R.string.print_menu_item_add_service); |
| preference.setOrder(Integer.MAX_VALUE - 1); |
| preference.setPersistent(false); |
| |
| // Try to set the icon this should fail in a test environment but work |
| // in the actual app. |
| try { |
| preference.setIcon(R.drawable.ic_add_24dp); |
| } catch (Resources.NotFoundException e) { |
| Log.e(TAG, "Failed to find icon for add services link", e); |
| } |
| return preference; |
| } |
| |
| /** Aggregates the list of services and builds a list of UI prefs to show. */ |
| @VisibleForTesting |
| public Map<String, SwitchPreference> buildPreferenceList( |
| Context context, PreferenceGroup group) { |
| // 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 this is an autofill provider then don't show it here. |
| if (combinedInfo.getCredentialProviderInfos().isEmpty()) { |
| continue; |
| } |
| |
| 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, combinedInfo.getSettingsSubtitle()); |
| output.put(packageName, pref); |
| group.addPreference(pref); |
| } |
| |
| // Set the visibility if we have services. |
| setVisibility(!output.isEmpty()); |
| |
| return output; |
| } |
| |
| /** Creates a preference object based on the provider info. */ |
| @VisibleForTesting |
| public SwitchPreference createPreference(Context context, CredentialProviderInfo service) { |
| CharSequence label = service.getLabel(context); |
| return addProviderPreference( |
| context, |
| label == null ? "" : label, |
| service.getServiceIcon(mContext), |
| service.getServiceInfo().packageName, |
| service.getSettingsSubtitle()); |
| } |
| |
| /** |
| * Enables the package name as an enabled credential manager provider. |
| * |
| * @param packageName the package name to enable |
| */ |
| @VisibleForTesting |
| public boolean togglePackageNameEnabled(String packageName) { |
| if (mEnabledPackageNames.size() >= MAX_SELECTABLE_PROVIDERS) { |
| return false; |
| } else { |
| mEnabledPackageNames.add(packageName); |
| commitEnabledPackages(); |
| return true; |
| } |
| } |
| |
| /** |
| * Disables the package name as a credential manager provider. |
| * |
| * @param packageName the package name to disable |
| */ |
| @VisibleForTesting |
| public void togglePackageNameDisabled(String packageName) { |
| mEnabledPackageNames.remove(packageName); |
| commitEnabledPackages(); |
| } |
| |
| /** Returns the enabled credential manager provider package names. */ |
| @VisibleForTesting |
| public Set<String> getEnabledProviders() { |
| return mEnabledPackageNames; |
| } |
| |
| /** |
| * Returns the enabled credential manager provider flattened component names that can be stored |
| * in the setting. |
| */ |
| @VisibleForTesting |
| public List<String> getEnabledSettings() { |
| // Get all the component names that match the enabled package names. |
| List<String> enabledServices = new ArrayList<>(); |
| for (CredentialProviderInfo service : mServices) { |
| ComponentName cn = service.getServiceInfo().getComponentName(); |
| if (mEnabledPackageNames.contains(service.getServiceInfo().packageName)) { |
| enabledServices.add(cn.flattenToString()); |
| } |
| } |
| |
| return enabledServices; |
| } |
| |
| private SwitchPreference addProviderPreference( |
| @NonNull Context prefContext, |
| @NonNull CharSequence title, |
| @Nullable Drawable icon, |
| @NonNull String packageName, |
| @Nullable CharSequence subtitle) { |
| final SwitchPreference pref = new SwitchPreference(prefContext); |
| pref.setTitle(title); |
| pref.setChecked(mEnabledPackageNames.contains(packageName)); |
| |
| if (icon != null) { |
| pref.setIcon(Utils.getSafeIcon(icon)); |
| } |
| |
| if (subtitle != null) { |
| pref.setSummary(subtitle); |
| } |
| |
| pref.setOnPreferenceClickListener( |
| p -> { |
| boolean isChecked = pref.isChecked(); |
| |
| if (isChecked) { |
| // Since we are enabling it we should confirm the user decision with a |
| // dialog box. |
| NewProviderConfirmationDialogFragment fragment = |
| newNewProviderConfirmationDialogFragment( |
| packageName, title, /* setActivityResult= */ false); |
| if (fragment == null || mFragmentManager == null) { |
| return true; |
| } |
| |
| fragment.show(mFragmentManager, NewProviderConfirmationDialogFragment.TAG); |
| |
| return true; |
| } else { |
| // If we are disabling the last enabled provider then show a warning. |
| if (mEnabledPackageNames.size() <= 1) { |
| final DialogFragment fragment = |
| newConfirmationDialogFragment(packageName, title, pref); |
| |
| if (fragment == null || mFragmentManager == null) { |
| return true; |
| } |
| |
| fragment.show(mFragmentManager, ConfirmationDialogFragment.TAG); |
| } else { |
| togglePackageNameDisabled(packageName); |
| } |
| } |
| |
| return true; |
| }); |
| |
| return pref; |
| } |
| |
| private void commitEnabledPackages() { |
| // Commit using the CredMan API. |
| if (mCredentialManager == null) { |
| return; |
| } |
| |
| // Get the existing primary providers since we don't touch them in |
| // this part of the UI we should just copy them over. |
| Set<String> primaryServices = new HashSet<>(); |
| for (CredentialProviderInfo service : mServices) { |
| if (service.isPrimary()) { |
| primaryServices.add(service.getServiceInfo().getComponentName().flattenToString()); |
| } |
| } |
| |
| mCredentialManager.setEnabledProviders( |
| new ArrayList<>(primaryServices), |
| getEnabledSettings(), |
| getUser(), |
| mExecutor, |
| new OutcomeReceiver<Void, SetEnabledProvidersException>() { |
| @Override |
| public void onResult(Void result) { |
| Log.i(TAG, "setEnabledProviders success"); |
| updateFromExternal(); |
| } |
| |
| @Override |
| public void onError(SetEnabledProvidersException e) { |
| Log.e(TAG, "setEnabledProviders error: " + e.toString()); |
| } |
| }); |
| } |
| |
| /** Create the new provider confirmation dialog. */ |
| private @Nullable NewProviderConfirmationDialogFragment |
| newNewProviderConfirmationDialogFragment( |
| @NonNull String packageName, |
| @NonNull CharSequence appName, |
| boolean setActivityResult) { |
| DialogHost host = |
| new DialogHost() { |
| @Override |
| public void onDialogClick(int whichButton) { |
| completeEnableProviderDialogBox( |
| whichButton, packageName, setActivityResult); |
| } |
| }; |
| |
| return new NewProviderConfirmationDialogFragment(host, packageName, appName); |
| } |
| |
| @VisibleForTesting |
| void completeEnableProviderDialogBox( |
| int whichButton, String packageName, boolean setActivityResult) { |
| int activityResult = -1; |
| if (whichButton == DialogInterface.BUTTON_POSITIVE) { |
| if (togglePackageNameEnabled(packageName)) { |
| // Enable all prefs. |
| if (mPrefs.containsKey(packageName)) { |
| mPrefs.get(packageName).setChecked(true); |
| } |
| activityResult = Activity.RESULT_OK; |
| } else { |
| // There are too many providers so set the result as cancelled. |
| activityResult = Activity.RESULT_CANCELED; |
| |
| // Show the error if too many enabled. |
| final DialogFragment fragment = newErrorDialogFragment(); |
| |
| if (fragment == null || mFragmentManager == null) { |
| return; |
| } |
| |
| fragment.show(mFragmentManager, ErrorDialogFragment.TAG); |
| } |
| } else { |
| // The user clicked the cancel button so send that result back. |
| activityResult = Activity.RESULT_CANCELED; |
| } |
| |
| // If the dialog is being shown because of the intent we should |
| // return a result. |
| if (activityResult == -1 || !setActivityResult) { |
| setActivityResult(activityResult); |
| } |
| } |
| |
| private @Nullable ErrorDialogFragment newErrorDialogFragment() { |
| DialogHost host = |
| new DialogHost() { |
| @Override |
| public void onDialogClick(int whichButton) {} |
| }; |
| |
| return new ErrorDialogFragment(host); |
| } |
| |
| private @Nullable ConfirmationDialogFragment newConfirmationDialogFragment( |
| @NonNull String packageName, |
| @NonNull CharSequence appName, |
| @NonNull SwitchPreference pref) { |
| DialogHost host = |
| new DialogHost() { |
| @Override |
| public void onDialogClick(int whichButton) { |
| if (whichButton == DialogInterface.BUTTON_POSITIVE) { |
| // Since the package is now enabled then we |
| // should remove it from the enabled list. |
| togglePackageNameDisabled(packageName); |
| } else if (whichButton == DialogInterface.BUTTON_NEGATIVE) { |
| // Set the checked back to true because we |
| // backed out of turning this off. |
| pref.setChecked(true); |
| } |
| } |
| }; |
| |
| return new ConfirmationDialogFragment(host, packageName, appName); |
| } |
| |
| private int getUser() { |
| UserHandle workUser = getWorkProfileUser(); |
| return workUser != null ? workUser.getIdentifier() : UserHandle.myUserId(); |
| } |
| |
| /** Called when the dialog button is clicked. */ |
| private static interface DialogHost { |
| void onDialogClick(int whichButton); |
| } |
| |
| /** 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 #DefaultCombinedPicker} when |
| * necessary |
| */ |
| 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(() -> updateFromExternal()); |
| } |
| |
| @Override |
| public void onPackageRemoved(String packageName, int uid) { |
| ThreadUtils.postOnMainThread(() -> updateFromExternal()); |
| } |
| }; |
| |
| /** Dialog fragment parent class. */ |
| private abstract static class CredentialManagerDialogFragment extends DialogFragment |
| implements DialogInterface.OnClickListener { |
| |
| public static final String TAG = "CredentialManagerDialogFragment"; |
| public static final String PACKAGE_NAME_KEY = "package_name"; |
| public static final String APP_NAME_KEY = "app_name"; |
| |
| private DialogHost mDialogHost; |
| |
| CredentialManagerDialogFragment(DialogHost dialogHost) { |
| super(); |
| mDialogHost = dialogHost; |
| } |
| |
| public DialogHost getDialogHost() { |
| return mDialogHost; |
| } |
| } |
| |
| /** Dialog showing error when too many providers are selected. */ |
| public static class ErrorDialogFragment extends CredentialManagerDialogFragment { |
| |
| ErrorDialogFragment(DialogHost dialogHost) { |
| super(dialogHost); |
| } |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| return new AlertDialog.Builder(getActivity()) |
| .setTitle(getContext().getString(R.string.credman_error_message_title)) |
| .setMessage(getContext().getString(R.string.credman_error_message)) |
| .setPositiveButton(android.R.string.ok, this) |
| .create(); |
| } |
| |
| @Override |
| public void onClick(DialogInterface dialog, int which) {} |
| } |
| |
| /** |
| * Confirmation dialog fragment shows a dialog to the user to confirm that they are disabling a |
| * provider. |
| */ |
| public static class ConfirmationDialogFragment extends CredentialManagerDialogFragment { |
| |
| ConfirmationDialogFragment( |
| DialogHost dialogHost, @NonNull String packageName, @NonNull CharSequence appName) { |
| super(dialogHost); |
| |
| final Bundle argument = new Bundle(); |
| argument.putString(PACKAGE_NAME_KEY, packageName); |
| argument.putCharSequence(APP_NAME_KEY, appName); |
| setArguments(argument); |
| } |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| final Bundle bundle = getArguments(); |
| final String title = |
| getContext() |
| .getString( |
| R.string.credman_confirmation_message_title, |
| bundle.getCharSequence( |
| CredentialManagerDialogFragment.APP_NAME_KEY)); |
| |
| return new AlertDialog.Builder(getActivity()) |
| .setTitle(title) |
| .setMessage(getContext().getString(R.string.credman_confirmation_message)) |
| .setPositiveButton(R.string.credman_confirmation_message_positive_button, this) |
| .setNegativeButton(android.R.string.cancel, this) |
| .create(); |
| } |
| |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| getDialogHost().onDialogClick(which); |
| } |
| } |
| |
| /** |
| * Confirmation dialog fragment shows a dialog to the user to confirm that they would like to |
| * enable the new provider. |
| */ |
| public static class NewProviderConfirmationDialogFragment |
| extends CredentialManagerDialogFragment { |
| |
| NewProviderConfirmationDialogFragment( |
| DialogHost dialogHost, @NonNull String packageName, @NonNull CharSequence appName) { |
| super(dialogHost); |
| |
| final Bundle argument = new Bundle(); |
| argument.putString(PACKAGE_NAME_KEY, packageName); |
| argument.putCharSequence(APP_NAME_KEY, appName); |
| setArguments(argument); |
| } |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| final Bundle bundle = getArguments(); |
| final Context context = getContext(); |
| final CharSequence appName = |
| bundle.getCharSequence(CredentialManagerDialogFragment.APP_NAME_KEY); |
| final String title = |
| context.getString(R.string.credman_enable_confirmation_message_title, appName); |
| final String message = |
| context.getString(R.string.credman_enable_confirmation_message, appName); |
| |
| return new AlertDialog.Builder(getActivity()) |
| .setTitle(title) |
| .setMessage(message) |
| .setPositiveButton(android.R.string.ok, this) |
| .setNegativeButton(android.R.string.cancel, this) |
| .create(); |
| } |
| |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| getDialogHost().onDialogClick(which); |
| } |
| } |
| |
| /** Updates the list if setting content changes. */ |
| private final class SettingContentObserver extends ContentObserver { |
| |
| private final Uri mAutofillService = |
| Settings.Secure.getUriFor(Settings.Secure.AUTOFILL_SERVICE); |
| |
| private final Uri mCredentialService = |
| Settings.Secure.getUriFor(Settings.Secure.CREDENTIAL_SERVICE); |
| |
| public SettingContentObserver(Handler handler) { |
| super(handler); |
| } |
| |
| public void register(ContentResolver contentResolver) { |
| contentResolver.registerContentObserver(mAutofillService, false, this, getUser()); |
| contentResolver.registerContentObserver(mCredentialService, false, this, getUser()); |
| } |
| |
| @Override |
| public void onChange(boolean selfChange, Uri uri) { |
| updateFromExternal(); |
| } |
| } |
| } |