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