Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 04ddf3c0 authored by Jeff Brown's avatar Jeff Brown
Browse files

Allow applications to recover from IME related ANRs.

Timeout after 2.5 seconds.

Because communication with an IME occurs asynchronously
using oneway binder calls, it's possible for an input event
that was delegated to the IME to be dropped on the floor.
When this happens, the app (not the IME!) will get blamed
for the problem and will ANR forever.

Even if an event is not dropped on the floor, we should
eventually time out event dispatch to the IME if it's
being too slow.

This patch implements a timeout on all events delegated
to the IME.  When the timeout expires, the event is marked
as having not been handled by the IME and the application
gets a crack at it.  We also write a message to the log when
this occurs.

Ensure that we do not invoke the event finished callback
while holding the InputMethodManager's lock to avoid
potential deadlocks.

Fixed a minor bug where the InputMethodManager would not
remember the id of the current input method.  This caused
the log messages and dumpsys state to print "null" as the
current input method id.

Bug: 6662465
Change-Id: Ibb3ddeb087ee6998996b0b845134e16a18aa3057
parent dcd3f375
Loading
Loading
Loading
Loading
+1 −7
Original line number Diff line number Diff line
package android.app;

import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;

import android.content.Context;
@@ -119,7 +118,7 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
        }
    }
    
    static class InputMethodCallback extends IInputMethodCallback.Stub {
    static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback {
        WeakReference<NativeActivity> mNa;

        InputMethodCallback(NativeActivity na) {
@@ -133,11 +132,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
                na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled);
            }
        }

        @Override
        public void sessionCreated(IInputMethodSession session) {
            // Stub -- not for use in the client.
        }
    }

    @Override
+2 −6
Original line number Diff line number Diff line
@@ -76,7 +76,6 @@ import android.widget.Scroller;
import com.android.internal.R;
import com.android.internal.policy.PolicyManager;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.RootViewSurfaceTaker;

