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