Let MockIme behave the same even w/ hard keyboard

This is a follow up CL to my previous CLs [1][2] that do not pass when
a hardware keyboard is attached.

Those tests fail because MockIme#onStartInputView() was not called,
which is because the default implementation of
InputMethodService#onEvaluateInputViewShown() returns false when
neither of the following conditions is true.

 * Configuration#keyboard is KEYBOARD_NOKEYS
 * Configuration#hardKeyboardHidden is HARDKEYBOARDHIDDEN_YES

Like we by default disable fullscreen mode in MockIme for the
convenience of writing tests, this CL also cancels the above default
behavior in case the device to be tested has hardware keyboard.

Tests that require the original behavior of InputMethodService can
still opt out this behavior overrides as follows.

  try(MockImeSession imeSession = MockImeSession.create(
          context, uiautomation, new ImeSettings.Builder()
                  .setHardKeyboardConfigurationBehaviorAllowed(true))) {

      // Run test here

  }

  [1]: I0005aee0259f25c96fb94054535dd945a56e8cfb
       6ff19b02ae5aa75f595a0ebd073b641d6edebf89
  [2]: Ib265c6655468ec4788e360523e9f0c29447e1ecf
       3612f05c8f4dddbf11f4a5c7a7fd9059699ee44b

Fix: 71050235
Test: atest CtsInputMethodTestCases
      succeeds with and without a hardware keyboard attached.
Change-Id: I463bb4e9ae665b93950d664e0704ca4e938d32a8
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index 9b2eac3..7b79c26 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -38,6 +38,8 @@
     private static final String WINDOW_FLAGS_MASK = "WindowFlagsMask";
     private static final String FULLSCREEN_MODE_ALLOWED = "FullscreenModeAllowed";
     private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility";
+    private static final String HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED =
+            "HardKeyboardConfigurationBehaviorAllowed";
 
     @NonNull
     private final PersistableBundle mBundle;
@@ -86,6 +88,10 @@
         return mBundle.getInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, defaultFlags);
     }
 
+    public boolean getHardKeyboardConfigurationBehaviorAllowed(boolean defaultValue) {
+        return mBundle.getBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, defaultValue);
+    }
+
     static void writeToParcel(@NonNull Parcel parcel, @NonNull String eventCallbackActionName,
             @Nullable Builder builder) {
         parcel.writeString(eventCallbackActionName);
@@ -174,5 +180,28 @@
             mBundle.putInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, visibilityFlags);
             return this;
         }
+
+        /**
+         * Controls whether {@link MockIme} is allowed to change the behavior based on
+         * {@link android.content.res.Configuration#keyboard} and
+         * {@link android.content.res.Configuration#hardKeyboardHidden}.
+         *
+         * <p>Methods in {@link android.inputmethodservice.InputMethodService} such as
+         * {@link android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()} and
+         * {@link android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)}
+         * change their behaviors when a hardware keyboard is attached.  This is confusing when
+         * writing tests so by default {@link MockIme} tries to cancel those behaviors.  This
+         * settings re-enables such a behavior.</p>
+         *
+         * @param allowed {@code true} when {@link MockIme} is allowed to change the behavior when
+         *                a hardware keyboard is attached
+         *
+         * @see android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()
+         * @see android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)
+         */
+        public Builder setHardKeyboardConfigurationBehaviorAllowed(boolean allowed) {
+            mBundle.putBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, allowed);
+            return this;
+        }
     }
 }
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 8cf4dfc..16a90d6 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -25,7 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.Color;
+import android.content.res.Configuration;
 import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
 import android.os.Handler;
@@ -37,6 +37,7 @@
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.support.annotation.AnyThread;
+import android.support.annotation.CallSuper;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
@@ -49,6 +50,7 @@
 import android.view.WindowInsets;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethod;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
@@ -352,6 +354,41 @@
         getTracer().onFinishInput(() -> super.onFinishInput());
     }
 
+    @CallSuper
+    public boolean onEvaluateInputViewShown() {
+        return getTracer().onEvaluateInputViewShown(() -> {
+            // onShowInputRequested() is indeed @CallSuper so we always call this, even when the
+            // result is ignored.
+            final boolean originalResult = super.onEvaluateInputViewShown();
+            if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
+                final Configuration config = getResources().getConfiguration();
+                if (config.keyboard != Configuration.KEYBOARD_NOKEYS
+                        && config.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES) {
+                    // Override the behavior of InputMethodService#onEvaluateInputViewShown()
+                    return true;
+                }
+            }
+            return originalResult;
+        });
+    }
+
+    @Override
+    public boolean onShowInputRequested(int flags, boolean configChange) {
+        return getTracer().onShowInputRequested(flags, configChange, () -> {
+            // onShowInputRequested() is not marked with @CallSuper, but just in case.
+            final boolean originalResult = super.onShowInputRequested(flags, configChange);
+            if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
+                if ((flags & InputMethod.SHOW_EXPLICIT) == 0
+                        && getResources().getConfiguration().keyboard
+                        != Configuration.KEYBOARD_NOKEYS) {
+                    // Override the behavior of InputMethodService#onShowInputRequested()
+                    return true;
+                }
+            }
+            return originalResult;
+        });
+    }
+
     @Override
     public void onDestroy() {
         getTracer().onDestroy(() -> {
@@ -435,11 +472,6 @@
             recordEventInternal(eventName, () -> { runnable.run(); return null; }, arguments);
         }
 
-        private boolean recordEventInternal(@NonNull String eventName,
-                @NonNull BooleanSupplier supplier) {
-            return recordEventInternal(eventName, () -> supplier.getAsBoolean(), new Bundle());
-        }
-
         private <T> T recordEventInternal(@NonNull String eventName,
                 @NonNull Supplier<T> supplier) {
             return recordEventInternal(eventName, supplier, new Bundle());
@@ -479,8 +511,12 @@
             recordEventInternal("onConfigureWindow", runnable, arguments);
         }
 
-        public boolean onEvaluateFullscreenMode(@NonNull BooleanSupplier runnable) {
-            return recordEventInternal("onEvaluateFullscreenMode", runnable);
+        public boolean onEvaluateFullscreenMode(@NonNull BooleanSupplier supplier) {
+            return recordEventInternal("onEvaluateFullscreenMode", supplier::getAsBoolean);
+        }
+
+        public boolean onEvaluateInputViewShown(@NonNull BooleanSupplier supplier) {
+            return recordEventInternal("onEvaluateInputViewShown", supplier::getAsBoolean);
         }
 
         public View onCreateInputView(@NonNull Supplier<View> supplier) {
@@ -513,6 +549,14 @@
             recordEventInternal("onFinishInput", runnable);
         }
 
+        public boolean onShowInputRequested(int flags, boolean configChange,
+                @NonNull BooleanSupplier supplier) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("flags", flags);
+            arguments.putBoolean("configChange", configChange);
+            return recordEventInternal("onShowInputRequested", supplier::getAsBoolean, arguments);
+        }
+
         public void onDestroy(@NonNull Runnable runnable) {
             recordEventInternal("onDestroy", runnable);
         }