@@ -4667,23 +4666,20 @@ public final class ViewRootImpl implements ViewParent,
        }
    }
    
    static class InputMethodCallback extends IInputMethodCallback.Stub {
    static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback {
        private WeakReference<ViewRootImpl> mViewAncestor;

        public InputMethodCallback(ViewRootImpl viewAncestor) {
            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
        }

        @Override
        public void finishedEvent(int seq, boolean handled) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchImeFinishedEvent(seq, handled);
            }
        }

        public void sessionCreated(IInputMethodSession session) {
            // Stub -- not for use in the client.
        }
    }

    static class W extends IWindow.Stub {
+185 −47
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.PrintWriterPrinter;
@@ -225,6 +226,13 @@ public final class InputMethodManager {
     */
    public static final int CONTROL_START_INITIAL = 1<<8;

    /**
     * Timeout in milliseconds for delivering a key to an IME.
     */
    static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500;

    private static final int MAX_PENDING_EVENT_POOL_SIZE = 4;

    final IInputMethodManager mService;
    final Looper mMainLooper;
    
@@ -312,12 +320,17 @@ public final class InputMethodManager {
     */
    IInputMethodSession mCurMethod;

    PendingEvent mPendingEventPool;
    int mPendingEventPoolSize;
    PendingEvent mFirstPendingEvent;

    // -----------------------------------------------------------
    
    static final int MSG_DUMP = 1;
    static final int MSG_BIND = 2;
    static final int MSG_UNBIND = 3;
    static final int MSG_SET_ACTIVE = 4;
    static final int MSG_EVENT_TIMEOUT = 5;
    
    class H extends Handler {
        H(Looper looper) {
@@ -413,6 +426,17 @@ public final class InputMethodManager {
                    }
                    return;
                }
                case MSG_EVENT_TIMEOUT: {
                    // Even though the message contains both the sequence number
                    // and the PendingEvent object itself, we only pass the
                    // sequence number to the timeoutEvent function because it's
                    // possible for the PendingEvent object to be dequeued and
                    // recycled concurrently.  To avoid a possible race, we make
                    // a point of always looking up the PendingEvent within the
                    // queue given only the sequence number of the event.
                    timeoutEvent(msg.arg1);
                    return;
                }
            }
        }
    }
@@ -477,6 +501,18 @@ public final class InputMethodManager {
    
    final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);

    final IInputMethodCallback mInputMethodCallback = new IInputMethodCallback.Stub() {
        @Override
        public void finishedEvent(int seq, boolean handled) {
            InputMethodManager.this.finishedEvent(seq, handled);
        }

        @Override
        public void sessionCreated(IInputMethodSession session) {
            // Stub -- not for use in the client.
        }
    };
    
    InputMethodManager(IInputMethodManager service, Looper looper) {
        mService = service;
        mMainLooper = looper;
@@ -1105,6 +1141,7 @@ public final class InputMethodManager {
                    if (res.id != null) {
                        mBindSequence = res.sequence;
                        mCurMethod = res.method;
                        mCurId = res.id;
                    } else if (mCurMethod == null) {
                        // This means there is no input method available.
                        if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
@@ -1511,78 +1548,161 @@ public final class InputMethodManager {
     * @hide
     */
    public void dispatchKeyEvent(Context context, int seq, KeyEvent key,
            IInputMethodCallback callback) {
            FinishedEventCallback callback) {
        boolean handled = false;
        synchronized (mH) {
            if (DEBUG) Log.d(TAG, "dispatchKeyEvent");

            if (mCurMethod == null) {
                try {
                    callback.finishedEvent(seq, false);
                } catch (RemoteException e) {
                }
                return;
            }
    
            if (mCurMethod != null) {
                if (key.getAction() == KeyEvent.ACTION_DOWN
                        && key.getKeyCode() == KeyEvent.KEYCODE_SYM) {
                showInputMethodPicker();
                try {
                    callback.finishedEvent(seq, true);
                } catch (RemoteException e) {
                }
                return;
            }
                    showInputMethodPickerLocked();
                    handled = true;
                } else {
                    try {
                        if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod);
                mCurMethod.dispatchKeyEvent(seq, key, callback);
                        final long startTime = SystemClock.uptimeMillis();
                        mCurMethod.dispatchKeyEvent(seq, key, mInputMethodCallback);
                        enqueuePendingEventLocked(startTime, seq, mCurId, callback);
                        return;
                    } catch (RemoteException e) {
                        Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
                try {
                    callback.finishedEvent(seq, false);
                } catch (RemoteException ex) {
                    }
                }
            }
        }

        callback.finishedEvent(seq, handled);
    }

    /**
     * @hide
     */
    void dispatchTrackballEvent(Context context, int seq, MotionEvent motion,
            IInputMethodCallback callback) {
            FinishedEventCallback callback) {
        synchronized (mH) {
            if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");

            if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
            if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
                try {
                    callback.finishedEvent(seq, false);
                    if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
                    final long startTime = SystemClock.uptimeMillis();
                    mCurMethod.dispatchTrackballEvent(seq, motion, mInputMethodCallback);
                    enqueuePendingEventLocked(startTime, seq, mCurId, callback);
                    return;
                } catch (RemoteException e) {
                    Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
                }
            }
                return;
        }

            try {
                if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
                mCurMethod.dispatchTrackballEvent(seq, motion, callback);
            } catch (RemoteException e) {
                Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
                try {
        callback.finishedEvent(seq, false);
                } catch (RemoteException ex) {
    }

    void finishedEvent(int seq, boolean handled) {
        final FinishedEventCallback callback;
        synchronized (mH) {
            PendingEvent p = dequeuePendingEventLocked(seq);
            if (p == null) {
                return; // spurious, event already finished or timed out
            }
            mH.removeMessages(MSG_EVENT_TIMEOUT, p);
            callback = p.mCallback;
            recyclePendingEventLocked(p);
        }
        callback.finishedEvent(seq, handled);
    }

    void timeoutEvent(int seq) {
        final FinishedEventCallback callback;
        synchronized (mH) {
            PendingEvent p = dequeuePendingEventLocked(seq);
            if (p == null) {
                return; // spurious, event already finished or timed out
            }
            long delay = SystemClock.uptimeMillis() - p.mStartTime;
            Log.w(TAG, "Timeout waiting for IME to handle input event after "
                    + delay + "ms: " + p.mInputMethodId);
            callback = p.mCallback;
            recyclePendingEventLocked(p);
        }
        callback.finishedEvent(seq, false);
    }

    private void enqueuePendingEventLocked(
            long startTime, int seq, String inputMethodId, FinishedEventCallback callback) {
        PendingEvent p = obtainPendingEventLocked(startTime, seq, inputMethodId, callback);
        p.mNext = mFirstPendingEvent;
        mFirstPendingEvent = p;

        Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, seq, 0, p);
        msg.setAsynchronous(true);
        mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);
    }

    private PendingEvent dequeuePendingEventLocked(int seq) {
        PendingEvent p = mFirstPendingEvent;
        if (p == null) {
            return null;
        }
        if (p.mSeq == seq) {
            mFirstPendingEvent = p.mNext;
        } else {
            PendingEvent prev = p;
            do {
                p = prev.mNext;
                if (p == null) {
                    return null;
                }
            } while (p.mSeq != seq);
            prev.mNext = p.mNext;
        }
        p.mNext = null;
        return p;
    }

    private PendingEvent obtainPendingEventLocked(
            long startTime, int seq, String inputMethodId, FinishedEventCallback callback) {
        PendingEvent p = mPendingEventPool;
        if (p != null) {
            mPendingEventPoolSize -= 1;
            mPendingEventPool = p.mNext;
            p.mNext = null;
        } else {
            p = new PendingEvent();
        }

        p.mStartTime = startTime;
        p.mSeq = seq;
        p.mInputMethodId = inputMethodId;
        p.mCallback = callback;
        return p;
    }

    private void recyclePendingEventLocked(PendingEvent p) {
        p.mInputMethodId = null;
        p.mCallback = null;

        if (mPendingEventPoolSize < MAX_PENDING_EVENT_POOL_SIZE) {
            mPendingEventPoolSize += 1;
            p.mNext = mPendingEventPool;
            mPendingEventPool = p;
        }
    }

    public void showInputMethodPicker() {
        synchronized (mH) {
            showInputMethodPickerLocked();
        }
    }

    private void showInputMethodPickerLocked() {
        try {
            mService.showInputMethodPickerFromClient(mClient);
        } catch (RemoteException e) {
            Log.w(TAG, "IME died: " + mCurId, e);
        }
    }
    }

    /**
     * Show the settings for enabling subtypes of the specified input method.
@@ -1773,4 +1893,22 @@ public final class InputMethodManager {
                + " mCursorCandStart=" + mCursorCandStart
                + " mCursorCandEnd=" + mCursorCandEnd);
    }

    /**
     * Callback that is invoked when an input event that was dispatched to
     * the IME has been finished.
     * @hide
     */
    public interface FinishedEventCallback {
        public void finishedEvent(int seq, boolean handled);
    }

    private static final class PendingEvent {
        public PendingEvent mNext;

        public long mStartTime;
        public int mSeq;
        public String mInputMethodId;
        public FinishedEventCallback mCallback;
    }
}