Introduce a custom intent action to close software keyboard

This CL introduces a custom intent action for apps to ask AOSP Keyboard to
close its software keyboard with guarding it with a signature-protected
permission.

Any app that is signed with the same signature as AOSP Keyboard can have
the following line in AndroidManifest.xml

  <uses-permission
          android:name="com.android.inputmethod.latin.HIDE_SOFT_INPUT"/>

to request AOSP Keyboard to close its software keyboard as follows.

  sendBroadcast(new Intent("com.android.inputmethod.latin.HIDE_SOFT_INPUT")
          .setPackage("com.android.inputmethod.latin"));

Test: Manually verified with a test app.
Fixes: 65270710
Change-Id: I4fd2e3a7336ec66c70582a2f274a200cbf035a7f
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 9bb16c7..dedece5 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -35,6 +35,23 @@
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
 
+    <!-- A signature-protected permission to ask AOSP Keyboard to close the software keyboard.
+         To use this, add the following line into calling application's AndroidManifest.xml
+         <pre>
+         {@code
+         <uses-permission android:name="com.android.inputmethod.latin.HIDE_SOFT_INPUT"/>
+         }
+         </pre>
+         then call {@link android.content.Context#sendBroadcast(Intent)} as follows:
+         <pre>
+         {@code
+         sendBroadcast(new Intent("com.android.inputmethod.latin.HIDE_SOFT_INPUT")
+                 .setPackage("com.android.inputmethod.latin"));
+         }
+         </pre> -->
+    <permission android:name="com.android.inputmethod.latin.HIDE_SOFT_INPUT"
+                android:protectionLevel="signature" />
+
     <application android:label="@string/english_ime_name"
             android:icon="@drawable/ic_launcher_keyboard"
             android:supportsRtl="true"
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 1764ded..00ed52c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -124,6 +124,18 @@
     static final long DELAY_DEALLOCATE_MEMORY_MILLIS = TimeUnit.SECONDS.toMillis(10);
 
     /**
+     * A broadcast intent action to hide the software keyboard.
+     */
+    static final String ACTION_HIDE_SOFT_INPUT =
+            "com.android.inputmethod.latin.HIDE_SOFT_INPUT";
+
+    /**
+     * A custom permission for external apps to send {@link #ACTION_HIDE_SOFT_INPUT}.
+     */
+    static final String PERMISSION_HIDE_SOFT_INPUT =
+            "com.android.inputmethod.latin.HIDE_SOFT_INPUT";
+
+    /**
      * The name of the scheme used by the Package Manager to warn of a new package installation,
      * replacement or removal.
      */
@@ -160,6 +172,25 @@
     private final BroadcastReceiver mDictionaryDumpBroadcastReceiver =
             new DictionaryDumpBroadcastReceiver(this);
 
+    final static class HideSoftInputReceiver extends BroadcastReceiver {
+        private final InputMethodService mIms;
+
+        public HideSoftInputReceiver(InputMethodService ims) {
+            mIms = ims;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (ACTION_HIDE_SOFT_INPUT.equals(action)) {
+                mIms.requestHideSelf(0 /* flags */);
+            } else {
+                Log.e(TAG, "Unexpected intent " + intent);
+            }
+        }
+    }
+    final HideSoftInputReceiver mHideSoftInputReceiver = new HideSoftInputReceiver(this);
+
     private AlertDialog mOptionsDialog;
 
     private final boolean mIsHardwareAcceleratedDrawingEnabled;
@@ -595,6 +626,11 @@
         dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
         registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter);
 
+        final IntentFilter hideSoftInputFilter = new IntentFilter();
+        hideSoftInputFilter.addAction(ACTION_HIDE_SOFT_INPUT);
+        registerReceiver(mHideSoftInputReceiver, hideSoftInputFilter, PERMISSION_HIDE_SOFT_INPUT,
+                null /* scheduler */);
+
         StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
     }
 
@@ -699,6 +735,7 @@
     public void onDestroy() {
         mDictionaryFacilitator.closeDictionaries();
         mSettings.onDestroy();
+        unregisterReceiver(mHideSoftInputReceiver);
         unregisterReceiver(mRingerModeChangeReceiver);
         unregisterReceiver(mDictionaryPackInstallReceiver);
         unregisterReceiver(mDictionaryDumpBroadcastReceiver);