aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/setup
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin/setup')
-rw-r--r--java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java122
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupActivity.java328
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java56
3 files changed, 501 insertions, 5 deletions
diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
new file mode 100644
index 000000000..1b893a65d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
@@ -0,0 +1,122 @@
+/*
+ * 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 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 com.android.inputmethod.compat.IntentCompatUtils;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.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
+ * {@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 LauncherIconVisibilityManager extends BroadcastReceiver {
+ private static final String TAG = LauncherIconVisibilityManager.class.getSimpleName();
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (shouldHandleThisIntent(intent, context)) {
+ 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 been booted,
+ // 3) a multiuser has been created.
+ // There is no good reason to keep the process alive if this IME isn't a current IME.
+ RichInputMethodManager.init(context);
+ if (!SetupActivity.isThisImeCurrent(context)) {
+ 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.has_ACTION_USER_INITIALIZE(intent)) {
+ Log.i(TAG, "User initialize");
+ return true;
+ }
+ return false;
+ }
+
+ public static void updateSetupWizardIconVisibility(final Context context) {
+ final ComponentName setupWizardActivity = new ComponentName(context, SetupActivity.class);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final boolean stateHasSet;
+ if (Settings.readShowSetupWizardIcon(prefs, context)) {
+ stateHasSet = setActivityState(context, setupWizardActivity,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ Log.i(TAG, (stateHasSet ? "Enable activity: " : "Activity has already been enabled: ")
+ + setupWizardActivity);
+ } else {
+ stateHasSet = setActivityState(context, setupWizardActivity,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ Log.i(TAG, (stateHasSet ? "Disable activity: " : "Activity has already been disabled: ")
+ + setupWizardActivity);
+ }
+ }
+
+ private static boolean setActivityState(final Context context,
+ final ComponentName activityComponent, final int activityState) {
+ final PackageManager pm = context.getPackageManager();
+ final int activityComponentState = pm.getComponentEnabledSetting(activityComponent);
+ if (activityComponentState == activityState) {
+ return false;
+ }
+ pm.setComponentEnabledSetting(
+ activityComponent, activityState, PackageManager.DONT_KILL_APP);
+ return true;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index fab894584..e009fbc39 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -17,23 +17,341 @@
package com.android.inputmethod.latin.setup;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.Message;
+import android.provider.Settings;
+import android.view.View;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+import com.android.inputmethod.compat.TextViewCompatUtils;
+import com.android.inputmethod.compat.ViewCompatUtils;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
import com.android.inputmethod.latin.SettingsActivity;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+
+import java.util.HashMap;
public final class SetupActivity extends Activity {
+ private SetupStepIndicatorView mStepIndicatorView;
+ private final SetupStepGroup mSetupSteps = new SetupStepGroup();
+ private static final String STATE_STEP = "step";
+ private int mStepNumber;
+ private static final int STEP_1 = 1;
+ private static final int STEP_2 = 2;
+ private static final int STEP_3 = 3;
+
+ private final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this);
+
+ static final class SettingsPoolingHandler extends StaticInnerHandlerWrapper<SetupActivity> {
+ private static final int MSG_POLLING_IME_SETTINGS = 0;
+ private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
+
+ public SettingsPoolingHandler(final SetupActivity outerInstance) {
+ super(outerInstance);
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ final SetupActivity setupActivity = getOuterInstance();
+ switch (msg.what) {
+ case MSG_POLLING_IME_SETTINGS:
+ if (SetupActivity.isThisImeEnabled(setupActivity)) {
+ setupActivity.invokeSetupWizardOfThisIme();
+ return;
+ }
+ startPollingImeSettings();
+ break;
+ }
+ }
+
+ public void startPollingImeSettings() {
+ sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS),
+ IME_SETTINGS_POLLING_INTERVAL);
+ }
+
+ public void cancelPollingImeSettings() {
+ removeMessages(MSG_POLLING_IME_SETTINGS);
+ }
+ }
+
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
+ setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar);
super.onCreate(savedInstanceState);
- // TODO: Implement setup wizard.
+ setContentView(R.layout.setup_wizard);
+
+ RichInputMethodManager.init(this);
+
+ if (savedInstanceState == null) {
+ mStepNumber = determineSetupStepNumber();
+ } else {
+ mStepNumber = savedInstanceState.getInt(STATE_STEP);
+ }
+
+ if (mStepNumber == STEP_3) {
+ // This IME already has been enabled and set as current IME.
+ // TODO: Implement tutorial.
+ invokeSettingsOfThisIme();
+ finish();
+ return;
+ }
+
+ // TODO: Use sans-serif-thin font family depending on the system locale white list and
+ // the SDK version.
+ final TextView titleView = (TextView)findViewById(R.id.setup_title);
+ titleView.setText(getString(R.string.setup_title, getString(R.string.english_ime_name)));
+
+ mStepIndicatorView = (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator);
+
+ final SetupStep step1 = new SetupStep(findViewById(R.id.setup_step1),
+ R.string.setup_step1_title, R.string.setup_step1_instruction,
+ R.drawable.ic_settings_language, R.string.language_settings);
+ step1.setAction(new Runnable() {
+ @Override
+ public void run() {
+ invokeLanguageAndInputSettings();
+ mHandler.startPollingImeSettings();
+ }
+ });
+ mSetupSteps.addStep(STEP_1, step1);
+
+ final SetupStep step2 = new SetupStep(findViewById(R.id.setup_step2),
+ R.string.setup_step2_title, R.string.setup_step2_instruction,
+ 0 /* actionIcon */, R.string.select_input_method);
+ step2.setAction(new Runnable() {
+ @Override
+ public void run() {
+ // Invoke input method picker.
+ RichInputMethodManager.getInstance().getInputMethodManager()
+ .showInputMethodPicker();
+ }
+ });
+ mSetupSteps.addStep(STEP_2, step2);
+
+ final SetupStep step3 = new SetupStep(findViewById(R.id.setup_step3),
+ R.string.setup_step3_title, 0 /* instruction */,
+ R.drawable.sym_keyboard_language_switch, R.string.setup_step3_instruction);
+ step3.setAction(new Runnable() {
+ @Override
+ public void run() {
+ invokeSubtypeEnablerOfThisIme();
+ }
+ });
+ mSetupSteps.addStep(STEP_3, step3);
+ }
+
+ private void invokeSetupWizardOfThisIme() {
+ final Intent intent = new Intent();
+ intent.setClass(this, SetupActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ }
+
+ private void invokeSettingsOfThisIme() {
final Intent intent = new Intent();
intent.setClass(this, SettingsActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
- finish();
+ }
+
+ private void invokeLanguageAndInputSettings() {
+ final Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ startActivity(intent);
+ }
+
+ private void invokeSubtypeEnablerOfThisIme() {
+ final InputMethodInfo imi =
+ RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+ final Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId());
+ startActivity(intent);
+ }
+
+ /**
+ * Check if the IME specified by the context is enabled.
+ * Note that {@link RichInputMethodManager} must have been initialized before calling this
+ * method.
+ *
+ * @param context package context of the IME to be checked.
+ * @return true if this IME is enabled.
+ */
+ public static boolean isThisImeEnabled(final Context context) {
+ final String packageName = context.getPackageName();
+ final InputMethodManager imm = RichInputMethodManager.getInstance().getInputMethodManager();
+ 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.
+ * Note that {@link RichInputMethodManager} must have been initialized before calling this
+ * method.
+ *
+ * @param context package context of the IME to be checked.
+ * @return true if this IME is the current IME.
+ */
+ public static boolean isThisImeCurrent(final Context context) {
+ final InputMethodInfo myImi =
+ RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+ final String currentImeId = Settings.Secure.getString(
+ context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ return myImi.getId().equals(currentImeId);
+ }
+
+ private int determineSetupStepNumber() {
+ mHandler.cancelPollingImeSettings();
+ if (!isThisImeEnabled(this)) {
+ return STEP_1;
+ }
+ if (!isThisImeCurrent(this)) {
+ return STEP_2;
+ }
+ return STEP_3;
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_STEP, mStepNumber);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mStepNumber = savedInstanceState.getInt(STATE_STEP);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mStepNumber = determineSetupStepNumber();
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ mStepNumber = determineSetupStepNumber();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ updateSetupStepView();
+ }
+
+ @Override
+ public void onWindowFocusChanged(final boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (!hasFocus) {
+ return;
+ }
+ mStepNumber = determineSetupStepNumber();
+ updateSetupStepView();
+ }
+
+ private void updateSetupStepView() {
+ final int layoutDirection = ViewCompatUtils.getLayoutDirection(mStepIndicatorView);
+ mStepIndicatorView.setIndicatorPosition(
+ getIndicatorPosition(mStepNumber, mSetupSteps.getTotalStep(), layoutDirection));
+ mSetupSteps.enableStep(mStepNumber);
+ }
+
+ private static float getIndicatorPosition(final int step, final int totalStep,
+ final int layoutDirection) {
+ final float pos = ((step - STEP_1) * 2 + 1) / (float)(totalStep * 2);
+ return (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos;
+ }
+
+ static final class SetupStep implements View.OnClickListener {
+ private final View mRootView;
+ private final TextView mActionLabel;
+ private Runnable mAction;
+
+ public SetupStep(final View rootView, final int title, final int instruction,
+ final int actionIcon, final int actionLabel) {
+ mRootView = rootView;
+ final Resources res = rootView.getResources();
+ final String applicationName = res.getString(R.string.english_ime_name);
+
+ final TextView titleView = (TextView)rootView.findViewById(R.id.setup_step_title);
+ titleView.setText(res.getString(title, applicationName));
+
+ final TextView instructionView = (TextView)rootView.findViewById(
+ R.id.setup_step_instruction);
+ if (instruction == 0) {
+ instructionView.setVisibility(View.GONE);
+ } else {
+ instructionView.setText(res.getString(instruction, applicationName));
+ }
+
+ mActionLabel = (TextView)rootView.findViewById(R.id.setup_step_action_label);
+ mActionLabel.setText(res.getString(actionLabel));
+ if (actionIcon == 0) {
+ final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel);
+ ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0);
+ } else {
+ final int overrideColor = res.getColor(R.color.setup_text_action);
+ final Drawable icon = res.getDrawable(actionIcon);
+ icon.setColorFilter(overrideColor, PorterDuff.Mode.MULTIPLY);
+ icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ TextViewCompatUtils.setCompoundDrawablesRelative(
+ mActionLabel, icon, null, null, null);
+ }
+ }
+
+ public void setEnabled(final boolean enabled) {
+ mRootView.setVisibility(enabled ? View.VISIBLE : View.GONE);
+ }
+
+ public void setAction(final Runnable action) {
+ mActionLabel.setOnClickListener(this);
+ mAction = action;
+ }
+
+ @Override
+ public void onClick(final View v) {
+ if (mAction != null) {
+ mAction.run();
+ }
+ }
+ }
+
+ static final class SetupStepGroup {
+ private final HashMap<Integer, SetupStep> mGroup = CollectionUtils.newHashMap();
+
+ public void addStep(final int stepNo, final SetupStep step) {
+ mGroup.put(stepNo, step);
+ }
+
+ public void enableStep(final int enableStepNo) {
+ for (final Integer stepNo : mGroup.keySet()) {
+ final SetupStep step = mGroup.get(stepNo);
+ step.setEnabled(stepNo == enableStepNo);
+ }
+ }
+
+ public int getTotalStep() {
+ return mGroup.size();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
new file mode 100644
index 000000000..077a21793
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
@@ -0,0 +1,56 @@
+/*
+ * 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 com.android.inputmethod.latin.setup;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.inputmethod.latin.R;
+
+public final class SetupStepIndicatorView extends View {
+ private final Path mIndicatorPath = new Path();
+ private final Paint mIndicatorPaint = new Paint();
+ private float mXRatio;
+
+ public SetupStepIndicatorView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mIndicatorPaint.setColor(getResources().getColor(R.color.setup_step_background));
+ mIndicatorPaint.setStyle(Paint.Style.FILL);
+ }
+
+ public void setIndicatorPosition(final float xRatio) {
+ mXRatio = xRatio;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ super.onDraw(canvas);
+ final int xPos = (int)(getWidth() * mXRatio);
+ final int height = getHeight();
+ mIndicatorPath.rewind();
+ mIndicatorPath.moveTo(xPos, 0);
+ mIndicatorPath.lineTo(xPos + height, height);
+ mIndicatorPath.lineTo(xPos - height, height);
+ mIndicatorPath.close();
+ canvas.drawPath(mIndicatorPath, mIndicatorPaint);
+ }
+}