From 052ec62abd577182af8d5b50564d8075b18be3c9 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Fri, 4 Jul 2014 16:36:54 +0900 Subject: [PATCH] Restore additional subtypes when the package is updated With this CL, Intent#ACTION_MY_PACKAGE_REPLACED will not only update the visibility of the setup activity but also reconstruct additional subtypes. This is important because the system always removes all the additional subtypes whenever the package is updated. BUG: 15890448 Change-Id: Ic36ea68f50b1ac89b4cbd268ee53f9a5e5d60afd --- java/AndroidManifest.xml | 2 +- .../latin/RichInputMethodManager.java | 16 ++- .../latin/SystemBroadcastReceiver.java | 104 ++++++++++++++++++ .../setup/LauncherIconVisibilityManager.java | 64 +++-------- .../latin/setup/SetupActivity.java | 61 ---------- .../latin/setup/SetupWizardActivity.java | 11 +- .../UncachedInputMethodManagerUtils.java | 84 ++++++++++++++ 7 files changed, 221 insertions(+), 121 deletions(-) create mode 100644 java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java create mode 100644 java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml index 6f4e602ce..ab9b13eb5 100644 --- a/java/AndroidManifest.xml +++ b/java/AndroidManifest.xml @@ -77,7 +77,7 @@ - + diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java index 7758ac78e..7cf4eff92 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -64,8 +64,7 @@ public final class RichInputMethodManager { } public static void init(final Context context) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - sInstance.initInternal(context, prefs); + sInstance.initInternal(context); } private boolean isInitialized() { @@ -78,7 +77,7 @@ public final class RichInputMethodManager { } } - private void initInternal(final Context context, final SharedPreferences prefs) { + private void initInternal(final Context context) { if (isInitialized()) { return; } @@ -88,11 +87,16 @@ public final class RichInputMethodManager { // Initialize additional subtypes. SubtypeLocaleUtils.init(context); + final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes(context); + setAdditionalInputMethodSubtypes(additionalSubtypes); + } + + public InputMethodSubtype[] getAdditionalSubtypes(final Context context) { + SubtypeLocaleUtils.init(context); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes( prefs, context.getResources()); - final InputMethodSubtype[] additionalSubtypes = - AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes); - setAdditionalInputMethodSubtypes(additionalSubtypes); + return AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes); } public InputMethodManager getInputMethodManager() { diff --git a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java new file mode 100644 index 000000000..e4ee42660 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2014 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 com.android.inputmethod.latin; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Process; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.compat.IntentCompatUtils; +import com.android.inputmethod.latin.settings.Settings; +import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager; +import com.android.inputmethod.latin.setup.SetupActivity; +import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils; + +/** + * This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME + * package has been replaced by a newer version of the same package. This class also detects + * {@link Intent#ACTION_BOOT_COMPLETED} and {@link Intent#ACTION_USER_INITIALIZE} broadcast intent. + * + * If this IME has already been installed in the system image and a new version of this IME has + * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver and it + * will hide the setup wizard's icon. + * + * If this IME has already been installed in the data partition and a new version of this IME has + * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver but it + * will not hide the setup wizard's icon, and the icon will appear on the launcher. + * + * If this IME hasn't been installed yet and has been newly installed, no + * {@link Intent#ACTION_MY_PACKAGE_REPLACED} will be sent and the setup wizard's icon will appear + * on the launcher. + * + * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is received by this + * receiver and it checks whether the setup wizard's icon should be appeared or not on the launcher + * depending on which partition this IME is installed. + * + * When a multiuser account has been created, {@link Intent#ACTION_USER_INITIALIZE} is received + * by this receiver and it checks the whether the setup wizard's icon should be appeared or not on + * the launcher depending on which partition this IME is installed. + */ +public final class SystemBroadcastReceiver extends BroadcastReceiver { + private static final String TAG = SystemBroadcastReceiver.class.getSimpleName(); + + @Override + public void onReceive(final Context context, final Intent intent) { + final String intentAction = intent.getAction(); + if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intentAction)) { + Log.i(TAG, "Package has been replaced: " + context.getPackageName()); + } else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) { + Log.i(TAG, "Boot has been completed"); + } else if (IntentCompatUtils.is_ACTION_USER_INITIALIZE(intentAction)) { + Log.i(TAG, "User initialize"); + } + + LauncherIconVisibilityManager.onReceiveGlobalIntent(intentAction, context); + + if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intentAction)) { + // Need to restore additional subtypes because system always clears additional + // subtypes when the package is replaced. + RichInputMethodManager.init(context); + final RichInputMethodManager richImm = RichInputMethodManager.getInstance(); + final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes(context); + richImm.setAdditionalInputMethodSubtypes(additionalSubtypes); + } + + // The process that hosts this broadcast receiver is invoked and remains alive even after + // 1) the package has been re-installed, 2) the device has just booted, + // 3) a new user has been created. + // There is no good reason to keep the process alive if this IME isn't a current IME. + final InputMethodManager imm = + (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); + // Called to check whether this IME has been triggered by the current user or not + final boolean isInputMethodManagerValidForUserOfThisProcess = + !imm.getInputMethodList().isEmpty(); + final boolean isCurrentImeOfCurrentUser = isInputMethodManagerValidForUserOfThisProcess + && UncachedInputMethodManagerUtils.isThisImeCurrent(context, imm); + if (!isCurrentImeOfCurrentUser) { + final int myPid = Process.myPid(); + Log.i(TAG, "Killing my process: pid=" + myPid); + Process.killProcess(myPid); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java index 050d8d26f..9585736e7 100644 --- a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java +++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java @@ -16,85 +16,51 @@ package com.android.inputmethod.latin.setup; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.os.Process; import android.preference.PreferenceManager; import android.util.Log; -import android.view.inputmethod.InputMethodManager; import com.android.inputmethod.compat.IntentCompatUtils; import com.android.inputmethod.latin.settings.Settings; /** - * This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME - * package has been replaced by a newer version of the same package. This class also detects + * This class handles the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME + * package has been replaced by a newer version of the same package. This class also handles * {@link Intent#ACTION_BOOT_COMPLETED} and {@link Intent#ACTION_USER_INITIALIZE} broadcast intent. * * If this IME has already been installed in the system image and a new version of this IME has - * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver and it - * will hide the setup wizard's icon. + * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received to this class to hide the + * setup wizard's icon. * * If this IME has already been installed in the data partition and a new version of this IME has - * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver but it + * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is forwarded to this class but it * will not hide the setup wizard's icon, and the icon will appear on the launcher. * * If this IME hasn't been installed yet and has been newly installed, no * {@link Intent#ACTION_MY_PACKAGE_REPLACED} will be sent and the setup wizard's icon will appear * on the launcher. * - * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is received by this - * receiver and it checks whether the setup wizard's icon should be appeared or not on the launcher + * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is forwarded to this class + * to check whether the setup wizard's icon should be appeared or not on the launcher * depending on which partition this IME is installed. * - * When a multiuser account has been created, {@link Intent#ACTION_USER_INITIALIZE} is received - * by this receiver and it checks the whether the setup wizard's icon should be appeared or not on - * the launcher depending on which partition this IME is installed. + * When a multiuser account has been created, {@link Intent#ACTION_USER_INITIALIZE} is forwarded to + * this class to check whether the setup wizard's icon should be appeared or not on the launcher + * depending on which partition this IME is installed. */ -public final class LauncherIconVisibilityManager extends BroadcastReceiver { +public final class LauncherIconVisibilityManager { private static final String TAG = LauncherIconVisibilityManager.class.getSimpleName(); - @Override - public void onReceive(final Context context, final Intent intent) { - if (shouldHandleThisIntent(intent, context)) { + public static void onReceiveGlobalIntent(final String action, final Context context) { + if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(action) || + Intent.ACTION_BOOT_COMPLETED.equals(action) || + IntentCompatUtils.is_ACTION_USER_INITIALIZE(action)) { updateSetupWizardIconVisibility(context); } - - // The process that hosts this broadcast receiver is invoked and remains alive even after - // 1) the package has been re-installed, 2) the device has just booted, - // 3) a new user has been created. - // There is no good reason to keep the process alive if this IME isn't a current IME. - final InputMethodManager imm = - (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); - // Called to check whether this IME has been triggered by the current user or not - final boolean isInputMethodManagerValidForUserOfThisProcess = - !imm.getInputMethodList().isEmpty(); - final boolean isCurrentImeOfCurrentUser = isInputMethodManagerValidForUserOfThisProcess - && SetupActivity.isThisImeCurrent(context, imm); - if (!isCurrentImeOfCurrentUser) { - final int myPid = Process.myPid(); - Log.i(TAG, "Killing my process: pid=" + myPid); - Process.killProcess(myPid); - } - } - - private static boolean shouldHandleThisIntent(final Intent intent, final Context context) { - final String action = intent.getAction(); - if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) { - Log.i(TAG, "Package has been replaced: " + context.getPackageName()); - return true; - } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { - Log.i(TAG, "Boot has been completed"); - return true; - } else if (IntentCompatUtils.is_ACTION_USER_INITIALIZE(action)) { - Log.i(TAG, "User initialize"); - return true; - } - return false; } public static void updateSetupWizardIconVisibility(final Context context) { diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java index a68f98fe7..b770ea512 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java @@ -37,65 +37,4 @@ public final class SetupActivity extends Activity { finish(); } } - - /* - * We may not be able to get our own {@link InputMethodInfo} just after this IME is installed - * because {@link InputMethodManagerService} may not be aware of this IME yet. - * Note: {@link RichInputMethodManager} has similar methods. Here in setup wizard, we can't - * use it for the reason above. - */ - - /** - * Check if the IME specified by the context is enabled. - * CAVEAT: This may cause a round trip IPC. - * - * @param context package context of the IME to be checked. - * @param imm the {@link InputMethodManager}. - * @return true if this IME is enabled. - */ - /* package */ static boolean isThisImeEnabled(final Context context, - final InputMethodManager imm) { - final String packageName = context.getPackageName(); - for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) { - if (packageName.equals(imi.getPackageName())) { - return true; - } - } - return false; - } - - /** - * Check if the IME specified by the context is the current IME. - * CAVEAT: This may cause a round trip IPC. - * - * @param context package context of the IME to be checked. - * @param imm the {@link InputMethodManager}. - * @return true if this IME is the current IME. - */ - /* package */ static boolean isThisImeCurrent(final Context context, - final InputMethodManager imm) { - final InputMethodInfo imi = getInputMethodInfoOf(context.getPackageName(), imm); - final String currentImeId = Settings.Secure.getString( - context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); - return imi != null && imi.getId().equals(currentImeId); - } - - /** - * Get {@link InputMethodInfo} of the IME specified by the package name. - * CAVEAT: This may cause a round trip IPC. - * - * @param packageName package name of the IME. - * @param imm the {@link InputMethodManager}. - * @return the {@link InputMethodInfo} of the IME specified by the packageName, - * or null if not found. - */ - /* package */ static InputMethodInfo getInputMethodInfoOf(final String packageName, - final InputMethodManager imm) { - for (final InputMethodInfo imi : imm.getInputMethodList()) { - if (packageName.equals(imi.getPackageName())) { - return imi; - } - } - return null; - } } diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java index bcac05a6a..e455e53d3 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java @@ -38,6 +38,7 @@ import com.android.inputmethod.compat.ViewCompatUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.settings.SettingsActivity; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; +import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils; import java.util.ArrayList; @@ -93,7 +94,8 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL } switch (msg.what) { case MSG_POLLING_IME_SETTINGS: - if (SetupActivity.isThisImeEnabled(setupWizardActivity, mImmInHandler)) { + if (UncachedInputMethodManagerUtils.isThisImeEnabled(setupWizardActivity, + mImmInHandler)) { setupWizardActivity.invokeSetupWizardOfThisIme(); return; } @@ -277,7 +279,8 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL } void invokeSubtypeEnablerOfThisIme() { - final InputMethodInfo imi = SetupActivity.getInputMethodInfoOf(getPackageName(), mImm); + final InputMethodInfo imi = + UncachedInputMethodManagerUtils.getInputMethodInfoOf(getPackageName(), mImm); if (imi == null) { return; } @@ -301,10 +304,10 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL private int determineSetupStepNumber() { mHandler.cancelPollingImeSettings(); - if (!SetupActivity.isThisImeEnabled(this, mImm)) { + if (!UncachedInputMethodManagerUtils.isThisImeEnabled(this, mImm)) { return STEP_1; } - if (!SetupActivity.isThisImeCurrent(this, mImm)) { + if (!UncachedInputMethodManagerUtils.isThisImeCurrent(this, mImm)) { return STEP_2; } return STEP_3; diff --git a/java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java b/java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java new file mode 100644 index 000000000..5df00efb9 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 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 com.android.inputmethod.latin.utils; + +import android.content.Context; +import android.provider.Settings; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; + +/* + * A utility class for {@link InputMethodManager}. Unlike {@link RichInputMethodManager}, this + * class provides synchronous, non-cached access to {@link InputMethodManager}. The setup activity + * is a good example to use this class because {@link InputMethodManagerService} may not be aware of + * this IME immediately after this IME is installed. + */ +public final class UncachedInputMethodManagerUtils { + /** + * Check if the IME specified by the context is enabled. + * CAVEAT: This may cause a round trip IPC. + * + * @param context package context of the IME to be checked. + * @param imm the {@link InputMethodManager}. + * @return true if this IME is enabled. + */ + public static boolean isThisImeEnabled(final Context context, + final InputMethodManager imm) { + final String packageName = context.getPackageName(); + for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) { + if (packageName.equals(imi.getPackageName())) { + return true; + } + } + return false; + } + + /** + * Check if the IME specified by the context is the current IME. + * CAVEAT: This may cause a round trip IPC. + * + * @param context package context of the IME to be checked. + * @param imm the {@link InputMethodManager}. + * @return true if this IME is the current IME. + */ + public static boolean isThisImeCurrent(final Context context, + final InputMethodManager imm) { + final InputMethodInfo imi = getInputMethodInfoOf(context.getPackageName(), imm); + final String currentImeId = Settings.Secure.getString( + context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + return imi != null && imi.getId().equals(currentImeId); + } + + /** + * Get {@link InputMethodInfo} of the IME specified by the package name. + * CAVEAT: This may cause a round trip IPC. + * + * @param packageName package name of the IME. + * @param imm the {@link InputMethodManager}. + * @return the {@link InputMethodInfo} of the IME specified by the packageName, + * or null if not found. + */ + public static InputMethodInfo getInputMethodInfoOf(final String packageName, + final InputMethodManager imm) { + for (final InputMethodInfo imi : imm.getInputMethodList()) { + if (packageName.equals(imi.getPackageName())) { + return imi; + } + } + return null; + } +}