com.googlecode.eyesfree.brailleback.IMEHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.eyesfree.brailleback.IMEHelper.java

Source

/*
 * Copyright (C) 2012 Google Inc.
 *
 * 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.googlecode.eyesfree.brailleback;

import com.googlecode.eyesfree.braille.display.BrailleInputEvent;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.v4.content.LocalBroadcastManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.InputMethodManager;

import java.util.List;

/**
 * Helps coordinating between the accessibility service and input method.
 * Among other things, this class pops up the IME switcher if the user
 * tries to use the braille keyboard when the braille IME is not the current
 * one.
 */
public class IMEHelper {
    private static final String ACTION_WAIT_FOR_IME_PICKER = "com.googlecode.eyesfree.braille.brailleback."
            + "ACTION_WAIT_FOR_IME_PICKER";
    private static final String PREF_HAS_RUN_WIZARD = "IME_has_run_wizard";
    private final InputMethodManager mInputMethodManager;
    private final Context mContext;
    private final SharedPreferences mSharedPreferences;

    private boolean mWaitingForImePicker = false;

    /**
     * Initializes an object, registering a broadcast receiver to
     * open the IME picker.
     */
    public IMEHelper(Context context) {
        mInputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        mContext = context;
        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
        LocalBroadcastManager.getInstance(mContext).registerReceiver(mReceiver,
                new IntentFilter(ACTION_WAIT_FOR_IME_PICKER));
    }

    /**
     * Unregisters the broadcast receiver.
     */
    public void destroy() {
        LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mReceiver);
    }

    /**
     * Processes incoming accessibility events, keeping the Braille IME
     * in sync with the currently focused node.  If the IME picker was
     * recently opened (by this class), this method will also try to focus
     * the item for the Braille IME.
     */
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (mWaitingForImePicker) {
            checkIMEPicker(event);
        }
    }

    /**
     * Listens for text input keystrokes.  If the user tries to enter text
     * and the Braille IME is not the default on the system, takes the user
     * through the {@link IMESetupWizardActivity}.
     */
    public boolean onInputEvent(BrailleInputEvent event) {
        int cmd = event.getCommand();
        if (cmd == BrailleInputEvent.CMD_BRAILLE_KEY || cmd == BrailleInputEvent.CMD_KEY_DEL
                || cmd == BrailleInputEvent.CMD_KEY_FORWARD_DEL) {
            if (!isInputMethodDefault(mContext, BrailleIME.class)) {
                tryIMESwitch();
                return true;
            }
        }
        return false;
    }

    /**
     * Sends a broadcast to open the IME picker and try to focus the
     * Braille IME, using {@code context}.  There must be an existing
     * instance of this class in the same process for this to work.
     */
    public static void sendWaitForIMEPicker(Context context) {
        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_WAIT_FOR_IME_PICKER));
    }

    /**
     * Determines from system settings if {@code IMEClass} is an enabled input
     * method.
     */
    public static boolean isInputMethodEnabled(Context context, Class<?> IMEClass) {
        final ComponentName imeComponentName = new ComponentName(context, IMEClass);
        final String enabledIMEIds = Settings.Secure.getString(context.getContentResolver(),
                Settings.Secure.ENABLED_INPUT_METHODS);
        if (enabledIMEIds == null) {
            return false;
        }

        for (String enabledIMEId : enabledIMEIds.split(":")) {
            if (imeComponentName.equals(ComponentName.unflattenFromString(enabledIMEId))) {
                return true;
            }
        }
        return false;
    }

    /**
     * Determines, from system settings, if {@code IMEClass} is the default
     * input method.
     */
    public static boolean isInputMethodDefault(Context context, Class<?> IMEClass) {
        final ComponentName imeComponentName = new ComponentName(context, IMEClass);
        final String defaultIMEId = Settings.Secure.getString(context.getContentResolver(),
                Settings.Secure.DEFAULT_INPUT_METHOD);

        return defaultIMEId != null && imeComponentName.equals(ComponentName.unflattenFromString(defaultIMEId));
    }

    private void tryIMESwitch() {
        // If the user already saw the wizard and the IME is enabled,
        // then take the shortcut of dropping the user directly into the IME
        // picker.
        if (mSharedPreferences.getBoolean(PREF_HAS_RUN_WIZARD, false)
                && isInputMethodEnabled(mContext, BrailleIME.class)) {
            showIMEPicker();
        } else {
            Intent intent = new Intent(mContext, IMESetupWizardActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mContext.startActivity(intent);
            mSharedPreferences.edit().putBoolean(PREF_HAS_RUN_WIZARD, true).apply();
        }
    }

    private void showIMEPicker() {
        mInputMethodManager.showInputMethodPicker();
        waitForIMEPicker();
    }

    private void waitForIMEPicker() {
        mWaitingForImePicker = true;
        mHandler.scheduleStopWaitingForImePicker();
    }

    private void checkIMEPicker(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
                && "android".equals(event.getPackageName())
                && "android.app.AlertDialog".equals(event.getClassName())) {
            AccessibilityNodeInfo node = event.getSource();
            if (node == null) {
                return;
            }
            String IMETitle = mContext.getString(R.string.braille_ime_name);
            List<AccessibilityNodeInfo> found = node.findAccessibilityNodeInfosByText(IMETitle);
            if (found.size() == 0) {
                return;
            }
            AccessibilityNodeInfo firstFound = found.get(0);
            AccessibilityNodeInfo toFocus = firstFound.getParent();
            if (toFocus != null) {
                toFocus.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
            }
        }
    }

    private final IMEHelperHandler mHandler = new IMEHelperHandler();

    private class IMEHelperHandler extends Handler {
        private static final int WHAT_STOP_WAITING_FOR_IME_SWITCHER = 1;
        private static final long IME_SWITCHER_WAIT_MILLIS = 1000;

        public void scheduleStopWaitingForImePicker() {
            sendEmptyMessageDelayed(WHAT_STOP_WAITING_FOR_IME_SWITCHER, IME_SWITCHER_WAIT_MILLIS);
        }

        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
            case WHAT_STOP_WAITING_FOR_IME_SWITCHER:
                mWaitingForImePicker = false;
                return;
            }
        }
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (ACTION_WAIT_FOR_IME_PICKER.equals(intent.getAction())) {
                waitForIMEPicker();
            }
        }
    };
}