Merge "API to set additional subtypes"

This commit is contained in:
satok
2011-06-10 00:26:52 -07:00
committed by Android (Google) Code Review
5 changed files with 302 additions and 8 deletions

View File

@@ -23314,6 +23314,7 @@ package android.view.inputmethod {
method public boolean isWatchingCursor(android.view.View);
method public void restartInput(android.view.View);
method public void sendAppPrivateCommand(android.view.View, java.lang.String, android.os.Bundle);
method public boolean setAdditionalInputMethodSubtypes(android.os.IBinder, android.view.inputmethod.InputMethodSubtype[]);
method public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
method public void setInputMethod(android.os.IBinder, java.lang.String);
method public void setInputMethodAndSubtype(android.os.IBinder, java.lang.String, android.view.inputmethod.InputMethodSubtype);

View File

@@ -23,9 +23,9 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -38,6 +38,8 @@ import android.util.Xml;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* This class is used to specify meta information of an input method.
@@ -77,13 +79,28 @@ public final class InputMethodInfo implements Parcelable {
/**
* Constructor.
*
*
* @param context The Context in which we are parsing the input method.
* @param service The ResolveInfo returned from the package manager about
* this input method's component.
*/
public InputMethodInfo(Context context, ResolveInfo service)
throws XmlPullParserException, IOException {
this(context, service, null);
}
/**
* Constructor.
*
* @param context The Context in which we are parsing the input method.
* @param service The ResolveInfo returned from the package manager about
* this input method's component.
* @param additionalSubtypes additional subtypes being added to this InputMethodInfo
* @hide
*/
public InputMethodInfo(Context context, ResolveInfo service,
Map<String, List<InputMethodSubtype>> additionalSubtypesMap)
throws XmlPullParserException, IOException {
mService = service;
ServiceInfo si = service.serviceInfo;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
@@ -157,6 +174,17 @@ public final class InputMethodInfo implements Parcelable {
} finally {
if (parser != null) parser.close();
}
if (additionalSubtypesMap != null && additionalSubtypesMap.containsKey(mId)) {
final List<InputMethodSubtype> additionalSubtypes = additionalSubtypesMap.get(mId);
final int N = additionalSubtypes.size();
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = additionalSubtypes.get(i);
if (!mSubtypes.contains(subtype)) {
mSubtypes.add(subtype);
}
}
}
mSettingsActivityName = settingsActivityComponent;
mIsDefaultResId = isDefaultResId;
}

View File

@@ -1558,6 +1558,24 @@ public final class InputMethodManager {
}
}
/**
* Set additional input method subtypes.
* @param imeToken Supplies the identifying token given to an input method.
* @param subtypes subtypes will be added as additional subtypes of the current input method.
* @return true if the additional input method subtypes are successfully added.
*/
public boolean setAdditionalInputMethodSubtypes(
IBinder imeToken, InputMethodSubtype[] subtypes) {
synchronized (mH) {
try {
return mService.setAdditionalInputMethodSubtypes(imeToken, subtypes);
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
return false;
}
}
}
public InputMethodSubtype getLastInputMethodSubtype() {
synchronized (mH) {
try {

View File

@@ -68,4 +68,5 @@ interface IInputMethodManager {
boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
boolean switchToLastInputMethod(in IBinder token);
boolean setInputMethodEnabled(String id, boolean enabled);
boolean setAdditionalInputMethodSubtypes(in IBinder token, in InputMethodSubtype[] subtypes);
}

View File

@@ -17,7 +17,9 @@
package com.android.server;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.AtomicFile;
import com.android.internal.os.HandlerCaller;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodCallback;
@@ -25,10 +27,11 @@ import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.InputBindResult;
import com.android.server.EventLogTags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.app.ActivityManagerNative;
import android.app.AlertDialog;
@@ -37,9 +40,9 @@ import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.IntentFilter;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -51,6 +54,7 @@ import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.inputmethodservice.InputMethodService;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
@@ -68,9 +72,10 @@ import android.text.style.SuggestionSpan;
import android.util.EventLog;
import android.util.LruCache;
import android.util.Pair;
import android.util.Slog;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
import android.util.Xml;
import android.view.IWindowManager;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
@@ -80,15 +85,19 @@ import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
@@ -134,6 +143,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final StatusBarManagerService mStatusBar;
final IWindowManager mIWindowManager;
final HandlerCaller mCaller;
private final InputMethodFileManager mFileManager;
final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
@@ -407,10 +417,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (curInputMethodId != null) {
for (int i=0; i<N; i++) {
InputMethodInfo imi = mMethodList.get(i);
if (imi.getId().equals(curInputMethodId)) {
final String imiId = imi.getId();
if (imiId.equals(curInputMethodId)) {
curIm = imi;
}
int change = isPackageDisappearing(imi.getPackageName());
if (isPackageModified(imi.getPackageName())) {
mFileManager.deleteAllInputMethodSubtypes(imiId);
}
if (change == PACKAGE_TEMPORARY_CHANGE
|| change == PACKAGE_PERMANENT_CHANGE) {
Slog.i(TAG, "Input method uninstalled, disabling: "
@@ -471,9 +486,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mMethod = method;
}
@Override
public void finishedEvent(int seq, boolean handled) throws RemoteException {
}
@Override
public void sessionCreated(IInputMethodSession session) throws RemoteException {
onSessionCreated(mMethod, session);
}
@@ -486,10 +503,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
@Override
public void executeMessage(Message msg) {
handleMessage(msg);
}
});
synchronized (mMethodMap) {
mFileManager = new InputMethodFileManager(mMethodMap);
}
(new MyPackageMonitor()).register(mContext, true);
@@ -566,12 +587,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
@Override
public List<InputMethodInfo> getInputMethodList() {
synchronized (mMethodMap) {
return new ArrayList<InputMethodInfo>(mMethodList);
}
}
@Override
public List<InputMethodInfo> getEnabledInputMethodList() {
synchronized (mMethodMap) {
return mSettings.getEnabledInputMethodListLocked();
@@ -602,6 +625,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return InputMethodSubtype.sort(mContext, 0, imi, enabledSubtypes);
}
@Override
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
boolean allowsImplicitlySelectedSubtypes) {
synchronized (mMethodMap) {
@@ -609,6 +633,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
@Override
public void addClient(IInputMethodClient client,
IInputContext inputContext, int uid, int pid) {
synchronized (mMethodMap) {
@@ -617,6 +642,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
@Override
public void removeClient(IInputMethodClient client) {
synchronized (mMethodMap) {
mClients.remove(client.asBinder());
@@ -840,6 +866,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return null;
}
@Override
public InputBindResult startInput(IInputMethodClient client,
IInputContext inputContext, EditorInfo attribute,
boolean initial, boolean needResult) {
@@ -854,9 +881,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
@Override
public void finishInput(IInputMethodClient client) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
@@ -947,6 +976,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mStatusBar.setIconVisibility("ime", false);
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mMethodMap) {
if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
@@ -1012,6 +1042,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
@Override
public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
synchronized (mMethodMap) {
final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
@@ -1025,6 +1056,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
@Override
public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
synchronized (mMethodMap) {
final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
@@ -1485,6 +1517,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
@Override
public InputMethodSubtype getLastInputMethodSubtype() {
synchronized (mMethodMap) {
final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
@@ -1503,6 +1536,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
@Override
public boolean setAdditionalInputMethodSubtypes(IBinder token, InputMethodSubtype[] subtypes) {
if (token == null || mCurToken != token) {
return false;
}
if (subtypes == null || subtypes.length == 0) return false;
synchronized (mMethodMap) {
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
if (imi == null) return false;
final int N = subtypes.length;
mFileManager.addInputMethodSubtypes(mCurMethodId, subtypes);
buildInputMethodListLocked(mMethodList, mMethodMap);
return true;
}
}
private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
synchronized (mMethodMap) {
if (token == null) {
@@ -1749,6 +1798,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
new Intent(InputMethod.SERVICE_INTERFACE),
PackageManager.GET_META_DATA);
final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
mFileManager.getAllAdditionalInputMethodSubtypes();
for (int i = 0; i < services.size(); ++i) {
ResolveInfo ri = services.get(i);
ServiceInfo si = ri.serviceInfo;
@@ -1764,7 +1815,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) Slog.d(TAG, "Checking " + compName);
try {
InputMethodInfo p = new InputMethodInfo(mContext, ri);
InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
list.add(p);
final String id = p.getId();
map.put(id, p);
@@ -2399,6 +2450,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
// TODO: We should change the return type from List to List<Parcelable>
@Override
public List getShortcutInputMethodsAndSubtypes() {
synchronized (mMethodMap) {
ArrayList<Object> ret = new ArrayList<Object>();
@@ -2821,6 +2873,200 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
private static class InputMethodFileManager {
private static final String SYSTEM_PATH = "system";
private static final String INPUT_METHOD_PATH = "inputmethod";
private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
private static final String NODE_SUBTYPES = "subtypes";
private static final String NODE_SUBTYPE = "subtype";
private static final String NODE_IMI = "imi";
private static final String ATTR_ID = "id";
private static final String ATTR_LABEL = "label";
private static final String ATTR_ICON = "icon";
private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
private final AtomicFile mAdditionalInputMethodSubtypeFile;
private final HashMap<String, InputMethodInfo> mMethodMap;
private final HashMap<String, List<InputMethodSubtype>> mSubtypesMap =
new HashMap<String, List<InputMethodSubtype>>();
public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap) {
if (methodMap == null) {
throw new NullPointerException("methodMap is null");
}
mMethodMap = methodMap;
final File systemDir = new File(Environment.getDataDirectory(), SYSTEM_PATH);
final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
if (!inputMethodDir.mkdirs()) {
Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
}
final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
if (!subtypeFile.exists()) {
// If "subtypes.xml" doesn't exist, create a blank file.
writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
methodMap);
} else {
readAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile);
}
}
private void deleteAllInputMethodSubtypes(String imiId) {
synchronized (mMethodMap) {
mSubtypesMap.remove(imiId);
writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
mMethodMap);
}
}
public void addInputMethodSubtypes(
String imiId, InputMethodSubtype[] additionalSubtypes) {
synchronized (mMethodMap) {
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
final int N = additionalSubtypes.length;
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = additionalSubtypes[i];
if (!subtypes.contains(subtype)) {
subtypes.add(subtype);
}
}
mSubtypesMap.put(imiId, subtypes);
writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
mMethodMap);
}
}
public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
synchronized (mMethodMap) {
return mSubtypesMap;
}
}
private static void writeAdditionalInputMethodSubtypes(
HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
HashMap<String, InputMethodInfo> methodMap) {
// Safety net for the case that this function is called before methodMap is set.
final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
FileOutputStream fos = null;
try {
fos = subtypesFile.startWrite();
final XmlSerializer out = new FastXmlSerializer();
out.setOutput(fos, "utf-8");
out.startDocument(null, true);
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
out.startTag(null, NODE_SUBTYPES);
for (String imiId : allSubtypes.keySet()) {
if (isSetMethodMap && !methodMap.containsKey(imiId)) {
Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
continue;
}
out.startTag(null, NODE_IMI);
out.attribute(null, ATTR_ID, imiId);
final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
final int N = subtypesList.size();
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = subtypesList.get(i);
out.startTag(null, NODE_SUBTYPE);
out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
out.attribute(null, ATTR_IS_AUXILIARY,
String.valueOf(subtype.isAuxiliary() ? 1 : 0));
out.endTag(null, NODE_SUBTYPE);
}
out.endTag(null, NODE_IMI);
}
out.endTag(null, NODE_SUBTYPES);
out.endDocument();
subtypesFile.finishWrite(fos);
} catch (java.io.IOException e) {
Slog.w(TAG, "Error writing subtypes", e);
if (fos != null) {
subtypesFile.failWrite(fos);
}
}
}
private static void readAdditionalInputMethodSubtypes(
HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
if (allSubtypes == null || subtypesFile == null) return;
allSubtypes.clear();
FileInputStream fis = null;
try {
fis = subtypesFile.openRead();
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
int type = parser.getEventType();
// Skip parsing until START_TAG
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {}
String firstNodeName = parser.getName();
if (!NODE_SUBTYPES.equals(firstNodeName)) {
throw new XmlPullParserException("Xml doesn't start with subtypes");
}
final int depth =parser.getDepth();
String currentImiId = null;
ArrayList<InputMethodSubtype> tempSubtypesArray = null;
while (((type = parser.next()) != XmlPullParser.END_TAG
|| parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG)
continue;
final String nodeName = parser.getName();
if (NODE_IMI.equals(nodeName)) {
currentImiId = parser.getAttributeValue(null, ATTR_ID);
if (TextUtils.isEmpty(currentImiId)) {
Slog.w(TAG, "Invalid imi id found in subtypes.xml");
continue;
}
tempSubtypesArray = new ArrayList<InputMethodSubtype>();
allSubtypes.put(currentImiId, tempSubtypesArray);
} else if (NODE_SUBTYPE.equals(nodeName)) {
if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
continue;
}
final int icon = Integer.valueOf(
parser.getAttributeValue(null, ATTR_ICON));
final int label = Integer.valueOf(
parser.getAttributeValue(null, ATTR_LABEL));
final String imeSubtypeLocale =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
final String imeSubtypeMode =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
final String imeSubtypeExtraValue =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
final boolean isAuxiliary =
Boolean.valueOf(parser.getAttributeValue(null, ATTR_IS_AUXILIARY));
final InputMethodSubtype subtype =
new InputMethodSubtype(label, icon, imeSubtypeLocale,
imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
tempSubtypesArray.add(subtype);
}
}
} catch (XmlPullParserException e) {
Slog.w(TAG, "Error reading subtypes: " + e);
return;
} catch (java.io.IOException e) {
Slog.w(TAG, "Error reading subtypes: " + e);
return;
} catch (NumberFormatException e) {
Slog.w(TAG, "Error reading subtypes: " + e);
return;
} finally {
if (fis != null) {
try {
fis.close();
} catch (java.io.IOException e1) {
Slog.w(TAG, "Failed to close.");
}
}
}
}
}
// ----------------------------------------------------------------------
@Override