OSDN Git Service

Add IC#closeConnection().
authorYohei Yukawa <yukawa@google.com>
Wed, 30 Mar 2016 19:03:51 +0000 (12:03 -0700)
committerYohei Yukawa <yukawa@google.com>
Wed, 30 Mar 2016 19:03:51 +0000 (12:03 -0700)
It turns out that BaseInputConnection has still depended on a private
API named BaseInputConnection#reportFinish(), which was introduced
4 years ago to work around a UI freeze due to an unbalanced batch edit
count [1].  Note that such an unbalanced batch edit count cannot always
be avoidable.  It can easily occur in the following situations.
 - The current IME crashed during batch edit.
 - The user changed the View focus during batch edit.
 - The current IME called IMM#switchToNextInputMethod() during batch
   edit.

The remaining problem is that #reportFinish() is still an internal API
and only subclasses of BaseInputConnection can implement it, and IMM
calls it when and only when the current InputConnection is
BaseInputConnection or its subclass.  InputConnectionWrapper and any
other InputConnection implementations will never receive such a callback
to clean up InputConnection#{begin, end}BatchEdit(), which is considered
to be a major contributor to UI freeze.

To address the above issue, we unhide BaseInputConnection#reportFinish()
as InputConnection#closeConnection() so that application developers can
receive an appropriate callback to clean up internal state including
unfinished batch edit.

  [1] I5525d776916f0c42d5e6d4a4282aed590d7f0e9a
      9d69ecbf61a4a142c3f4cbb9d5659faa6f85e832

Bug: 24688781
Bug: 25332806
Change-Id: I234309c5880c9fe0b299b8bd0f8862796d4dda0d

12 files changed:
api/current.txt
api/system-current.txt
api/test-current.txt
core/java/android/view/inputmethod/BaseInputConnection.java
core/java/android/view/inputmethod/InputConnection.java
core/java/android/view/inputmethod/InputConnectionInspector.java
core/java/android/view/inputmethod/InputConnectionWrapper.java
core/java/android/view/inputmethod/InputMethodManager.java
core/java/android/widget/AbsListView.java
core/java/com/android/internal/view/IInputConnectionWrapper.java
core/java/com/android/internal/view/InputConnectionWrapper.java
core/java/com/android/internal/widget/EditableInputConnection.java

