1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.applications.credentials; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.credentials.CredentialManager; 22 import android.credentials.CredentialProviderInfo; 23 import android.credentials.SetEnabledProvidersException; 24 import android.graphics.drawable.Drawable; 25 import android.os.OutcomeReceiver; 26 import android.os.UserHandle; 27 import android.provider.Settings; 28 import android.service.autofill.AutofillService; 29 import android.service.autofill.AutofillServiceInfo; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import android.view.autofill.AutofillManager; 33 34 import androidx.annotation.NonNull; 35 import androidx.annotation.Nullable; 36 import androidx.core.content.ContextCompat; 37 import androidx.preference.Preference; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.settings.R; 41 import com.android.settings.Utils; 42 import com.android.settings.applications.defaultapps.DefaultAppPreferenceController; 43 import com.android.settingslib.applications.DefaultAppInfo; 44 import com.android.settingslib.widget.TwoTargetPreference; 45 46 import java.util.ArrayList; 47 import java.util.List; 48 import java.util.concurrent.Executor; 49 50 public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController { 51 52 private static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE); 53 private static final String TAG = "DefaultCombinedPreferenceController"; 54 55 private final AutofillManager mAutofillManager; 56 private final CredentialManager mCredentialManager; 57 private final Executor mExecutor; 58 DefaultCombinedPreferenceController(Context context)59 public DefaultCombinedPreferenceController(Context context) { 60 super(context); 61 62 mExecutor = ContextCompat.getMainExecutor(context); 63 mAutofillManager = mContext.getSystemService(AutofillManager.class); 64 65 if (CredentialManager.isServiceEnabled(context)) { 66 mCredentialManager = mContext.getSystemService(CredentialManager.class); 67 } else { 68 mCredentialManager = null; 69 } 70 } 71 72 @Override isAvailable()73 public boolean isAvailable() { 74 return mAutofillManager != null 75 && mCredentialManager != null 76 && mAutofillManager.hasAutofillFeature() 77 && mAutofillManager.isAutofillSupported(); 78 } 79 80 @Override getPreferenceKey()81 public String getPreferenceKey() { 82 return "default_credman_autofill_main"; 83 } 84 85 @Override getSettingIntent(DefaultAppInfo info)86 protected Intent getSettingIntent(DefaultAppInfo info) { 87 // Despite this method being called getSettingIntent this intent actually 88 // opens the primary picker. This is so that we can swap the cog and the left 89 // hand side presses to align the UX. 90 if (PrimaryProviderPreference.shouldUseNewSettingsUi()) { 91 // We need to return an empty intent here since the class we inherit 92 // from will throw an NPE if we return null and we don't want it to 93 // open anything since we added the buttons. 94 return new Intent(); 95 } 96 return createIntentToOpenPicker(); 97 } 98 99 @Override updateState(@onNull Preference preference)100 public void updateState(@NonNull Preference preference) { 101 final CombinedProviderInfo topProvider = getTopProvider(); 102 final int userId = getUser(); 103 104 if (topProvider != null && mContext != null) { 105 updatePreferenceForProvider( 106 preference, 107 topProvider.getAppName(mContext), 108 topProvider.getSettingsSubtitle(), 109 topProvider.getAppIcon(mContext, userId), 110 topProvider.getPackageName(), 111 topProvider.getSettingsActivity()); 112 } else { 113 updatePreferenceForProvider(preference, null, null, null, null, null); 114 } 115 } 116 117 @VisibleForTesting updatePreferenceForProvider( Preference preference, @Nullable CharSequence appName, @Nullable String appSubtitle, @Nullable Drawable appIcon, @Nullable String packageName, @Nullable CharSequence settingsActivity)118 public void updatePreferenceForProvider( 119 Preference preference, 120 @Nullable CharSequence appName, 121 @Nullable String appSubtitle, 122 @Nullable Drawable appIcon, 123 @Nullable String packageName, 124 @Nullable CharSequence settingsActivity) { 125 if (appName == null) { 126 preference.setTitle(R.string.credman_app_list_preference_none); 127 } else { 128 preference.setTitle(appName); 129 } 130 131 if (appIcon == null) { 132 preference.setIcon(null); 133 } else { 134 preference.setIcon(Utils.getSafeIcon(appIcon)); 135 } 136 137 preference.setSummary(appSubtitle); 138 139 if (preference instanceof PrimaryProviderPreference) { 140 PrimaryProviderPreference primaryPref = (PrimaryProviderPreference) preference; 141 primaryPref.setIconSize(TwoTargetPreference.ICON_SIZE_MEDIUM); 142 primaryPref.setDelegate( 143 new PrimaryProviderPreference.Delegate() { 144 public void onOpenButtonClicked() { 145 CombinedProviderInfo.launchSettingsActivityIntent( 146 mContext, packageName, settingsActivity, getUser()); 147 } 148 149 public void onChangeButtonClicked() { 150 startActivity(createIntentToOpenPicker()); 151 } 152 }); 153 154 // Hide the open button if there is no defined settings activity. 155 primaryPref.setOpenButtonVisible(!TextUtils.isEmpty(settingsActivity)); 156 primaryPref.setButtonsCompactMode(appName != null); 157 } 158 } 159 getTopProvider()160 private @Nullable CombinedProviderInfo getTopProvider() { 161 final int userId = getUser(); 162 final @Nullable CombinedProviderInfo topProvider = 163 CombinedProviderInfo.getTopProvider(getAllProviders(userId)); 164 165 // Apply device admin restrictions to top provider. 166 if (topProvider != null 167 && topProvider.getDeviceAdminRestrictions(mContext, userId) != null) { 168 // This case means, the provider is blocked by device admin, but settings' storage has 169 // not be cleared correctly. So clean the storage here. 170 removePrimaryProvider(); 171 return null; 172 } 173 174 return topProvider; 175 } 176 177 @Override getDefaultAppInfo()178 protected DefaultAppInfo getDefaultAppInfo() { 179 return null; 180 } 181 getAllProviders(int userId)182 private List<CombinedProviderInfo> getAllProviders(int userId) { 183 final List<AutofillServiceInfo> autofillProviders = 184 AutofillServiceInfo.getAvailableServices(mContext, userId); 185 final String selectedAutofillProvider = 186 CredentialManagerPreferenceController 187 .getSelectedAutofillProvider(mContext, userId, TAG); 188 189 final List<CredentialProviderInfo> credManProviders = new ArrayList<>(); 190 if (mCredentialManager != null) { 191 credManProviders.addAll( 192 mCredentialManager.getCredentialProviderServices( 193 userId, 194 CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_INCLUDING_HIDDEN)); 195 } 196 197 return CombinedProviderInfo.buildMergedList( 198 autofillProviders, credManProviders, selectedAutofillProvider); 199 } 200 201 @Override showLabelAsTitle()202 protected boolean showLabelAsTitle() { 203 return true; 204 } 205 206 @Override showAppSummary()207 protected boolean showAppSummary() { 208 return true; 209 } 210 getUser()211 protected int getUser() { 212 return UserHandle.myUserId(); 213 } 214 215 /** Creates an intent to open the credential picker. */ createIntentToOpenPicker()216 private Intent createIntentToOpenPicker() { 217 final Context context = 218 mContext.createContextAsUser(UserHandle.of(getUser()), /* flags= */ 0); 219 return new Intent(context, CredentialsPickerActivity.class); 220 } 221 removePrimaryProvider()222 private void removePrimaryProvider() { 223 // Commit using the CredMan API. 224 if (mCredentialManager == null) { 225 return; 226 } 227 228 // Clean the autofill provider settings 229 Settings.Secure.putStringForUser( 230 mContext.getContentResolver(), 231 DefaultCombinedPicker.AUTOFILL_SETTING, null, getUser()); 232 233 // Clean the credman provider settings. 234 mCredentialManager.setEnabledProviders( 235 List.of(), // empty primary provider. 236 List.of(), // empty enabled providers. 237 getUser(), 238 mExecutor, 239 new OutcomeReceiver<Void, SetEnabledProvidersException>() { 240 @Override 241 public void onResult(Void result) { 242 Log.i(TAG, "setEnabledProviders success"); 243 } 244 245 @Override 246 public void onError(SetEnabledProvidersException e) { 247 Log.e(TAG, "setEnabledProviders error: " + e.toString()); 248 } 249 }); 250 } 251 } 252