1 /* 2 * Copyright (C) 2017 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.defaultapps; 18 19 import android.Manifest; 20 import android.app.Activity; 21 import android.app.settings.SettingsEnums; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.Intent; 26 import android.content.pm.PackageManager; 27 import android.content.pm.ResolveInfo; 28 import android.content.pm.ServiceInfo; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.UserHandle; 32 import android.provider.Settings; 33 import android.service.autofill.AutofillService; 34 import android.service.autofill.AutofillServiceInfo; 35 import android.text.Html; 36 import android.text.TextUtils; 37 import android.util.Log; 38 39 import androidx.preference.Preference; 40 41 import com.android.internal.content.PackageMonitor; 42 import com.android.settings.R; 43 import com.android.settingslib.applications.DefaultAppInfo; 44 import com.android.settingslib.utils.ThreadUtils; 45 import com.android.settingslib.widget.CandidateInfo; 46 47 import java.util.ArrayList; 48 import java.util.List; 49 50 public class DefaultAutofillPicker extends DefaultAppPickerFragment { 51 52 private static final String TAG = "DefaultAutofillPicker"; 53 54 static final String SETTING = Settings.Secure.AUTOFILL_SERVICE; 55 static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE); 56 57 /** 58 * Extra set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. 59 */ 60 public static final String EXTRA_PACKAGE_NAME = "package_name"; 61 62 /** 63 * Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. 64 */ 65 private DialogInterface.OnClickListener mCancelListener; 66 67 @Override onCreate(Bundle savedInstanceState)68 public void onCreate(Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 71 final Activity activity = getActivity(); 72 if (activity != null && activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME) != null) { 73 mCancelListener = (d, w) -> { 74 activity.setResult(Activity.RESULT_CANCELED); 75 activity.finish(); 76 }; 77 // If mCancelListener is not null, fragment is started from 78 // ACTION_REQUEST_SET_AUTOFILL_SERVICE and we should always use the calling uid. 79 mUserId = UserHandle.myUserId(); 80 } 81 mSettingsPackageMonitor.register(activity, activity.getMainLooper(), false); 82 update(); 83 } 84 85 @Override newConfirmationDialogFragment(String selectedKey, CharSequence confirmationMessage)86 protected ConfirmationDialogFragment newConfirmationDialogFragment(String selectedKey, 87 CharSequence confirmationMessage) { 88 final AutofillPickerConfirmationDialogFragment fragment = 89 new AutofillPickerConfirmationDialogFragment(); 90 fragment.init(this, selectedKey, confirmationMessage); 91 return fragment; 92 } 93 94 /** 95 * Custom dialog fragment that has a cancel listener used to propagate the result back to 96 * caller (for the cases where the picker is launched by 97 * {@code android.settings.REQUEST_SET_AUTOFILL_SERVICE}. 98 */ 99 public static class AutofillPickerConfirmationDialogFragment 100 extends ConfirmationDialogFragment { 101 102 @Override onCreate(Bundle savedInstanceState)103 public void onCreate(Bundle savedInstanceState) { 104 final DefaultAutofillPicker target = (DefaultAutofillPicker) getTargetFragment(); 105 setCancelListener(target.mCancelListener); 106 super.onCreate(savedInstanceState); 107 } 108 } 109 110 @Override getPreferenceScreenResId()111 protected int getPreferenceScreenResId() { 112 return R.xml.default_autofill_settings; 113 } 114 115 @Override getMetricsCategory()116 public int getMetricsCategory() { 117 return SettingsEnums.DEFAULT_AUTOFILL_PICKER; 118 } 119 120 @Override shouldShowItemNone()121 protected boolean shouldShowItemNone() { 122 return true; 123 } 124 125 /** 126 * Monitor coming and going auto fill services and calls {@link #update()} when necessary 127 */ 128 private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() { 129 @Override 130 public void onPackageAdded(String packageName, int uid) { 131 ThreadUtils.postOnMainThread(() -> update()); 132 } 133 134 @Override 135 public void onPackageModified(String packageName) { 136 ThreadUtils.postOnMainThread(() -> update()); 137 } 138 139 @Override 140 public void onPackageRemoved(String packageName, int uid) { 141 ThreadUtils.postOnMainThread(() -> update()); 142 } 143 }; 144 145 /** 146 * Update the data in this UI. 147 */ update()148 private void update() { 149 updateCandidates(); 150 addAddServicePreference(); 151 } 152 153 @Override onDestroy()154 public void onDestroy() { 155 mSettingsPackageMonitor.unregister(); 156 super.onDestroy(); 157 } 158 159 /** 160 * Gets the preference that allows to add a new autofill service. 161 * 162 * @return The preference or {@code null} if no service can be added 163 */ newAddServicePreferenceOrNull()164 private Preference newAddServicePreferenceOrNull() { 165 final String searchUri = Settings.Secure.getStringForUser( 166 getActivity().getContentResolver(), 167 Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI, 168 mUserId); 169 if (TextUtils.isEmpty(searchUri)) { 170 return null; 171 } 172 173 final Intent addNewServiceIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); 174 final Context context = getPrefContext(); 175 final Preference preference = new Preference(context); 176 preference.setOnPreferenceClickListener(p -> { 177 context.startActivityAsUser(addNewServiceIntent, UserHandle.of(mUserId)); 178 return true; 179 }); 180 preference.setTitle(R.string.print_menu_item_add_service); 181 preference.setIcon(R.drawable.ic_add_24dp); 182 preference.setOrder(Integer.MAX_VALUE -1); 183 preference.setPersistent(false); 184 return preference; 185 } 186 187 /** 188 * Add a preference that allows the user to add a service if the market link for that is 189 * configured. 190 */ addAddServicePreference()191 private void addAddServicePreference() { 192 final Preference addNewServicePreference = newAddServicePreferenceOrNull(); 193 if (addNewServicePreference != null) { 194 getPreferenceScreen().addPreference(addNewServicePreference); 195 } 196 } 197 198 @Override getCandidates()199 protected List<DefaultAppInfo> getCandidates() { 200 final List<DefaultAppInfo> candidates = new ArrayList<>(); 201 final List<ResolveInfo> resolveInfos = mPm.queryIntentServicesAsUser( 202 AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId); 203 final Context context = getContext(); 204 for (ResolveInfo info : resolveInfos) { 205 final String permission = info.serviceInfo.permission; 206 if (Manifest.permission.BIND_AUTOFILL_SERVICE.equals(permission)) { 207 candidates.add(new DefaultAppInfo(context, mPm, mUserId, new ComponentName( 208 info.serviceInfo.packageName, info.serviceInfo.name))); 209 } 210 if (Manifest.permission.BIND_AUTOFILL.equals(permission)) { 211 // Let it go for now... 212 Log.w(TAG, "AutofillService from '" + info.serviceInfo.packageName 213 + "' uses unsupported permission " + Manifest.permission.BIND_AUTOFILL 214 + ". It works for now, but might not be supported on future releases"); 215 candidates.add(new DefaultAppInfo(context, mPm, mUserId, new ComponentName( 216 info.serviceInfo.packageName, info.serviceInfo.name))); 217 } 218 } 219 return candidates; 220 } 221 getDefaultKey(Context context, int userId)222 public static String getDefaultKey(Context context, int userId) { 223 String setting = Settings.Secure.getStringForUser( 224 context.getContentResolver(), SETTING, userId); 225 if (setting != null) { 226 ComponentName componentName = ComponentName.unflattenFromString(setting); 227 if (componentName != null) { 228 return componentName.flattenToString(); 229 } 230 } 231 return null; 232 } 233 234 @Override getDefaultKey()235 protected String getDefaultKey() { 236 return getDefaultKey(getContext(), mUserId); 237 } 238 239 @Override getConfirmationMessage(CandidateInfo appInfo)240 protected CharSequence getConfirmationMessage(CandidateInfo appInfo) { 241 if (appInfo == null) { 242 return null; 243 } 244 final CharSequence appName = appInfo.loadLabel(); 245 final String message = getContext().getString( 246 R.string.autofill_confirmation_message, appName); 247 return Html.fromHtml(message); 248 } 249 250 @Override setDefaultKey(String key)251 protected boolean setDefaultKey(String key) { 252 Settings.Secure.putStringForUser(getContext().getContentResolver(), SETTING, key, mUserId); 253 254 // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE 255 // intent, and set proper result if so... 256 final Activity activity = getActivity(); 257 if (activity != null) { 258 final String packageName = activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME); 259 if (packageName != null) { 260 final int result = key != null && key.startsWith(packageName) ? Activity.RESULT_OK 261 : Activity.RESULT_CANCELED; 262 activity.setResult(result); 263 activity.finish(); 264 } 265 } 266 return true; 267 } 268 269 /** 270 * Provides Intent to setting activity for the specified autofill service. 271 */ 272 static final class AutofillSettingIntentProvider implements SettingIntentProvider { 273 274 private final String mSelectedKey; 275 private final Context mContext; 276 private final int mUserId; 277 AutofillSettingIntentProvider(Context context, int userId, String key)278 public AutofillSettingIntentProvider(Context context, int userId, String key) { 279 mSelectedKey = key; 280 mContext = context; 281 mUserId = userId; 282 } 283 284 @Override getIntent()285 public Intent getIntent() { 286 final List<ResolveInfo> resolveInfos = mContext.getPackageManager() 287 .queryIntentServicesAsUser( 288 AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId); 289 290 for (ResolveInfo resolveInfo : resolveInfos) { 291 final ServiceInfo serviceInfo = resolveInfo.serviceInfo; 292 final String flattenKey = new ComponentName( 293 serviceInfo.packageName, serviceInfo.name).flattenToString(); 294 if (TextUtils.equals(mSelectedKey, flattenKey)) { 295 final String settingsActivity; 296 try { 297 settingsActivity = new AutofillServiceInfo(mContext, serviceInfo) 298 .getSettingsActivity(); 299 } catch (SecurityException e) { 300 // Service does not declare the proper permission, ignore it. 301 Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e); 302 return null; 303 } 304 if (TextUtils.isEmpty(settingsActivity)) { 305 return null; 306 } 307 return new Intent(Intent.ACTION_MAIN).setComponent( 308 new ComponentName(serviceInfo.packageName, settingsActivity)); 309 } 310 } 311 312 return null; 313 } 314 } 315 } 316