index 0857c8b..5906f99 100644 (file)
@@ -44641,6 +44641,7 @@ package android.view.inputmethod {
     ctor public BaseInputConnection(android.view.View, boolean);
     method public boolean beginBatchEdit();
     method public boolean clearMetaKeyStates(int);
+    method public void closeConnection();
     method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
@@ -44808,6 +44809,7 @@ package android.view.inputmethod {
   public abstract interface InputConnection {
     method public abstract boolean beginBatchEdit();
     method public abstract boolean clearMetaKeyStates(int);
+    method public abstract void closeConnection();
     method public abstract boolean commitCompletion(android.view.inputmethod.CompletionInfo);
     method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public abstract boolean commitText(java.lang.CharSequence, int);
@@ -44840,6 +44842,7 @@ package android.view.inputmethod {
     ctor public InputConnectionWrapper(android.view.inputmethod.InputConnection, boolean);
     method public boolean beginBatchEdit();
     method public boolean clearMetaKeyStates(int);
+    method public void closeConnection();
     method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
@@ -66433,3 +66436,4 @@ package org.xmlpull.v1.sax2 {
   }
 
 }
+
index 99a4ad3..a3ed210 100644 (file)
@@ -47370,6 +47370,7 @@ package android.view.inputmethod {
     ctor public BaseInputConnection(android.view.View, boolean);
     method public boolean beginBatchEdit();
     method public boolean clearMetaKeyStates(int);
+    method public void closeConnection();
     method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
@@ -47537,6 +47538,7 @@ package android.view.inputmethod {
   public abstract interface InputConnection {
     method public abstract boolean beginBatchEdit();
     method public abstract boolean clearMetaKeyStates(int);
+    method public abstract void closeConnection();
     method public abstract boolean commitCompletion(android.view.inputmethod.CompletionInfo);
     method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public abstract boolean commitText(java.lang.CharSequence, int);
@@ -47569,6 +47571,7 @@ package android.view.inputmethod {
     ctor public InputConnectionWrapper(android.view.inputmethod.InputConnection, boolean);
     method public boolean beginBatchEdit();
     method public boolean clearMetaKeyStates(int);
+    method public void closeConnection();
     method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
@@ -69498,3 +69501,4 @@ package org.xmlpull.v1.sax2 {
   }
 
 }
+
index 6cf047a..6a31de4 100644 (file)
@@ -44715,6 +44715,7 @@ package android.view.inputmethod {
     ctor public BaseInputConnection(android.view.View, boolean);
     method public boolean beginBatchEdit();
     method public boolean clearMetaKeyStates(int);
+    method public void closeConnection();
     method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
@@ -44882,6 +44883,7 @@ package android.view.inputmethod {
   public abstract interface InputConnection {
     method public abstract boolean beginBatchEdit();
     method public abstract boolean clearMetaKeyStates(int);
+    method public abstract void closeConnection();
     method public abstract boolean commitCompletion(android.view.inputmethod.CompletionInfo);
     method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public abstract boolean commitText(java.lang.CharSequence, int);
@@ -44914,6 +44916,7 @@ package android.view.inputmethod {
     ctor public InputConnectionWrapper(android.view.inputmethod.InputConnection, boolean);
     method public boolean beginBatchEdit();
     method public boolean clearMetaKeyStates(int);
+    method public void closeConnection();
     method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
@@ -66507,3 +66510,4 @@ package org.xmlpull.v1.sax2 {
   }
 
 }
+
index a3c49c5..89dec2d 100644 (file)
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.annotation.CallSuper;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.os.Bundle;
@@ -154,12 +155,11 @@ public class BaseInputConnection implements InputConnection {
     }
 
     /**
-     * Called when this InputConnection is no longer used by the InputMethodManager.
-     *
-     * @hide
+     * Default implementation calls {@link #finishComposingText()}.
      */
-    public void reportFinish() {
-        // Intentionally empty
+    @CallSuper
+    public void closeConnection() {
+        finishComposingText();
     }
 
     /**
index 8002a8e..9f66429 100644 (file)
@@ -45,6 +45,8 @@ import android.view.KeyEvent;
  *     was introduced in {@link android.os.Build.VERSION_CODES#N}.</li>
  *     <li>{@link #getHandler()}}, which was introduced in
  *     {@link android.os.Build.VERSION_CODES#N}.</li>
+ *     <li>{@link #closeConnection()}}, which was introduced in
+ *     {@link android.os.Build.VERSION_CODES#N}.</li>
  * </ul>
  *
  * <h3>Implementing an IME or an editor</h3>
@@ -820,4 +822,18 @@ public interface InputConnection {
      * @return {@code null} to use the default {@link Handler}.
      */
     public Handler getHandler();
+
+    /**
+     * Called by the system up to only once to notify that the system is about to invalidate
+     * connection between the input method and the application.
+     *
+     * <p><strong>Editor authors</strong>: You can clear all the nested batch edit right now and
+     * you no longer need to handle subsequent callbacks on this connection, including
+     * {@link #beginBatchEdit()}}.  Note that although the system tries to call this method whenever
+     * possible, there may be a chance that this method is not called in some exceptional
+     * situations.</p>
+     *
+     * <p>Note: This does nothing when called from input methods.</p>
+     */
+    public void closeConnection();
 }
index 46b2c3e..118a61f 100644 (file)
@@ -73,6 +73,11 @@ public final class InputConnectionInspector {
          * {@link android.os.Build.VERSION_CODES#N} and later.
          */
         int GET_HANDLER = 1 << 5;
+        /**
+         * {@link InputConnection#closeConnection()}} is available in
+         * {@link android.os.Build.VERSION_CODES#N} and later.
+         */
+        int CLOSE_CONNECTION = 1 << 6;
     }
 
     private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
@@ -119,6 +124,9 @@ public final class InputConnectionInspector {
         if (!hasGetHandler(clazz)) {
             flags |= MissingMethodFlags.GET_HANDLER;
         }
+        if (!hasCloseConnection(clazz)) {
+            flags |= MissingMethodFlags.CLOSE_CONNECTION;
+        }
         sMissingMethodsMap.put(clazz, flags);
         return flags;
     }
@@ -178,6 +186,15 @@ public final class InputConnectionInspector {
         }
     }
 
+    private static boolean hasCloseConnection(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("closeConnection");
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
     public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
         final StringBuilder sb = new StringBuilder();
         boolean isEmpty = true;
@@ -219,6 +236,12 @@ public final class InputConnectionInspector {
             }
             sb.append("getHandler()");
         }
+        if ((flags & MissingMethodFlags.CLOSE_CONNECTION) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("closeConnection()");
+        }
         return sb.toString();
     }
 }
index 381df49..e743f62 100644 (file)
@@ -261,4 +261,12 @@ public class InputConnectionWrapper implements InputConnection {
     public Handler getHandler() {
         return mTarget.getHandler();
     }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    public void closeConnection() {
+        mTarget.closeConnection();
+    }
 }
index f5908a5..6879901 100644 (file)
@@ -544,7 +544,7 @@ public final class InputMethodManager {
                 // reportFinish() will take effect.
                 return;
             }
-            reportFinish();
+            closeConnection();
         }
 
         @Override
index 7cbe8de..6e1dff9 100644 (file)
@@ -5937,6 +5937,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
         public Handler getHandler() {
             return getTarget().getHandler();
         }
+
+        @Override
+        public void closeConnection() {
+            getTarget().closeConnection();
+        }
     }
 
     /**
index 6c1ebb4..3a4afad 100644 (file)
@@ -27,13 +27,12 @@ import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.KeyEvent;
-import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
-
-import java.lang.ref.WeakReference;
+import android.view.inputmethod.InputConnectionInspector;
+import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
 
 public abstract class IInputConnectionWrapper extends IInputContext.Stub {
     static final String TAG = "IInputConnectionWrapper";
@@ -61,7 +60,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
     private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
     private static final int DO_CLEAR_META_KEY_STATES = 130;
     private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
-    private static final int DO_REPORT_FINISH = 150;
+    private static final int DO_CLOSE_CONNECTION = 150;
 
     @GuardedBy("mLock")
     @Nullable
@@ -222,8 +221,8 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
                 seq, callback));
     }
 
-    public void reportFinish() {
-        dispatchMessage(obtainMessage(DO_REPORT_FINISH));
+    public void closeConnection() {
+        dispatchMessage(obtainMessage(DO_CLOSE_CONNECTION));
     }
 
     void dispatchMessage(Message msg) {
@@ -501,10 +500,10 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
                 }
                 return;
             }
-            case DO_REPORT_FINISH: {
+            case DO_CLOSE_CONNECTION: {
                 // Note that we do not need to worry about race condition here, because 1) mFinished
                 // is updated only inside this block, and 2) the code here is running on a Handler
-                // hence we assume multiple DO_REPORT_FINISH messages will not be handled at the
+                // hence we assume multiple DO_CLOSE_CONNECTION messages will not be handled at the
                 // same time.
                 if (isFinished()) {
                     return;
@@ -518,11 +517,10 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
                     if (ic == null) {
                         return;
                     }
-                    ic.finishComposingText();
-                    // TODO: Make reportFinish() public method of InputConnection to remove this
-                    // check.
-                    if (ic instanceof BaseInputConnection) {
-                        ((BaseInputConnection) ic).reportFinish();
+                    @MissingMethodFlags
+                    final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic);
+                    if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) {
+                        ic.closeConnection();
                     }
                 } finally {
                     synchronized (mLock) {
index 85b8606..f9884d8 100644 (file)
@@ -487,6 +487,10 @@ public class InputConnectionWrapper implements InputConnection {
         return null;
     }
 
+    public void closeConnection() {
+        // Nothing should happen when called from input method.
+    }
+
     private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
         return (mMissingMethods & methodFlag) == methodFlag;
     }
index a96b5a0..7f7528d 100644 (file)
@@ -83,13 +83,9 @@ public class EditableInputConnection extends BaseInputConnection {
         return false;
     }
 
-    /**
-     * @hide
-     */
     @Override
-    public void reportFinish() {
-        super.reportFinish();
-
+    public void closeConnection() {
+        super.closeConnection();
         synchronized(this) {
             while (mBatchEditNesting > 0) {
                 endBatchEdit();