AutofillServiceInfo.java revision a6ebff0f0ba66274b333a157c3f15d7c38527fe5
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 */
16package android.service.autofill;
17
18import android.Manifest;
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.app.AppGlobals;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.pm.PackageManager;
25import android.content.pm.ServiceInfo;
26import android.content.res.Resources;
27import android.content.res.TypedArray;
28import android.content.res.XmlResourceParser;
29import android.metrics.LogMaker;
30import android.os.RemoteException;
31import android.text.TextUtils;
32import android.util.ArrayMap;
33import android.util.AttributeSet;
34import android.util.Log;
35import android.util.Pair;
36import android.util.Xml;
37
38import com.android.internal.R;
39import com.android.internal.logging.MetricsLogger;
40import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
41import com.android.internal.util.XmlUtils;
42
43import org.xmlpull.v1.XmlPullParser;
44import org.xmlpull.v1.XmlPullParserException;
45
46import java.io.IOException;
47import java.io.PrintWriter;
48
49/**
50 * {@link ServiceInfo} and meta-data about an {@link AutofillService}.
51 *
52 * @hide
53 */
54public final class AutofillServiceInfo {
55    private static final String TAG = "AutofillServiceInfo";
56
57    private static final String TAG_AUTOFILL_SERVICE = "autofill-service";
58    private static final String TAG_COMPATIBILITY_PACKAGE = "compatibility-package";
59
60    private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
61            throws PackageManager.NameNotFoundException {
62        try {
63            ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(
64                    comp,
65                    PackageManager.GET_META_DATA,
66                    userHandle);
67            if (si != null) {
68                return si;
69            }
70        } catch (RemoteException e) {
71        }
72        throw new PackageManager.NameNotFoundException(comp.toString());
73    }
74
75    @NonNull
76    private final ServiceInfo mServiceInfo;
77
78    @Nullable
79    private final String mSettingsActivity;
80
81    @Nullable
82    private final ArrayMap<String, Pair<Long, String>> mCompatibilityPackages;
83
84    public AutofillServiceInfo(Context context, ComponentName comp, int userHandle)
85            throws PackageManager.NameNotFoundException {
86        this(context, getServiceInfoOrThrow(comp, userHandle));
87    }
88
89    public AutofillServiceInfo(Context context, ServiceInfo si) {
90        // Check for permissions.
91        if (!Manifest.permission.BIND_AUTOFILL_SERVICE.equals(si.permission)) {
92            if (Manifest.permission.BIND_AUTOFILL.equals(si.permission)) {
93                // Let it go for now...
94                Log.w(TAG, "AutofillService from '" + si.packageName + "' uses unsupported "
95                        + "permission " + Manifest.permission.BIND_AUTOFILL + ". It works for "
96                        + "now, but might not be supported on future releases");
97                new MetricsLogger().write(new LogMaker(MetricsEvent.AUTOFILL_INVALID_PERMISSION)
98                        .setPackageName(si.packageName));
99            } else {
100                Log.w(TAG, "AutofillService from '" + si.packageName
101                        + "' does not require permission "
102                        + Manifest.permission.BIND_AUTOFILL_SERVICE);
103                throw new SecurityException("Service does not require permission "
104                        + Manifest.permission.BIND_AUTOFILL_SERVICE);
105            }
106        }
107
108        mServiceInfo = si;
109
110        // Get the AutoFill metadata, if declared.
111        final XmlResourceParser parser = si.loadXmlMetaData(context.getPackageManager(),
112                AutofillService.SERVICE_META_DATA);
113        if (parser == null) {
114            mSettingsActivity = null;
115            mCompatibilityPackages = null;
116            return;
117        }
118
119        String settingsActivity = null;
120        ArrayMap<String, Pair<Long, String>> compatibilityPackages = null;
121
122        try {
123            final Resources resources = context.getPackageManager().getResourcesForApplication(
124                    si.applicationInfo);
125
126            int type = 0;
127            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
128                type = parser.next();
129            }
130
131            if (TAG_AUTOFILL_SERVICE.equals(parser.getName())) {
132                final AttributeSet allAttributes = Xml.asAttributeSet(parser);
133                TypedArray afsAttributes = null;
134                try {
135                    afsAttributes = resources.obtainAttributes(allAttributes,
136                            com.android.internal.R.styleable.AutofillService);
137                    settingsActivity = afsAttributes.getString(
138                            R.styleable.AutofillService_settingsActivity);
139                } finally {
140                    if (afsAttributes != null) {
141                        afsAttributes.recycle();
142                    }
143                }
144                compatibilityPackages = parseCompatibilityPackages(parser, resources);
145            } else {
146                Log.e(TAG, "Meta-data does not start with autofill-service tag");
147            }
148        } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
149            Log.e(TAG, "Error parsing auto fill service meta-data", e);
150        }
151
152        mSettingsActivity = settingsActivity;
153        mCompatibilityPackages = compatibilityPackages;
154    }
155
156    private ArrayMap<String, Pair<Long, String>> parseCompatibilityPackages(XmlPullParser parser,
157            Resources resources)
158            throws IOException, XmlPullParserException {
159        ArrayMap<String, Pair<Long, String>> compatibilityPackages = null;
160
161        final int outerDepth = parser.getDepth();
162        int type;
163        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
164                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
165            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
166                continue;
167            }
168
169            if (TAG_COMPATIBILITY_PACKAGE.equals(parser.getName())) {
170                TypedArray cpAttributes = null;
171                try {
172                    final AttributeSet allAttributes = Xml.asAttributeSet(parser);
173
174                    cpAttributes = resources.obtainAttributes(allAttributes,
175                           R.styleable.AutofillService_CompatibilityPackage);
176
177                    final String name = cpAttributes.getString(
178                            R.styleable.AutofillService_CompatibilityPackage_name);
179                    if (TextUtils.isEmpty(name)) {
180                        Log.e(TAG, "Invalid compatibility package:" + name);
181                        break;
182                    }
183
184                    final String maxVersionCodeStr = cpAttributes.getString(
185                            R.styleable.AutofillService_CompatibilityPackage_maxLongVersionCode);
186                    final Long maxVersionCode;
187                    if (maxVersionCodeStr != null) {
188                        try {
189                            maxVersionCode = Long.parseLong(maxVersionCodeStr);
190                        } catch (NumberFormatException e) {
191                            Log.e(TAG, "Invalid compatibility max version code:"
192                                    + maxVersionCodeStr);
193                            break;
194                        }
195                        if (maxVersionCode < 0) {
196                            Log.e(TAG, "Invalid compatibility max version code:"
197                                    + maxVersionCode);
198                            break;
199                        }
200                    } else {
201                        maxVersionCode = Long.MAX_VALUE;
202                    }
203                    final String urlBarResourceId = cpAttributes.getString(
204                            R.styleable.AutofillService_CompatibilityPackage_urlBarResourceId);
205
206                    if (compatibilityPackages == null) {
207                        compatibilityPackages = new ArrayMap<>();
208                    }
209                    compatibilityPackages.put(name, new Pair<>(maxVersionCode, urlBarResourceId));
210                } finally {
211                    XmlUtils.skipCurrentTag(parser);
212                    if (cpAttributes != null) {
213                        cpAttributes.recycle();
214                    }
215                }
216            }
217        }
218
219        return compatibilityPackages;
220    }
221
222    public ServiceInfo getServiceInfo() {
223        return mServiceInfo;
224    }
225
226    @Nullable
227    public String getSettingsActivity() {
228        return mSettingsActivity;
229    }
230
231    public ArrayMap<String, Pair<Long, String>> getCompatibilityPackages() {
232        return mCompatibilityPackages;
233    }
234
235    /**
236     * Gets the resource id of the URL bar for a package. Used in compat mode
237     */
238    // TODO: return a list of strings instead
239    @Nullable
240    public String getUrlBarResourceId(String packageName) {
241        if (mCompatibilityPackages == null) {
242            return null;
243        }
244        final Pair<Long, String> pair = mCompatibilityPackages.get(packageName);
245        return pair == null ? null : pair.second;
246    }
247
248    @Override
249    public String toString() {
250        final StringBuilder builder = new StringBuilder();
251        builder.append(getClass().getSimpleName());
252        builder.append("[").append(mServiceInfo);
253        builder.append(", settings:").append(mSettingsActivity);
254        builder.append(", hasCompatPckgs:").append(mCompatibilityPackages != null
255                && !mCompatibilityPackages.isEmpty()).append("]");
256        return builder.toString();
257    }
258
259    /**
260     * Dumps it!
261     */
262    public void dump(String prefix, PrintWriter pw) {
263        pw.print(prefix); pw.print("Component: "); pw.println(getServiceInfo().getComponentName());
264        pw.print(prefix); pw.print("Settings: "); pw.println(mSettingsActivity);
265        pw.print(prefix); pw.print("Compat packages: "); pw.println(mCompatibilityPackages);
266    }
267}
268