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 
17 package android.view.textservice;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.content.pm.ResolveInfo;
23 import android.content.pm.ServiceInfo;
24 import android.content.res.Resources;
25 import android.content.res.TypedArray;
26 import android.content.res.XmlResourceParser;
27 import android.graphics.drawable.Drawable;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.util.AttributeSet;
31 import android.util.PrintWriterPrinter;
32 import android.util.Slog;
33 import android.util.Xml;
34 
35 import org.xmlpull.v1.XmlPullParser;
36 import org.xmlpull.v1.XmlPullParserException;
37 
38 import java.io.IOException;
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 
42 /**
43  * This class is used to specify meta information of a spell checker.
44  */
45 public 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      */
SpellCheckerInfo(Context context, ResolveInfo service)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                     a.recycle();
128                     mSubtypes.add(subtype);
129                 }
130             }
131         } catch (Exception e) {
132             Slog.e(TAG, "Caught exception: " + e);
133             throw new XmlPullParserException(
134                     "Unable to create context for: " + si.packageName);
135         } finally {
136             if (parser != null) parser.close();
137         }
138         mLabel = label;
139         mSettingsActivityName = settingsActivityComponent;
140     }
141 
142     /**
143      * Constructor.
144      * @hide
145      */
SpellCheckerInfo(Parcel source)146     public SpellCheckerInfo(Parcel source) {
147         mLabel = source.readInt();
148         mId = source.readString();
149         mSettingsActivityName = source.readString();
150         mService = ResolveInfo.CREATOR.createFromParcel(source);
151         source.readTypedList(mSubtypes, SpellCheckerSubtype.CREATOR);
152     }
153 
154     /**
155      * Return a unique ID for this spell checker.  The ID is generated from
156      * the package and class name implementing the method.
157      */
getId()158     public String getId() {
159         return mId;
160     }
161 
162     /**
163      * Return the component of the service that implements.
164      */
getComponent()165     public ComponentName getComponent() {
166         return new ComponentName(
167                 mService.serviceInfo.packageName, mService.serviceInfo.name);
168     }
169 
170     /**
171      * Return the .apk package that implements this.
172      */
getPackageName()173     public String getPackageName() {
174         return mService.serviceInfo.packageName;
175     }
176 
177     /**
178      * Used to package this object into a {@link Parcel}.
179      *
180      * @param dest The {@link Parcel} to be written.
181      * @param flags The flags used for parceling.
182      */
183     @Override
writeToParcel(Parcel dest, int flags)184     public void writeToParcel(Parcel dest, int flags) {
185         dest.writeInt(mLabel);
186         dest.writeString(mId);
187         dest.writeString(mSettingsActivityName);
188         mService.writeToParcel(dest, flags);
189         dest.writeTypedList(mSubtypes);
190     }
191 
192 
193     /**
194      * Used to make this class parcelable.
195      */
196     public static final @android.annotation.NonNull Parcelable.Creator<SpellCheckerInfo> CREATOR
197             = new Parcelable.Creator<SpellCheckerInfo>() {
198         @Override
199         public SpellCheckerInfo createFromParcel(Parcel source) {
200             return new SpellCheckerInfo(source);
201         }
202 
203         @Override
204         public SpellCheckerInfo[] newArray(int size) {
205             return new SpellCheckerInfo[size];
206         }
207     };
208 
209     /**
210      * Load the user-displayed label for this spell checker.
211      *
212      * @param pm Supply a PackageManager used to load the spell checker's resources.
213      */
loadLabel(PackageManager pm)214     public CharSequence loadLabel(PackageManager pm) {
215         if (mLabel == 0 || pm == null) return "";
216         return pm.getText(getPackageName(), mLabel, mService.serviceInfo.applicationInfo);
217     }
218 
219     /**
220      * Load the user-displayed icon for this spell checker.
221      *
222      * @param pm Supply a PackageManager used to load the spell checker's resources.
223      */
loadIcon(PackageManager pm)224     public Drawable loadIcon(PackageManager pm) {
225         return mService.loadIcon(pm);
226     }
227 
228 
229     /**
230      * Return the raw information about the Service implementing this
231      * spell checker.  Do not modify the returned object.
232      */
getServiceInfo()233     public ServiceInfo getServiceInfo() {
234         return mService.serviceInfo;
235     }
236 
237     /**
238      * Return the class name of an activity that provides a settings UI.
239      * You can launch this activity be starting it with
240      * an {@link android.content.Intent} whose action is MAIN and with an
241      * explicit {@link android.content.ComponentName}
242      * composed of {@link #getPackageName} and the class name returned here.
243      *
244      * <p>A null will be returned if there is no settings activity.
245      */
getSettingsActivity()246     public String getSettingsActivity() {
247         return mSettingsActivityName;
248     }
249 
250     /**
251      * Return the count of the subtypes.
252      */
getSubtypeCount()253     public int getSubtypeCount() {
254         return mSubtypes.size();
255     }
256 
257     /**
258      * Return the subtype at the specified index.
259      *
260      * @param index the index of the subtype to return.
261      */
getSubtypeAt(int index)262     public SpellCheckerSubtype getSubtypeAt(int index) {
263         return mSubtypes.get(index);
264     }
265 
266     /**
267      * Used to make this class parcelable.
268      */
269     @Override
describeContents()270     public int describeContents() {
271         return 0;
272     }
273 
274     /**
275      * @hide
276      */
dump(final PrintWriter pw, final String prefix)277     public void dump(final PrintWriter pw, final String prefix) {
278         pw.println(prefix + "mId=" + mId);
279         pw.println(prefix + "mSettingsActivityName=" + mSettingsActivityName);
280         pw.println(prefix + "Service:");
281         mService.dump(new PrintWriterPrinter(pw), prefix + "  ");
282         final int N = getSubtypeCount();
283         for (int i = 0; i < N; i++) {
284             final SpellCheckerSubtype st = getSubtypeAt(i);
285             pw.println(prefix + "  " + "Subtype #" + i + ":");
286             pw.println(prefix + "    " + "locale=" + st.getLocale()
287                     + " languageTag=" + st.getLanguageTag());
288             pw.println(prefix + "    " + "extraValue=" + st.getExtraValue());
289         }
290     }
291 }
292