From d6475a682d9651a651f60856baef9b17b4633b13 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Mon, 17 Apr 2017 10:35:27 -0700 Subject: [PATCH] A new power button mode to hide the IME when shown As discussed in Bug 33038203 on certain platforms there is a demand that the power button can change the behavior depending on whether an IME window is shown on the screen or not. The behavior requested here can be summarized into two parts: * Hide the IME window if it is shown [1] * Go to the home screen if no IME window is shown This CL implements the above request by introducing a new config mode for config_shortPressOnPowerBehavior. Note the definition of when an IME is shown is often tricky than people would expect. The way this CL is implemented is to propagate IME window state from InputMethodManagerService (IMMS) to PhoneWindowManager via WindowManagerService regarding when the back button on the NavBar for phones/tablets should be shown as an IME dismiss key [2]. [1]: Even with this CL the IME still is allowed to ignore the request to hide the software keyboard. Currently there is no official protocol to forcefully hide the software keyboard. How to deal with such a situation is a long standing TODO task. [2]: Internally this is controlled by the following IMMS fields: - InputMethodManagerService#mImeWindowVis - InputMethodManagerService#mBackDisposition Note that those fields rely on self-report from the IME. To be precise, the base implementation of InputMethodService is responsible for report back its internal state to IMMS when necessary. The important point is that, although this could allow a malicious IME to confuse the system UI to some extent, supporting malicious IMEs is not clearly a goal of Android. Anyway, the definition of when an IME is shown is a kind of hot topic in several system services recently. Hopefully we can come up with better definition and reliable mechanism in a future release. Fixes: 33824860 Test: Manually verified as follows 1. Change config_shortPressOnPowerBehavior to "5" 2. Rebuilt the OS image and flash it to the device 3. Make sure that the power button works like a home button if software keyboard is not shown. 4. Open dialer and focus in to the text field shown on top 5. Make sure that the AOSP keyboard is shown. 6. Run 'adb shell dumpsys input_method' to observe the following line: mImeWindowVis=Active|Visible 7. Tap the power button to make sure that the AOSP keyboard gets dismissed. 8. Tap the power button again to make sure that it works as if a home button. Test: Manually tested as follows 1. Open dialer and focus in to the text field to show an IME 2. Run 'adb shell dumpsys window policy' to make sure mDismissImeOnBackKeyPressed=true 3. Tap the back button to dismiss the IME 4. Run 'adb shell dumpsys window policy' to make sure mDismissImeOnBackKeyPressed=false Change-Id: I20721547c73360a70b5fc5cbe06824d577d1768a --- .../android/view/WindowManagerInternal.java | 5 ++- .../android/view/WindowManagerPolicy.java | 12 ++++++ core/res/res/values/config.xml | 2 + .../server/InputMethodManagerService.java | 18 ++++++++- .../server/policy/PhoneWindowManager.java | 38 ++++++++++++++++++- .../server/wm/WindowManagerService.java | 5 ++- 6 files changed, 76 insertions(+), 4 deletions(-) diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java index 6dbc09ceef97f..bf0e10f993ad7 100644 --- a/core/java/android/view/WindowManagerInternal.java +++ b/core/java/android/view/WindowManagerInternal.java @@ -303,13 +303,16 @@ public abstract class WindowManagerInternal { * hidden, no matter how WindowManagerService will react / has reacted * to corresponding API calls. Note that this state is not guaranteed * to be synchronized with state in WindowManagerService. + * @param dismissImeOnBackKeyPressed {@code true} if the software keyboard is shown and the back + * key is expected to dismiss the software keyboard. * @param targetWindowToken token to identify the target window that the IME is associated with. * {@code null} when application, system, or the IME itself decided to * change its window visibility before being associated with any target * window. */ public abstract void updateInputMethodWindowStatus(@NonNull IBinder imeToken, - boolean imeWindowVisible, @Nullable IBinder targetWindowToken); + boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed, + @Nullable IBinder targetWindowToken); /** * Returns true when the hardware keyboard is available. diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index bb6e0eed57cc7..030c78b7f4b65 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1536,6 +1536,18 @@ public interface WindowManagerPolicy { */ public void setLastInputMethodWindowLw(WindowState ime, WindowState target); + /** + * An internal callback (from InputMethodManagerService) to notify a state change regarding + * whether the back key should dismiss the software keyboard (IME) or not. + * + * @param newValue {@code true} if the software keyboard is shown and the back key is expected + * to dismiss the software keyboard. + * @hide + */ + default void setDismissImeOnBackKeyPressed(boolean newValue) { + // Default implementation does nothing. + } + /** * Show the recents task list app. * @hide diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 45dccb6bfe33d..6a31e161842be 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -852,6 +852,8 @@ 1 - Go to sleep (doze) 2 - Really go to sleep (don't doze) 3 - Really go to sleep and go home (don't doze) + 4 - Go to home + 5 - Dismiss IME if shown. Otherwise go to home --> 1 diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 39bfedae69d81..8ad3d23648bf1 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -49,6 +49,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import android.annotation.BinderThread; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -2146,6 +2147,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); } + @BinderThread @SuppressWarnings("deprecation") @Override public void setImeWindowStatus(IBinder token, IBinder startInputToken, int vis, @@ -2161,9 +2163,23 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mBackDisposition = backDisposition; updateSystemUiLocked(token, vis, backDisposition); } + + final boolean dismissImeOnBackKeyPressed; + switch (backDisposition) { + case InputMethodService.BACK_DISPOSITION_WILL_DISMISS: + dismissImeOnBackKeyPressed = true; + break; + case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS: + dismissImeOnBackKeyPressed = false; + break; + default: + case InputMethodService.BACK_DISPOSITION_DEFAULT: + dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0); + break; + } mWindowManagerInternal.updateInputMethodWindowStatus(token, (vis & InputMethodService.IME_VISIBLE) != 0, - info != null ? info.mTargetWindow : null); + dismissImeOnBackKeyPressed, info != null ? info.mTargetWindow : null); } private void updateSystemUi(IBinder token, int vis, int backDisposition) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4f29bfa2829e3..bcb4121a6d2f4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -222,9 +222,10 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; +import android.view.inputmethod.InputMethodManagerInternal; import android.widget.ImageView; - import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; @@ -279,6 +280,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP = 2; static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME = 3; static final int SHORT_PRESS_POWER_GO_HOME = 4; + static final int SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME = 5; static final int LONG_PRESS_POWER_NOTHING = 0; static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; @@ -407,6 +409,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { PowerManager mPowerManager; ActivityManagerInternal mActivityManagerInternal; InputManagerInternal mInputManagerInternal; + InputMethodManagerInternal mInputMethodManagerInternal; DreamManagerInternal mDreamManagerInternal; PowerManagerInternal mPowerManagerInternal; IStatusBarService mStatusBarService; @@ -494,6 +497,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowState mLastInputMethodWindow = null; WindowState mLastInputMethodTargetWindow = null; + @GuardedBy("mLock") + private boolean mDismissImeOnBackKeyPressed; + // FIXME This state is shared between the input reader and handler thread. // Technically it's broken and buggy but it has been like this for many years // and we have not yet seen any problems. Someday we'll rewrite this logic @@ -1396,6 +1402,27 @@ public class PhoneWindowManager implements WindowManagerPolicy { case SHORT_PRESS_POWER_GO_HOME: launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/); break; + case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: { + final boolean dismissImeOnBackKeyPressed; + // We can be here on both the main thread (via mHandler) and native callback + // thread (from interceptPowerKeyUp via WindowManagerCallbacks). + synchronized (mLock) { + dismissImeOnBackKeyPressed = mDismissImeOnBackKeyPressed; + } + if (dismissImeOnBackKeyPressed) { + if (mInputMethodManagerInternal == null) { + mInputMethodManagerInternal = + LocalServices.getService(InputMethodManagerInternal.class); + } + if (mInputMethodManagerInternal != null) { + mInputMethodManagerInternal.hideCurrentInputMethod(); + } + } else { + launchHomeFromHotKey(true /* awakenFromDreams */, + false /*respectKeyguard*/); + } + break; + } } } } @@ -7954,6 +7981,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { mLastInputMethodTargetWindow = target; } + @Override + public void setDismissImeOnBackKeyPressed(boolean newValue) { + synchronized (mLock) { + mDismissImeOnBackKeyPressed = newValue; + } + } + @Override public int getInputMethodWindowVisibleHeightLw() { return mDockBottom - mCurBottom; @@ -8170,6 +8204,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(prefix); pw.print("mLastInputMethodTargetWindow="); pw.println(mLastInputMethodTargetWindow); } + pw.print(prefix); pw.print("mDismissImeOnBackKeyPressed="); + pw.println(mDismissImeOnBackKeyPressed); if (mStatusBar != null) { pw.print(prefix); pw.print("mStatusBar="); pw.print(mStatusBar); pw.print(" isStatusBarKeyguard="); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0049585f5c520..252b4d4e0473a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7267,13 +7267,16 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void updateInputMethodWindowStatus(@NonNull IBinder imeToken, - boolean imeWindowVisible, @Nullable IBinder targetWindowToken) { + boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed, + @Nullable IBinder targetWindowToken) { // TODO (b/34628091): Use this method to address the window animation issue. if (DEBUG_INPUT_METHOD) { Slog.w(TAG_WM, "updateInputMethodWindowStatus: imeToken=" + imeToken + + " dismissImeOnBackKeyPressed=" + dismissImeOnBackKeyPressed + " imeWindowVisible=" + imeWindowVisible + " targetWindowToken=" + targetWindowToken); } + mPolicy.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed); } @Override