1 /* 2 * Copyright (C) 2016 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 package android.service.autofill; 17 18 import android.Manifest; 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UserIdInt; 22 import android.app.AppGlobals; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.content.pm.ServiceInfo; 30 import android.content.res.Resources; 31 import android.content.res.TypedArray; 32 import android.content.res.XmlResourceParser; 33 import android.metrics.LogMaker; 34 import android.os.RemoteException; 35 import android.text.TextUtils; 36 import android.util.ArrayMap; 37 import android.util.AttributeSet; 38 import android.util.Log; 39 import android.util.Xml; 40 41 import com.android.internal.R; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.logging.MetricsLogger; 44 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 45 import com.android.internal.util.XmlUtils; 46 47 import org.xmlpull.v1.XmlPullParser; 48 import org.xmlpull.v1.XmlPullParserException; 49 50 import java.io.IOException; 51 import java.io.PrintWriter; 52 import java.util.ArrayList; 53 import java.util.List; 54 55 /** 56 * {@link ServiceInfo} and meta-data about an {@link AutofillService}. 57 * 58 * @hide 59 */ 60 public final class AutofillServiceInfo { 61 private static final String TAG = "AutofillServiceInfo"; 62 63 private static final String TAG_AUTOFILL_SERVICE = "autofill-service"; 64 private static final String TAG_COMPATIBILITY_PACKAGE = "compatibility-package"; 65 66 private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME = 67 new ComponentName("com.android.credentialmanager", 68 "com.android.credentialmanager.autofill.CredentialAutofillService"); 69 getServiceInfoOrThrow(ComponentName comp, int userHandle)70 private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle) 71 throws PackageManager.NameNotFoundException { 72 try { 73 ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo( 74 comp, 75 PackageManager.GET_META_DATA, 76 userHandle); 77 if (si != null) { 78 return si; 79 } 80 } catch (RemoteException e) { 81 } 82 throw new PackageManager.NameNotFoundException(comp.toString()); 83 } 84 85 @NonNull 86 private final ServiceInfo mServiceInfo; 87 88 @Nullable 89 private final String mSettingsActivity; 90 @Nullable 91 private final String mPasswordsActivity; 92 93 @Nullable 94 private final ArrayMap<String, Long> mCompatibilityPackages; 95 96 private final boolean mInlineSuggestionsEnabled; 97 AutofillServiceInfo(Context context, ComponentName comp, int userHandle)98 public AutofillServiceInfo(Context context, ComponentName comp, int userHandle) 99 throws PackageManager.NameNotFoundException { 100 this(context, getServiceInfoOrThrow(comp, userHandle)); 101 } 102 AutofillServiceInfo(Context context, ServiceInfo si)103 public AutofillServiceInfo(Context context, ServiceInfo si) { 104 // Check for permissions. 105 if (!Manifest.permission.BIND_AUTOFILL_SERVICE.equals(si.permission)) { 106 if (Manifest.permission.BIND_AUTOFILL.equals(si.permission)) { 107 // Let it go for now... 108 Log.w(TAG, "AutofillService from '" + si.packageName + "' uses unsupported " 109 + "permission " + Manifest.permission.BIND_AUTOFILL + ". It works for " 110 + "now, but might not be supported on future releases"); 111 new MetricsLogger().write(new LogMaker(MetricsEvent.AUTOFILL_INVALID_PERMISSION) 112 .setPackageName(si.packageName)); 113 } else { 114 Log.w(TAG, "AutofillService from '" + si.packageName 115 + "' does not require permission " 116 + Manifest.permission.BIND_AUTOFILL_SERVICE); 117 throw new SecurityException("Service does not require permission " 118 + Manifest.permission.BIND_AUTOFILL_SERVICE); 119 } 120 } 121 122 mServiceInfo = si; 123 124 // Get the AutoFill metadata, if declared. 125 final XmlResourceParser parser = si.loadXmlMetaData(context.getPackageManager(), 126 AutofillService.SERVICE_META_DATA); 127 if (parser == null) { 128 mSettingsActivity = null; 129 mPasswordsActivity = null; 130 mCompatibilityPackages = null; 131 mInlineSuggestionsEnabled = false; 132 return; 133 } 134 135 String settingsActivity = null; 136 String passwordsActivity = null; 137 ArrayMap<String, Long> compatibilityPackages = null; 138 boolean inlineSuggestionsEnabled = false; // false by default. 139 140 try { 141 final Resources resources = context.getPackageManager().getResourcesForApplication( 142 si.applicationInfo); 143 144 int type = 0; 145 while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { 146 type = parser.next(); 147 } 148 149 if (TAG_AUTOFILL_SERVICE.equals(parser.getName())) { 150 final AttributeSet allAttributes = Xml.asAttributeSet(parser); 151 TypedArray afsAttributes = null; 152 try { 153 afsAttributes = resources.obtainAttributes(allAttributes, 154 com.android.internal.R.styleable.AutofillService); 155 settingsActivity = afsAttributes.getString( 156 R.styleable.AutofillService_settingsActivity); 157 passwordsActivity = afsAttributes.getString( 158 R.styleable.AutofillService_passwordsActivity); 159 inlineSuggestionsEnabled = afsAttributes.getBoolean( 160 R.styleable.AutofillService_supportsInlineSuggestions, false); 161 } finally { 162 if (afsAttributes != null) { 163 afsAttributes.recycle(); 164 } 165 } 166 compatibilityPackages = parseCompatibilityPackages(parser, resources); 167 } else { 168 Log.e(TAG, "Meta-data does not start with autofill-service tag"); 169 } 170 } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) { 171 Log.e(TAG, "Error parsing auto fill service meta-data", e); 172 } 173 174 mSettingsActivity = settingsActivity; 175 mPasswordsActivity = passwordsActivity; 176 mCompatibilityPackages = compatibilityPackages; 177 mInlineSuggestionsEnabled = inlineSuggestionsEnabled; 178 } 179 parseCompatibilityPackages(XmlPullParser parser, Resources resources)180 private ArrayMap<String, Long> parseCompatibilityPackages(XmlPullParser parser, 181 Resources resources) throws IOException, XmlPullParserException { 182 ArrayMap<String, Long> compatibilityPackages = null; 183 184 final int outerDepth = parser.getDepth(); 185 int type; 186 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 187 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 188 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 189 continue; 190 } 191 192 if (TAG_COMPATIBILITY_PACKAGE.equals(parser.getName())) { 193 TypedArray cpAttributes = null; 194 try { 195 final AttributeSet allAttributes = Xml.asAttributeSet(parser); 196 197 cpAttributes = resources.obtainAttributes(allAttributes, 198 R.styleable.AutofillService_CompatibilityPackage); 199 200 final String name = cpAttributes.getString( 201 R.styleable.AutofillService_CompatibilityPackage_name); 202 if (TextUtils.isEmpty(name)) { 203 Log.e(TAG, "Invalid compatibility package:" + name); 204 break; 205 } 206 207 final String maxVersionCodeStr = cpAttributes.getString( 208 R.styleable.AutofillService_CompatibilityPackage_maxLongVersionCode); 209 final Long maxVersionCode; 210 if (maxVersionCodeStr != null) { 211 try { 212 maxVersionCode = Long.parseLong(maxVersionCodeStr); 213 } catch (NumberFormatException e) { 214 Log.e(TAG, "Invalid compatibility max version code:" 215 + maxVersionCodeStr); 216 break; 217 } 218 if (maxVersionCode < 0) { 219 Log.e(TAG, "Invalid compatibility max version code:" 220 + maxVersionCode); 221 break; 222 } 223 } else { 224 maxVersionCode = Long.MAX_VALUE; 225 } 226 if (compatibilityPackages == null) { 227 compatibilityPackages = new ArrayMap<>(); 228 } 229 compatibilityPackages.put(name, maxVersionCode); 230 } finally { 231 XmlUtils.skipCurrentTag(parser); 232 if (cpAttributes != null) { 233 cpAttributes.recycle(); 234 } 235 } 236 } 237 } 238 239 return compatibilityPackages; 240 } 241 242 /** 243 * Used by {@link TestDataBuilder}. 244 */ AutofillServiceInfo(String passwordsActivity)245 private AutofillServiceInfo(String passwordsActivity) { 246 mServiceInfo = new ServiceInfo(); 247 mServiceInfo.applicationInfo = new ApplicationInfo(); 248 mServiceInfo.packageName = "com.android.test"; 249 mSettingsActivity = null; 250 mPasswordsActivity = passwordsActivity; 251 mCompatibilityPackages = null; 252 mInlineSuggestionsEnabled = false; 253 } 254 255 /** 256 * Builds test data for unit tests. 257 */ 258 @VisibleForTesting 259 public static final class TestDataBuilder { 260 private String mPasswordsActivity; 261 TestDataBuilder()262 public TestDataBuilder() { 263 } 264 setPasswordsActivity(String passwordsActivity)265 public TestDataBuilder setPasswordsActivity(String passwordsActivity) { 266 mPasswordsActivity = passwordsActivity; 267 return this; 268 } 269 build()270 public AutofillServiceInfo build() { 271 return new AutofillServiceInfo(mPasswordsActivity); 272 } 273 } 274 275 @NonNull getServiceInfo()276 public ServiceInfo getServiceInfo() { 277 return mServiceInfo; 278 } 279 280 @Nullable getSettingsActivity()281 public String getSettingsActivity() { 282 return mSettingsActivity; 283 } 284 285 @Nullable getPasswordsActivity()286 public String getPasswordsActivity() { 287 return mPasswordsActivity; 288 } 289 290 @Nullable getCompatibilityPackages()291 public ArrayMap<String, Long> getCompatibilityPackages() { 292 return mCompatibilityPackages; 293 } 294 isInlineSuggestionsEnabled()295 public boolean isInlineSuggestionsEnabled() { 296 return mInlineSuggestionsEnabled; 297 } 298 299 /** 300 * Queries the valid autofill services available for the user. 301 */ getAvailableServices( Context context, @UserIdInt int user)302 public static List<AutofillServiceInfo> getAvailableServices( 303 Context context, @UserIdInt int user) { 304 final List<AutofillServiceInfo> services = new ArrayList<>(); 305 306 final List<ResolveInfo> resolveInfos = 307 context.getPackageManager().queryIntentServicesAsUser( 308 new Intent(AutofillService.SERVICE_INTERFACE), 309 PackageManager.GET_META_DATA, 310 user); 311 for (ResolveInfo resolveInfo : resolveInfos) { 312 final ServiceInfo serviceInfo = resolveInfo.serviceInfo; 313 try { 314 if (serviceInfo != null && isCredentialManagerAutofillService( 315 context, 316 serviceInfo.getComponentName())) { 317 // Skip this service as it is for internal use only 318 continue; 319 } 320 services.add(new AutofillServiceInfo(context, serviceInfo)); 321 } catch (SecurityException e) { 322 // Service does not declare the proper permission, ignore it. 323 Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e); 324 } 325 } 326 return services; 327 } 328 isCredentialManagerAutofillService(Context context, ComponentName componentName)329 private static boolean isCredentialManagerAutofillService(Context context, 330 ComponentName componentName) { 331 if (componentName == null) { 332 return false; 333 } 334 ComponentName credAutofillService = null; 335 String credentialManagerAutofillCompName = context.getResources().getString( 336 R.string.config_defaultCredentialManagerAutofillService); 337 if (credentialManagerAutofillCompName != null && !credentialManagerAutofillCompName 338 .isEmpty()) { 339 credAutofillService = ComponentName.unflattenFromString( 340 credentialManagerAutofillCompName); 341 } else { 342 Log.w(TAG, "Invalid CredentialAutofillService"); 343 } 344 345 return componentName.equals(credAutofillService); 346 } 347 348 @Override toString()349 public String toString() { 350 final StringBuilder builder = new StringBuilder(); 351 builder.append(getClass().getSimpleName()); 352 builder.append("[").append(mServiceInfo); 353 builder.append(", settings:").append(mSettingsActivity); 354 builder.append(", passwords activity:").append(mPasswordsActivity); 355 builder.append(", hasCompatPckgs:").append(mCompatibilityPackages != null 356 && !mCompatibilityPackages.isEmpty()).append("]"); 357 builder.append(", inline suggestions enabled:").append(mInlineSuggestionsEnabled); 358 return builder.toString(); 359 } 360 361 /** 362 * Dumps it! 363 */ dump(String prefix, PrintWriter pw)364 public void dump(String prefix, PrintWriter pw) { 365 pw.print(prefix); pw.print("Component: "); pw.println(getServiceInfo().getComponentName()); 366 pw.print(prefix); pw.print("Settings: "); pw.println(mSettingsActivity); 367 pw.print(prefix); pw.print("Passwords activity: "); pw.println(mPasswordsActivity); 368 pw.print(prefix); pw.print("Compat packages: "); pw.println(mCompatibilityPackages); 369 pw.print(prefix); pw.print("Inline Suggestions Enabled: "); 370 pw.println(mInlineSuggestionsEnabled); 371 } 372 } 373