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.pm.PackageManager;
24import android.content.pm.ServiceInfo;
25import android.content.res.Resources;
26import android.content.res.TypedArray;
27import android.content.res.XmlResourceParser;
28import android.os.RemoteException;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.util.Xml;
32import org.xmlpull.v1.XmlPullParser;
33import org.xmlpull.v1.XmlPullParserException;
34
35import com.android.internal.R;
36
37import java.io.IOException;
38
39/**
40 * {@link ServiceInfo} and meta-data about an {@link AutofillService}.
41 *
42 * @hide
43 */
44public final class AutofillServiceInfo {
45    private static final String TAG = "AutofillServiceInfo";
46
47    private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
48            throws PackageManager.NameNotFoundException {
49        try {
50            ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(
51                    comp,
52                    PackageManager.GET_META_DATA,
53                    userHandle);
54            if (si != null) {
55                return si;
56            }
57        } catch (RemoteException e) {
58        }
59        throw new PackageManager.NameNotFoundException(comp.toString());
60    }
61
62    @NonNull
63    private final ServiceInfo mServiceInfo;
64
65    @Nullable
66    private final String mSettingsActivity;
67
68    public AutofillServiceInfo(PackageManager pm, ComponentName comp, int userHandle)
69            throws PackageManager.NameNotFoundException {
70        this(pm, getServiceInfoOrThrow(comp, userHandle));
71    }
72
73    public AutofillServiceInfo(PackageManager pm, ServiceInfo si) {
74        mServiceInfo = si;
75        final TypedArray metaDataArray = getMetaDataArray(pm, si);
76        if (metaDataArray != null) {
77            mSettingsActivity = metaDataArray
78                    .getString(R.styleable.AutofillService_settingsActivity);
79            metaDataArray.recycle();
80        } else {
81            mSettingsActivity = null;
82        }
83    }
84
85    /**
86     * Gets the meta-data as a {@link TypedArray}, or {@code null} if not provided,
87     * or throws if invalid.
88     */
89    @Nullable
90    private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) {
91        // Check for permissions.
92        // TODO(b/37563972): remove support to BIND_AUTOFILL once clients use BIND_AUTOFILL_SERVICE
93        if (!Manifest.permission.BIND_AUTOFILL_SERVICE.equals(si.permission)
94                && !Manifest.permission.BIND_AUTOFILL.equals(si.permission)) {
95            Log.w(TAG, "AutofillService from '" + si.packageName + "' does not require permission "
96                    + Manifest.permission.BIND_AUTOFILL_SERVICE);
97            throw new SecurityException("Service does not require permission "
98                    + Manifest.permission.BIND_AUTOFILL_SERVICE);
99        }
100
101        // Get the AutoFill metadata, if declared.
102        XmlResourceParser parser = si.loadXmlMetaData(pm, AutofillService.SERVICE_META_DATA);
103        if (parser == null) {
104            return null;
105        }
106
107        // Parse service info and get the <autofill-service> tag as an AttributeSet.
108        AttributeSet attrs;
109        try {
110            // Move the XML parser to the first tag.
111            try {
112                int type;
113                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
114                        && type != XmlPullParser.START_TAG) {
115                }
116            } catch (XmlPullParserException | IOException e) {
117                Log.e(TAG, "Error parsing auto fill service meta-data", e);
118                return null;
119            }
120
121            if (!"autofill-service".equals(parser.getName())) {
122                Log.e(TAG, "Meta-data does not start with autofill-service tag");
123                return null;
124            }
125            attrs = Xml.asAttributeSet(parser);
126
127            // Get resources required to read the AttributeSet.
128            Resources res;
129            try {
130                res = pm.getResourcesForApplication(si.applicationInfo);
131            } catch (PackageManager.NameNotFoundException e) {
132                Log.e(TAG, "Error getting application resources", e);
133                return null;
134            }
135
136            return res.obtainAttributes(attrs, R.styleable.AutofillService);
137        } finally {
138            parser.close();
139        }
140    }
141
142    public ServiceInfo getServiceInfo() {
143        return mServiceInfo;
144    }
145
146    @Nullable
147    public String getSettingsActivity() {
148        return mSettingsActivity;
149    }
150}
151