diff --git a/api/current.txt b/api/current.txt
index 0857c8bb696ea2581fe0917082da2b651e8939b9..5906f996a77e5f10eeb3fdbf69a147ceee56204c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -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 {
   }
 
 }
+
diff --git a/api/system-current.txt b/api/system-current.txt
index 99a4ad3c448a60a19353e8c804359f44b0cf47c7..a3ed210d7d16aae35716ca9ede73f16aca30fe25 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -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 {
   }
 
 }
+
diff --git a/api/test-current.txt b/api/test-current.txt
index 6cf047a2d411c9cfcc896210bee051631ca7af35..6a31de4dd635181962b9927fc7528379ca469a40 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -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 {
   }
 
 }
+
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index a3c49c5a84570bbbd48f83b14b4cd3f67d901f5d..89dec2d22d7a74abf37d0833d72643ed0ef1ac6e 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -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();
     }
 
     /**
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 8002a8e9ba14d434ce11399e47f5ec9203cfa2d6..9f664293476835b2eb8f6f54d0340deb1b3d092b 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -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();
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionInspector.java b/core/java/android/view/inputmethod/InputConnectionInspector.java
index 46b2c3e0fbea8774b35a0713358accee63096ade..118a61f8d571c3d447b694971144bffd5dcd6d85 100644
--- a/core/java/android/view/inputmethod/InputConnectionInspector.java
+++ b/core/java/android/view/inputmethod/InputConnectionInspector.java
@@ -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();
     }
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 381df49d8a69262e53b2a645e071cfe4b1bee3af..e743f62a5a0a6df97288951d4acdc5e6e1d7f51b 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -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();
+    }
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f5908a50ec64b5009f92949ed7a58826a45687bb..687990124a5585f87f0f200f78736b2125b8fd73 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -544,7 +544,7 @@ public final class InputMethodManager {
                 // reportFinish() will take effect.
                 return;
             }
-            reportFinish();
+            closeConnection();
         }
 
         @Override
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 7cbe8de674920c758a3b8dbea9fcf056feabb77f..6e1dff9ea0defbc1324caae04fc979e4cd73ba5c 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -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();
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 6c1ebb43287b789423e2156b84998b554a6132ad..3a4afad3f3670a4e08c6030b80614e45abc9e09e 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -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) {
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 85b8606f75003b2c51dd5a712ba0ea7d8b23a2a4..f9884d85d52cb7337e7cf1633545ce3efa6e735b 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -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;
     }
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index a96b5a0d6ab12047651c70111375934da688db23..7f7528dd3de07073f5117181e6c82149e3b0119c 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -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();