1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package android.view.textservice;
18
19import org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.content.pm.ServiceInfo;
27import android.content.res.Resources;
28import android.content.res.TypedArray;
29import android.content.res.XmlResourceParser;
30import android.graphics.drawable.Drawable;
31import android.os.Parcel;
32import android.os.Parcelable;
33import android.util.AttributeSet;
34import android.util.PrintWriterPrinter;
35import android.util.Slog;
36import android.util.Xml;
37
38import java.io.IOException;
39import java.io.PrintWriter;
40import java.util.ArrayList;
41
42/**
43 * This class is used to specify meta information of a spell checker.
44 */
45public final class SpellCheckerInfo implements Parcelable {
46    private static final String TAG = SpellCheckerInfo.class.getSimpleName();
47    private final ResolveInfo mService;
48    private final String mId;
49    private final int mLabel;
50
51    /**
52     * The spell checker setting activity's name, used by the system settings to
53     * launch the setting activity.
54     */
55    private final String mSettingsActivityName;
56
57    /**
58     * The array of subtypes.
59     */
60    private final ArrayList<SpellCheckerSubtype> mSubtypes = new ArrayList<>();
61
62    /**
63     * Constructor.
64     * @hide
65     */
66    public SpellCheckerInfo(Context context, ResolveInfo service)
67            throws XmlPullParserException, IOException {
68        mService = service;
69        ServiceInfo si = service.serviceInfo;
70        mId = new ComponentName(si.packageName, si.name).flattenToShortString();
71
72        final PackageManager pm = context.getPackageManager();
73        int label = 0;
74        String settingsActivityComponent = null;
75
76        XmlResourceParser parser = null;
77        try {
78            parser = si.loadXmlMetaData(pm, SpellCheckerSession.SERVICE_META_DATA);
79            if (parser == null) {
80                throw new XmlPullParserException("No "
81                        + SpellCheckerSession.SERVICE_META_DATA + " meta-data");
82            }
83
84            final Resources res = pm.getResourcesForApplication(si.applicationInfo);
85            final AttributeSet attrs = Xml.asAttributeSet(parser);
86            int type;
87            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
88                    && type != XmlPullParser.START_TAG) {
89            }
90
91            final String nodeName = parser.getName();
92            if (!"spell-checker".equals(nodeName)) {
93                throw new XmlPullParserException(
94                        "Meta-data does not start with spell-checker tag");
95            }
96
97            TypedArray sa = res.obtainAttributes(attrs,
98                    com.android.internal.R.styleable.SpellChecker);
99            label = sa.getResourceId(com.android.internal.R.styleable.SpellChecker_label, 0);
100            settingsActivityComponent = sa.getString(
101                    com.android.internal.R.styleable.SpellChecker_settingsActivity);
102            sa.recycle();
103
104            final int depth = parser.getDepth();
105            // Parse all subtypes
106            while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
107                    && type != XmlPullParser.END_DOCUMENT) {
108                if (type == XmlPullParser.START_TAG) {
109                    final String subtypeNodeName = parser.getName();
110                    if (!"subtype".equals(subtypeNodeName)) {
111                        throw new XmlPullParserException(
112                                "Meta-data in spell-checker does not start with subtype tag");
113                    }
114                    final TypedArray a = res.obtainAttributes(
115                            attrs, com.android.internal.R.styleable.SpellChecker_Subtype);
116                    SpellCheckerSubtype subtype = new SpellCheckerSubtype(
117                            a.getResourceId(com.android.internal.R.styleable
118                                    .SpellChecker_Subtype_label, 0),
119                            a.getString(com.android.internal.R.styleable
120                                    .SpellChecker_Subtype_subtypeLocale),
121                            a.getString(com.android.internal.R.styleable
122                                    .SpellChecker_Subtype_languageTag),
123                            a.getString(com.android.internal.R.styleable
124                                    .SpellChecker_Subtype_subtypeExtraValue),
125                            a.getInt(com.android.internal.R.styleable
126                                    .SpellChecker_Subtype_subtypeId, 0));
127                    mSubtypes.add(subtype);
128                }
129            }
130        } catch (Exception e) {
131            Slog.e(TAG, "Caught exception: " + e);
132            throw new XmlPullParserException(
133                    "Unable to create context for: " + si.packageName);
134        } finally {
135            if (parser != null) parser.close();
136        }
137        mLabel = label;
138        mSettingsActivityName = settingsActivityComponent;
139    }
140
141    /**
142     * Constructor.
143     * @hide
144     */
145    public SpellCheckerInfo(Parcel source) {
146        mLabel = source.readInt();
147        mId = source.readString();
148        mSettingsActivityName = source.readString();
149        mService = ResolveInfo.CREATOR.createFromParcel(source);
150        source.readTypedList(mSubtypes, SpellCheckerSubtype.CREATOR);
151    }
152
153    /**
154     * Return a unique ID for this spell checker.  The ID is generated from
155     * the package and class name implementing the method.
156     */
157    public String getId() {
158        return mId;
159    }
160
161    /**
162     * Return the component of the service that implements.
163     */
164    public ComponentName getComponent() {
165        return new ComponentName(
166                mService.serviceInfo.packageName, mService.serviceInfo.name);
167    }
168
169    /**
170     * Return the .apk package that implements this.
171     */
172    public String getPackageName() {
173        return mService.serviceInfo.packageName;
174    }
175
176    /**
177     * Used to package this object into a {@link Parcel}.
178     *
179     * @param dest The {@link Parcel} to be written.
180     * @param flags The flags used for parceling.
181     */
182    @Override
183    public void writeToParcel(Parcel dest, int flags) {
184        dest.writeInt(mLabel);
185        dest.writeString(mId);
186        dest.writeString(mSettingsActivityName);
187        mService.writeToParcel(dest, flags);
188        dest.writeTypedList(mSubtypes);
189    }
190
191
192    /**
193     * Used to make this class parcelable.
194     */
195    public static final Parcelable.Creator<SpellCheckerInfo> CREATOR
196            = new Parcelable.Creator<SpellCheckerInfo>() {
197        @Override
198        public SpellCheckerInfo createFromParcel(Parcel source) {
199            return new SpellCheckerInfo(source);
200        }
201
202        @Override
203        public SpellCheckerInfo[] newArray(int size) {
204            return new SpellCheckerInfo[size];
205        }
206    };
207
208    /**
209     * Load the user-displayed label for this spell checker.
210     *
211     * @param pm Supply a PackageManager used to load the spell checker's resources.
212     */
213    public CharSequence loadLabel(PackageManager pm) {
214        if (mLabel == 0 || pm == null) return "";
215        return pm.getText(getPackageName(), mLabel, mService.serviceInfo.applicationInfo);
216    }
217
218    /**
219     * Load the user-displayed icon for this spell checker.
220     *
221     * @param pm Supply a PackageManager used to load the spell checker's resources.
222     */
223    public Drawable loadIcon(PackageManager pm) {
224        return mService.loadIcon(pm);
225    }
226
227
228    /**
229     * Return the raw information about the Service implementing this
230     * spell checker.  Do not modify the returned object.
231     */
232    public ServiceInfo getServiceInfo() {
233        return mService.serviceInfo;
234    }
235
236    /**
237     * Return the class name of an activity that provides a settings UI.
238     * You can launch this activity be starting it with
239     * an {@link android.content.Intent} whose action is MAIN and with an
240     * explicit {@link android.content.ComponentName}
241     * composed of {@link #getPackageName} and the class name returned here.
242     *
243     * <p>A null will be returned if there is no settings activity.
244     */
245    public String getSettingsActivity() {
246        return mSettingsActivityName;
247    }
248
249    /**
250     * Return the count of the subtypes.
251     */
252    public int getSubtypeCount() {
253        return mSubtypes.size();
254    }
255
256    /**
257     * Return the subtype at the specified index.
258     *
259     * @param index the index of the subtype to return.
260     */
261    public SpellCheckerSubtype getSubtypeAt(int index) {
262        return mSubtypes.get(index);
263    }
264
265    /**
266     * Used to make this class parcelable.
267     */
268    @Override
269    public int describeContents() {
270        return 0;
271    }
272
273    /**
274     * @hide
275     */
276    public void dump(final PrintWriter pw, final String prefix) {
277        pw.println(prefix + "mId=" + mId);
278        pw.println(prefix + "mSettingsActivityName=" + mSettingsActivityName);
279        pw.println(prefix + "Service:");
280        mService.dump(new PrintWriterPrinter(pw), prefix + "  ");
281        final int N = getSubtypeCount();
282        for (int i = 0; i < N; i++) {
283            final SpellCheckerSubtype st = getSubtypeAt(i);
284            pw.println(prefix + "  " + "Subtype #" + i + ":");
285            pw.println(prefix + "    " + "locale=" + st.getLocale()
286                    + " languageTag=" + st.getLanguageTag());
287            pw.println(prefix + "    " + "extraValue=" + st.getExtraValue());
288        }
289    }
290}
291