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