Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7b739a80 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Stop lying about hardware keyboard presence.

Here is a brief history of "Use Physical Keyboard", or "Show input
method", or SHOW_IME_WITH_HARD_KEYBOARD.

In Honeycomb, we introduced a mechanism which allows users to temporary
disconnect the physical keyboard connection from a UI [1].  That state
was temporary and non-persistent, but it was implemented to have
system-wide effect.  Whenever the user turned on that temporarily
disconnected state, not only IMEs but also other processes would be
notified that Configuration#keyboard was changed to KEYBOARD_NOKEYS.
The UI to do that was available only in tablet UI at that point and
nothing changed in phones.

In JellyBean, we decided to port that UI to phone, and the "Use Physical
Keyboard" toggle switch found its new home on the IME switcher
dialog [2].

In Lollipop, we revised the wording for the toggle switch on the IME
switcher dialog.  Now it's labeled as "Show input method" [3].

In Lollipop, we also decided to make the settings of "Show input method"
to be persistent as @hide entry in the secure settings [4].

  [1] 2992ea78
      I136087ca4f726d0068d5983d7d3686787ba60c55
  [2] cb598b11ea2c012bd15e7e7f44a3006f752c0b81
      (Ica768083f95c33dc1e494a28ba7d8b6eb989b0ef)
  [3] be11f3a5cea67c68a08c8da9ce85b3418501060a
      (I9c8a7eb98b5277f1d09cc19fa7402e9b4cf51d92)
  [4] b64e56aad3ffb267aec44308f57641a4c91caa83
      (I2237ded850a0d4ab43ca441d0b7df13e0958e630)

Now, we have hard time making everyone happy.  Having an easy way to
bring software keyboard back is probably still a valid use case, but
after several wording changes it is no longer obvious why
onConfigurationChanged() event is sent to all the processes as a
consequence of tapping that toggle.

In order to address above confusions, this CL removes the concept of
"temporarily disconnected physical keyboard" but tries to implement
"Show input method" functionality in InputMethodManagerService and
InputMethodService to keep the affordance that has been offered in the
IME switcher dialog.  As long as the IME overrides
InputMethodService#onEvaluateInputViewShown() as follows, the behavior
of "Show input method" toggle remains the same except for processes
including IMEs no longer receive onConfigurationChanged() event with
faked Configuration#keyboard value.

  @Override
  public boolean onEvaluateInputViewShown() {
      // If the default implementation returns true, recommend honoring
      // it.
      if (super.onEvaluateInputViewShown()) {
          return true;
      }

      // Implement the logic that is specific to your IME here.
      return myOnEvaluateInputViewShown();
  }

