Merge "Do not turn on imes unexpectedly with unit tests"

This commit is contained in:
Satoshi Kataoka
2013-01-30 02:41:46 +00:00
committed by Android (Google) Code Review
7 changed files with 366 additions and 34 deletions

View File

@ -80,6 +80,11 @@ public final class InputMethodInfo implements Parcelable {
private boolean mIsAuxIme;
/**
* Cavert: mForceDefault must be false for production. This flag is only for test.
*/
private final boolean mForceDefault;
/**
* Constructor.
*
@ -108,6 +113,7 @@ public final class InputMethodInfo implements Parcelable {
ServiceInfo si = service.serviceInfo;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
mIsAuxIme = true;
mForceDefault = false;
PackageManager pm = context.getPackageManager();
String settingsActivityComponent = null;
@ -215,13 +221,39 @@ public final class InputMethodInfo implements Parcelable {
mIsAuxIme = source.readInt() == 1;
mService = ResolveInfo.CREATOR.createFromParcel(source);
source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR);
mForceDefault = false;
}
/**
* Temporary API for creating a built-in input method.
* Temporary API for creating a built-in input method for test.
*/
public InputMethodInfo(String packageName, String className,
CharSequence label, String settingsActivity) {
this(buildDummyResolveInfo(packageName, className, label), false, settingsActivity, null,
0, false);
}
/**
* Temporary API for creating a built-in input method for test.
* @hide
*/
public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
boolean forceDefault) {
final ServiceInfo si = ri.serviceInfo;
mService = ri;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
mSettingsActivityName = settingsActivity;
mIsDefaultResId = isDefaultResId;
mIsAuxIme = isAuxIme;
if (subtypes != null) {
mSubtypes.addAll(subtypes);
}
mForceDefault = forceDefault;
}
private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
CharSequence label) {
ResolveInfo ri = new ResolveInfo();
ServiceInfo si = new ServiceInfo();
ApplicationInfo ai = new ApplicationInfo();
@ -234,11 +266,7 @@ public final class InputMethodInfo implements Parcelable {
si.exported = true;
si.nonLocalizedLabel = label;
ri.serviceInfo = si;
mService = ri;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
mSettingsActivityName = settingsActivity;
mIsDefaultResId = 0;
mIsAuxIme = false;
return ri;
}
/**
@ -340,6 +368,22 @@ public final class InputMethodInfo implements Parcelable {
return mIsDefaultResId;
}
/**
* Return whether or not this ime is a default ime or not.
* @hide
*/
public boolean isDefault(Context context) {
if (mForceDefault) {
return true;
}
try {
final Resources res = context.createPackageContext(getPackageName(), 0).getResources();
return res.getBoolean(getIsDefaultResourceId());
} catch (NameNotFoundException e) {
return false;
}
}
public void dump(Printer pw, String prefix) {
pw.println(prefix + "mId=" + mId
+ " mSettingsActivityName=" + mSettingsActivityName);

View File

@ -59,11 +59,53 @@ public class InputMethodUtils {
& ApplicationInfo.FLAG_SYSTEM) != 0;
}
public static boolean isSystemImeThatHasEnglishSubtype(InputMethodInfo imi) {
public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) {
if (!isSystemIme(imi)) {
return false;
}
return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage());
return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD);
}
private static boolean isSystemAuxilialyImeThatHashAutomaticSubtype(InputMethodInfo imi) {
if (!isSystemIme(imi)) {
return false;
}
if (!imi.isAuxiliaryIme()) {
return false;
}
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
final InputMethodSubtype s = imi.getSubtypeAt(i);
if (s.overridesImplicitlyEnabledSubtype()) {
return true;
}
}
return false;
}
public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
Context context, boolean isSystemReady, ArrayList<InputMethodInfo> imis) {
final ArrayList<InputMethodInfo> retval = new ArrayList<InputMethodInfo>();
boolean auxilialyImeAdded = false;
for (int i = 0; i < imis.size(); ++i) {
final InputMethodInfo imi = imis.get(i);
if (isDefaultEnabledIme(isSystemReady, imi, context)) {
retval.add(imi);
if (imi.isAuxiliaryIme()) {
auxilialyImeAdded = true;
}
}
}
if (auxilialyImeAdded) {
return retval;
}
for (int i = 0; i < imis.size(); ++i) {
final InputMethodInfo imi = imis.get(i);
if (isSystemAuxilialyImeThatHashAutomaticSubtype(imi)) {
retval.add(imi);
}
}
return retval;
}
// TODO: Rename isSystemDefaultImeThatHasCurrentLanguageSubtype
@ -77,14 +119,11 @@ public class InputMethodUtils {
}
if (imi.getIsDefaultResourceId() != 0) {
try {
Resources res = context.createPackageContext(
imi.getPackageName(), 0).getResources();
if (res.getBoolean(imi.getIsDefaultResourceId())
&& containsSubtypeOf(imi, context.getResources().getConfiguration().
locale.getLanguage())) {
if (imi.isDefault(context) && containsSubtypeOf(
imi, context.getResources().getConfiguration().locale.getLanguage(),
null /* mode */)) {
return true;
}
} catch (PackageManager.NameNotFoundException ex) {
} catch (Resources.NotFoundException ex) {
}
}
@ -97,15 +136,19 @@ public class InputMethodUtils {
public static boolean isDefaultEnabledIme(
boolean isSystemReady, InputMethodInfo imi, Context context) {
return isValidSystemDefaultIme(isSystemReady, imi, context)
|| isSystemImeThatHasEnglishSubtype(imi);
|| isSystemImeThatHasEnglishKeyboardSubtype(imi);
}
private static boolean containsSubtypeOf(InputMethodInfo imi, String language) {
private static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) {
final int N = imi.getSubtypeCount();
for (int i = 0; i < N; ++i) {
if (imi.getSubtypeAt(i).getLocale().startsWith(language)) {
return true;
if (!imi.getSubtypeAt(i).getLocale().startsWith(language)) {
continue;
}
if(!TextUtils.isEmpty(mode) && !imi.getSubtypeAt(i).getMode().equalsIgnoreCase(mode)) {
continue;
}
return true;
}
return false;
}
@ -141,7 +184,7 @@ public class InputMethodUtils {
while (i > 0) {
i--;
final InputMethodInfo imi = enabledImes.get(i);
if (InputMethodUtils.isSystemImeThatHasEnglishSubtype(imi)
if (InputMethodUtils.isSystemImeThatHasEnglishKeyboardSubtype(imi)
&& !imi.isAuxiliaryIme()) {
return imi;
}

View File

@ -0,0 +1,18 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# We only want this apk build for tests.
LOCAL_MODULE_TAGS := tests
# Include all test java files.
LOCAL_SRC_FILES := \
$(call all-java-files-under, src)
LOCAL_DX_FLAGS := --core-library
LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-common frameworks-core-util-lib
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCoreInputMethodTests
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="internalOnly"
package="com.android.frameworks.coretests.inputmethod"
android:sharedUserId="android.uid.system">
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.frameworks.coretests.inputmethod"
android:label="Frameworks InputMethod Core Tests" />
</manifest>

View File

@ -0,0 +1,24 @@
#!/bin/bash
while [[ $# -gt 0 ]]; do
case "$1" in
--rebuild ) echo Rebuild && rebuild=true;;
* ) com_opts+=($1);;
esac
shift
done
if [[ -z $ANDROID_PRODUCT_OUT && $rebuilld == true ]]; then
echo You must lunch before running this test.
exit 0
fi
if [[ $rebuild == true ]]; then
make -j4 FrameworksCoreInputMethodTests
TESTAPP=${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreInputMethodTests.apk
COMMAND="adb install -r $TESTAPP"
echo $COMMAND
$COMMAND
fi
adb shell am instrument -w -e class android.os.InputMethodTest com.android.frameworks.coretests.inputmethod/android.test.InstrumentationTestRunner

View File

@ -0,0 +1,158 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import com.android.internal.inputmethod.InputMethodUtils;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import java.util.ArrayList;
import java.util.List;
public class InputMethodTest extends InstrumentationTestCase {
private static final boolean IS_AUX = true;
private static final boolean IS_DEFAULT = true;
private static final boolean IS_AUTO = true;
@SmallTest
public void testDefaultEnabledImesWithDefaultVoiceIme() throws Exception {
final Context context = getInstrumentation().getTargetContext();
final ArrayList<InputMethodInfo> imis = new ArrayList<InputMethodInfo>();
imis.add(createDefaultAutoDummyVoiceIme());
imis.add(createNonDefaultAutoDummyVoiceIme0());
imis.add(createNonDefaultAutoDummyVoiceIme1());
imis.add(createNonDefaultDummyVoiceIme2());
imis.add(createDefaultDummyEnUSKeyboardIme());
imis.add(createNonDefaultDummyJaJPKeyboardIme());
final ArrayList<InputMethodInfo> enabledImis = InputMethodUtils.getDefaultEnabledImes(
context, true, imis);
assertEquals(2, enabledImis.size());
for (int i = 0; i < enabledImis.size(); ++i) {
final InputMethodInfo imi = enabledImis.get(0);
// "DummyDefaultAutoVoiceIme" and "DummyDefaultEnKeyboardIme"
if (imi.getPackageName().equals("DummyDefaultAutoVoiceIme")
|| imi.getPackageName().equals("DummyDefaultEnKeyboardIme")) {
continue;
} else {
fail("Invalid enabled subtype.");
}
}
}
@SmallTest
public void testDefaultEnabledImesWithOutDefaultVoiceIme() throws Exception {
final Context context = getInstrumentation().getTargetContext();
final ArrayList<InputMethodInfo> imis = new ArrayList<InputMethodInfo>();
imis.add(createNonDefaultAutoDummyVoiceIme0());
imis.add(createNonDefaultAutoDummyVoiceIme1());
imis.add(createNonDefaultDummyVoiceIme2());
imis.add(createDefaultDummyEnUSKeyboardIme());
imis.add(createNonDefaultDummyJaJPKeyboardIme());
final ArrayList<InputMethodInfo> enabledImis = InputMethodUtils.getDefaultEnabledImes(
context, true, imis);
assertEquals(3, enabledImis.size());
for (int i = 0; i < enabledImis.size(); ++i) {
final InputMethodInfo imi = enabledImis.get(0);
// "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1" and
// "DummyDefaultEnKeyboardIme"
if (imi.getPackageName().equals("DummyNonDefaultAutoVoiceIme0")
|| imi.getPackageName().equals("DummyNonDefaultAutoVoiceIme1")
|| imi.getPackageName().equals("DummyDefaultEnKeyboardIme")) {
continue;
} else {
fail("Invalid enabled subtype.");
}
}
}
private static InputMethodInfo createDummyInputMethodInfo(String packageName, String name,
CharSequence label, boolean isAuxIme, boolean isDefault,
List<InputMethodSubtype> subtypes) {
final ResolveInfo ri = new ResolveInfo();
final ServiceInfo si = new ServiceInfo();
final ApplicationInfo ai = new ApplicationInfo();
ai.packageName = packageName;
ai.enabled = true;
ai.flags |= ApplicationInfo.FLAG_SYSTEM;
si.applicationInfo = ai;
si.enabled = true;
si.packageName = packageName;
si.name = name;
si.exported = true;
si.nonLocalizedLabel = label;
ri.serviceInfo = si;
return new InputMethodInfo(ri, isAuxIme, "", subtypes, 1, isDefault);
}
private static InputMethodSubtype createDummyInputMethodSubtype(String locale, String mode,
boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
return new InputMethodSubtype(0, 0, locale, mode, "", isAuxiliary,
overridesImplicitlyEnabledSubtype);
}
private static InputMethodInfo createDefaultAutoDummyVoiceIme() {
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
subtypes.add(createDummyInputMethodSubtype("auto", "voice", IS_AUX, IS_AUTO));
subtypes.add(createDummyInputMethodSubtype("en_US", "voice", IS_AUX, !IS_AUTO));
return createDummyInputMethodInfo("DummyDefaultAutoVoiceIme", "dummy.voice0",
"DummyVoice0", IS_AUX, IS_DEFAULT, subtypes);
}
private static InputMethodInfo createNonDefaultAutoDummyVoiceIme0() {
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
subtypes.add(createDummyInputMethodSubtype("auto", "voice", IS_AUX, IS_AUTO));
subtypes.add(createDummyInputMethodSubtype("en_US", "voice", IS_AUX, !IS_AUTO));
return createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme0", "dummy.voice1",
"DummyVoice1", IS_AUX, !IS_DEFAULT, subtypes);
}
private static InputMethodInfo createNonDefaultAutoDummyVoiceIme1() {
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
subtypes.add(createDummyInputMethodSubtype("auto", "voice", IS_AUX, IS_AUTO));
subtypes.add(createDummyInputMethodSubtype("en_US", "voice", IS_AUX, !IS_AUTO));
return createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme1", "dummy.voice2",
"DummyVoice2", IS_AUX, !IS_DEFAULT, subtypes);
}
private static InputMethodInfo createNonDefaultDummyVoiceIme2() {
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
subtypes.add(createDummyInputMethodSubtype("en_US", "voice", IS_AUX, !IS_AUTO));
return createDummyInputMethodInfo("DummyNonDefaultVoiceIme2", "dummy.voice3",
"DummyVoice3", IS_AUX, !IS_DEFAULT, subtypes);
}
private static InputMethodInfo createDefaultDummyEnUSKeyboardIme() {
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
subtypes.add(createDummyInputMethodSubtype("en_US", "keyboard", !IS_AUX, !IS_AUTO));
return createDummyInputMethodInfo("DummyDefaultEnKeyboardIme", "dummy.keyboard0",
"DummyKeyboard0", !IS_AUX, IS_DEFAULT, subtypes);
}
private static InputMethodInfo createNonDefaultDummyJaJPKeyboardIme() {
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
subtypes.add(createDummyInputMethodSubtype("ja_JP", "keyboard", !IS_AUX, !IS_AUTO));
return createDummyInputMethodInfo("DummyNonDefaultJaJPKeyboardIme", "dummy.keyboard1",
"DummyKeyboard1", !IS_AUX, !IS_DEFAULT, subtypes);
}
}

View File

@ -511,7 +511,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
buildInputMethodListLocked(mMethodList, mMethodMap);
buildInputMethodListLocked(
mMethodList, mMethodMap, false /* resetDefaultEnabledIme */);
boolean changed = false;
@ -671,9 +672,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// Just checking if defaultImiId is empty or not
final String defaultImiId = mSettings.getSelectedInputMethod();
if (DEBUG) {
Slog.d(TAG, "Initial default ime = " + defaultImiId);
}
mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
buildInputMethodListLocked(mMethodList, mMethodMap);
buildInputMethodListLocked(mMethodList, mMethodMap,
!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
mSettings.enableAllIMEsIfThereIsNoEnabledIME();
if (!mImeSelectedOnBoot) {
@ -726,7 +731,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
private void resetAllInternalStateLocked(boolean updateOnlyWhenLocaleChanged) {
private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged) {
if (!mSystemReady) {
// not system ready
return;
@ -744,7 +749,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
// InputMethodAndSubtypeListManager should be reset when the locale is changed.
mImListManager = new InputMethodAndSubtypeListManager(mContext, this);
buildInputMethodListLocked(mMethodList, mMethodMap);
buildInputMethodListLocked(mMethodList, mMethodMap,
updateOnlyWhenLocaleChanged /* resetDefaultEnabledIme */);
if (!updateOnlyWhenLocaleChanged) {
final String selectedImiId = mSettings.getSelectedInputMethod();
if (TextUtils.isEmpty(selectedImiId)) {
@ -814,7 +820,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mWindowManagerService.setOnHardKeyboardStatusChangeListener(
mHardKeyboardListener);
}
buildInputMethodListLocked(mMethodList, mMethodMap);
buildInputMethodListLocked(mMethodList, mMethodMap,
!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
if (!mImeSelectedOnBoot) {
Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
checkCurrentLocaleChangedLocked();
@ -2147,7 +2154,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mFileManager.addInputMethodSubtypes(imi, subtypes);
final long ident = Binder.clearCallingIdentity();
try {
buildInputMethodListLocked(mMethodList, mMethodMap);
buildInputMethodListLocked(mMethodList, mMethodMap,
false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
}
@ -2397,9 +2405,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
HashMap<String, InputMethodInfo> map) {
HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList " + ", \n ------ \n" + getStackTrace());
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ \n" + getStackTrace());
}
list.clear();
map.clear();
@ -2436,14 +2445,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final String id = p.getId();
map.put(id, p);
// Valid system default IMEs and IMEs that have English subtypes are enabled
// by default
if (InputMethodUtils.isDefaultEnabledIme(mSystemReady, p, mContext)) {
setInputMethodEnabledLocked(id, true);
}
if (DEBUG) {
Slog.d(TAG, "Found a third-party input method " + p);
Slog.d(TAG, "Found an input method " + p);
}
} catch (XmlPullParserException e) {
@ -2453,6 +2456,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
if (resetDefaultEnabledIme) {
final ArrayList<InputMethodInfo> defaultEnabledIme =
InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list);
for (int i = 0; i < defaultEnabledIme.size(); ++i) {
final InputMethodInfo imi = defaultEnabledIme.get(i);
if (DEBUG) {
Slog.d(TAG, "--- enable ime = " + imi);
}
setInputMethodEnabledLocked(imi.getId(), true);
}
}
final String defaultImiId = mSettings.getSelectedInputMethod();
if (!TextUtils.isEmpty(defaultImiId)) {
if (!map.containsKey(defaultImiId)) {