Bug: 22517687
Change-Id: Id4d332e3909590c68345e10e1f2e18650efb2eb7
parent fc515110
Loading
Loading
Loading
Loading
+120 −8
Original line number Diff line number Diff line
@@ -19,16 +19,22 @@ package android.inputmethodservice;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import android.annotation.CallSuper;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.app.ActivityManager;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Rect;
import android.graphics.Region;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.os.SystemClock;
@@ -68,6 +74,8 @@ import android.widget.LinearLayout;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * InputMethodService provides a standard implementation of an InputMethod,
@@ -633,6 +641,97 @@ public class InputMethodService extends AbstractInputMethodService {
        public int touchableInsets;
    }

    /**
     * A {@link ContentObserver} to monitor {@link Settings.Secure#SHOW_IME_WITH_HARD_KEYBOARD}.
     *
     * <p>Note that {@link Settings.Secure#SHOW_IME_WITH_HARD_KEYBOARD} is not a public API.
     * Basically this functionality still needs to be considered as implementation details.</p>
     */
    @MainThread
    private static final class SettingsObserver extends ContentObserver {
        @Retention(RetentionPolicy.SOURCE)
        @IntDef({
                ShowImeWithHardKeyboardType.UNKNOWN,
                ShowImeWithHardKeyboardType.FALSE,
                ShowImeWithHardKeyboardType.TRUE,
        })
        private @interface ShowImeWithHardKeyboardType {
            int UNKNOWN = 0;
            int FALSE = 1;
            int TRUE = 2;
        }
        @ShowImeWithHardKeyboardType
        private int mShowImeWithHardKeyboard = ShowImeWithHardKeyboardType.UNKNOWN;

        private final InputMethodService mService;

        private SettingsObserver(InputMethodService service) {
            super(new Handler(service.getMainLooper()));
            mService = service;
        }

        /**
         * A factory method that internally enforces two-phase initialization to make sure that the
         * object reference will not be escaped until the object is properly constructed.
         *
         * <p>NOTE: Currently {@link SettingsObserver} is accessed only from main thread.  Hence
         * this enforcement of two-phase initialization may be unnecessary at the moment.</p>
         *
         * @param service {@link InputMethodService} that needs to receive the callback.
         * @return {@link SettingsObserver} that is already registered to
         * {@link android.content.ContentResolver}. The caller must call
         * {@link SettingsObserver#unregister()}.
         */
        public static SettingsObserver createAndRegister(InputMethodService service) {
            final SettingsObserver observer = new SettingsObserver(service);
            // The observer is properly constructed. Let's start accepting the event.
            service.getContentResolver().registerContentObserver(
                    Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD),
                    false, observer);
            return observer;
        }

        void unregister() {
            mService.getContentResolver().unregisterContentObserver(this);
        }

        private boolean shouldShowImeWithHardKeyboard() {
            // Lazily initialize as needed.
            if (mShowImeWithHardKeyboard == ShowImeWithHardKeyboardType.UNKNOWN) {
                mShowImeWithHardKeyboard = Settings.Secure.getInt(mService.getContentResolver(),
                        Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0 ?
                        ShowImeWithHardKeyboardType.TRUE : ShowImeWithHardKeyboardType.FALSE;
            }
            switch (mShowImeWithHardKeyboard) {
                case ShowImeWithHardKeyboardType.TRUE:
                    return true;
                case ShowImeWithHardKeyboardType.FALSE:
                    return false;
                default:
                    Log.e(TAG, "Unexpected mShowImeWithHardKeyboard=" + mShowImeWithHardKeyboard);
                    return false;
            }
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            final Uri showImeWithHardKeyboardUri =
                    Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
            if (showImeWithHardKeyboardUri.equals(uri)) {
                mShowImeWithHardKeyboard = Settings.Secure.getInt(mService.getContentResolver(),
                        Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0 ?
                        ShowImeWithHardKeyboardType.TRUE : ShowImeWithHardKeyboardType.FALSE;
                mService.updateInputViewShown();
            }
        }

        @Override
        public String toString() {
            return "SettingsObserver{mShowImeWithHardKeyboard=" + mShowImeWithHardKeyboard  + "}";
        }
    }
    private SettingsObserver mSettingsObserver;

    /**
     * You can call this to customize the theme used by your IME's window.
     * This theme should typically be one that derives from
@@ -682,6 +781,7 @@ public class InputMethodService extends AbstractInputMethodService {
        super.setTheme(mTheme);
        super.onCreate();
        mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
        mSettingsObserver = SettingsObserver.createAndRegister(this);
        // If the previous IME has occupied non-empty inset in the screen, we need to decide whether
        // we continue to use the same size of the inset or update it
        mShouldClearInsetOfPreviousIme = (mImm.getInputMethodWindowVisibleHeight() > 0);
@@ -764,6 +864,10 @@ public class InputMethodService extends AbstractInputMethodService {
            mWindow.getWindow().setWindowAnimations(0);
            mWindow.dismiss();
        }
        if (mSettingsObserver != null) {
            mSettingsObserver.unregister();
            mSettingsObserver = null;
        }
    }

    /**
@@ -1142,14 +1246,21 @@ public class InputMethodService extends AbstractInputMethodService {
    }

    /**
     * Override this to control when the soft input area should be shown to
     * the user.  The default implementation only shows the input view when
     * there is no hard keyboard or the keyboard is hidden.  If you change what
     * this returns, you will need to call {@link #updateInputViewShown()}
     * yourself whenever the returned value may have changed to have it
     * re-evaluated and applied.
     * Override this to control when the soft input area should be shown to the user.  The default
     * implementation returns {@code false} when there is no hard keyboard or the keyboard is hidden
     * unless the user shows an intention to use software keyboard.  If you change what this
     * returns, you will need to call {@link #updateInputViewShown()} yourself whenever the returned
     * value may have changed to have it re-evaluated and applied.
     *
     * <p>When you override this method, it is recommended to call
     * {@code super.onEvaluateInputViewShown()} and return {@code true} when {@code true} is
     * returned.</p>
     */
    @CallSuper
    public boolean onEvaluateInputViewShown() {
        if (mSettingsObserver.shouldShowImeWithHardKeyboard()) {
            return true;
        }
        Configuration config = getResources().getConfiguration();
        return config.keyboard == Configuration.KEYBOARD_NOKEYS
                || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES;
@@ -2483,5 +2594,6 @@ public class InputMethodService extends AbstractInputMethodService {
                + " touchableInsets=" + mTmpInsets.touchableInsets
                + " touchableRegion=" + mTmpInsets.touchableRegion);
        p.println(" mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme);
        p.println(" mSettingsObserver=" + mSettingsObserver);
    }
}
+1 −25
Original line number Diff line number Diff line
@@ -577,31 +577,23 @@ public class WindowManagerService extends IWindowManager.Stub
    final ArrayList<WindowState> mTmpWindows = new ArrayList<>();

    boolean mHardKeyboardAvailable;
    boolean mShowImeWithHardKeyboard;
    WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
    SettingsObserver mSettingsObserver;

    private final class SettingsObserver extends ContentObserver {
        private final Uri mShowImeWithHardKeyboardUri =
                Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);

        private final Uri mDisplayInversionEnabledUri =
                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);

        public SettingsObserver() {
            super(new Handler());
            ContentResolver resolver = mContext.getContentResolver();
            resolver.registerContentObserver(mShowImeWithHardKeyboardUri, false, this,
                    UserHandle.USER_ALL);
            resolver.registerContentObserver(mDisplayInversionEnabledUri, false, this,
                    UserHandle.USER_ALL);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            if (mShowImeWithHardKeyboardUri.equals(uri)) {
                updateShowImeWithHardKeyboard();
            } else if (mDisplayInversionEnabledUri.equals(uri)) {
            if (mDisplayInversionEnabledUri.equals(uri)) {
                updateCircularDisplayMaskIfNeeded();
            }
        }
@@ -943,7 +935,6 @@ public class WindowManagerService extends IWindowManager.Stub
        mContext.registerReceiver(mBroadcastReceiver, filter);

        mSettingsObserver = new SettingsObserver();
        updateShowImeWithHardKeyboard();

        mHoldingScreenWakeLock = mPowerManager.newWakeLock(
                PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
@@ -7107,9 +7098,6 @@ public class WindowManagerService extends IWindowManager.Stub
            mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
            mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
        }
        if (mShowImeWithHardKeyboard) {
            config.keyboard = Configuration.KEYBOARD_NOKEYS;
        }

        // Let the policy update hidden states.
        config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
@@ -7118,18 +7106,6 @@ public class WindowManagerService extends IWindowManager.Stub
        mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
    }

    public void updateShowImeWithHardKeyboard() {
        synchronized (mWindowMap) {
            final boolean showImeWithHardKeyboard = Settings.Secure.getIntForUser(
                    mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0,
                    mCurrentUserId) == 1;
            if (mShowImeWithHardKeyboard != showImeWithHardKeyboard) {
                mShowImeWithHardKeyboard = showImeWithHardKeyboard;
                mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
            }
        }
    }

    void notifyHardKeyboardStatusChange() {
        final boolean available;
        final WindowManagerInternal.OnHardKeyboardStatusChangeListener listener;