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

Commit f9e989d5 authored by Jeff Brown's avatar Jeff Brown
Browse files

Queues, queues, queues and input.

Redesigned how ViewRootImpl delivers input events to views,
the IME and to native activities to fix several issues.

The prior change to make IME input event delegation use
InputChannels failed to take into account that InputMethodManager
is a singleton attached to the main looper whereas UI may be
attached to any looper.  Consequently interactions with the
InputChannel might occur on the wrong thread.  Fixed this
problem by checking the current thread and posting input
events or callbacks to the correct looper when necessary.

NativeActivity has also been broken for a while because the
default event handling logic for joysticks and touch navigation
was unable to dispatch events back into the native activity.
In particular, this meant that DPad synthesis from touch navigation
would not work in any native activity.  The plan is to fix
this problem by passing all events through ViewRootImpl as usual
then forwarding them to native activity as needed.  This should
greatly simplify IME pre-dispatch and system key handling
and make everything more robust overall.

Fixed issues related to when input events are synthesized.
In particular, added a more robust mechanism to ensure that
synthetic events are canceled appropriately when we discover
that events are no longer being resynthesized (because the
application or IME is handling or dropping them).

The new design is structured as a pipeline with a chain of
responsibility consisting of InputStage objects.  Each InputStage
is responsible for some part of handling each input event
such as dispatching to the view hierarchy or to the IME.
As a stage processes an input event, it has the option of
finishing the event, forwarding the event to the next stage
or handling the event asynchronously.  Some queueing logic
takes care to ensure that events are forwarded downstream in
the correct order even if they are handled out of order
by a given stage.

Cleaned up the InputMethodManager singleton initialization logic
to make it clearer that it must be attached to the main looper.
We don't actually need to pass this looper around.

Deleted the LatencyTimer class since no one uses it and we have
better ways of measuring latency these days using systrace.

Added a hidden helper to Looper to determine whether the current
thread is the indicated Looper thread.

Note: NativeActivity's IME dispatch is broken by this patch.
This will be fixed later in another patch.

Bug: 8473020
Change-Id: Iac2a1277545195a7a0137bbbdf04514c29165c60
parent 1951ce86
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -371,9 +371,9 @@ class ContextImpl extends Context {
                    return new DisplayManager(ctx.getOuterContext());
                }});

        registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return InputMethodManager.getInstance(ctx);
        registerService(INPUT_METHOD_SERVICE, new StaticServiceFetcher() {
                public Object createStaticService() {
                    return InputMethodManager.getInstance();
                }});

        registerService(TEXT_SERVICES_MANAGER_SERVICE, new ServiceFetcher() {
+17 −22
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.app;

import com.android.internal.view.IInputMethodSession;

import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
@@ -25,7 +38,6 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.inputmethod.InputMethodManager;

import java.io.File;
import java.lang.ref.WeakReference;

/**
 * Convenience for implementing an activity that will be implemented
@@ -65,7 +77,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,

    private NativeContentView mNativeContentView;
    private InputMethodManager mIMM;
    private InputMethodCallback mInputMethodCallback;

    private int mNativeHandle;
    
@@ -118,22 +129,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
        }
    }

    static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback {
        WeakReference<NativeActivity> mNa;

        InputMethodCallback(NativeActivity na) {
            mNa = new WeakReference<NativeActivity>(na);
        }

        @Override
        public void finishedEvent(int seq, boolean handled) {
            NativeActivity na = mNa.get();
            if (na != null) {
                na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled);
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        String libname = "main";
@@ -141,7 +136,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
        ActivityInfo ai;
        
        mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
        mInputMethodCallback = new InputMethodCallback(this);

        getWindow().takeSurface(this);
        getWindow().takeInputQueue(this);
@@ -353,7 +347,8 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
    }
    
    void preDispatchKeyEvent(KeyEvent event, int seq) {
        mIMM.dispatchInputEvent(this, seq, event, mInputMethodCallback);
        // FIXME: Input dispatch should be redirected back through ViewRootImpl again.
        finishPreDispatchKeyEventNative(mNativeHandle, seq, false);
    }

    void setWindowFlags(int flags, int mask) {
+3 −3
Original line number Diff line number Diff line
@@ -709,7 +709,7 @@ public class WallpaperManager {
    public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
        try {
            //Log.v(TAG, "Sending new wallpaper offsets from app...");
            WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
            WindowManagerGlobal.getWindowSession().setWallpaperPosition(
                    windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep);
            //Log.v(TAG, "...app returning after sending offsets!");
        } catch (RemoteException e) {
@@ -747,7 +747,7 @@ public class WallpaperManager {
            int x, int y, int z, Bundle extras) {
        try {
            //Log.v(TAG, "Sending new wallpaper offsets from app...");
            WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand(
            WindowManagerGlobal.getWindowSession().sendWallpaperCommand(
                    windowToken, action, x, y, z, extras, false);
            //Log.v(TAG, "...app returning after sending offsets!");
        } catch (RemoteException e) {
@@ -767,7 +767,7 @@ public class WallpaperManager {
     */
    public void clearWallpaperOffsets(IBinder windowToken) {
        try {
            WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
            WindowManagerGlobal.getWindowSession().setWallpaperPosition(
                    windowToken, -1, -1, -1, -1);
        } catch (RemoteException e) {
            // Ignore.
+6 −0
Original line number Diff line number Diff line
@@ -256,6 +256,12 @@ public abstract class Context {
     * Return the Looper for the main thread of the current process.  This is
     * the thread used to dispatch calls to application components (activities,
     * services, etc).
     * <p>
     * By definition, this method returns the same result as would be obtained
     * by calling {@link Looper#getMainLooper() Looper.getMainLooper()}.
     * </p>
     *
     * @return The main looper.
     */
    public abstract Looper getMainLooper();

+0 −94
Original line number Diff line number Diff line
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.os;

import android.util.Log;

import java.util.HashMap;

/**
 * A class to help with measuring latency in your code.
 * 
 * Suggested usage:
 * 1) Instanciate a LatencyTimer as a class field.
 *      private [static] LatencyTimer mLt = new LatencyTimer(100, 1000);
 * 2) At various points in the code call sample with a string and the time delta to some fixed time.
 *    The string should be unique at each point of the code you are measuring.
 *      mLt.sample("before processing event", System.nanoTime() - event.getEventTimeNano());
 *      processEvent(event);
 *      mLt.sample("after processing event ", System.nanoTime() - event.getEventTimeNano());
 *
 * @hide
 */
public final class LatencyTimer
{
    final String TAG = "LatencyTimer";
    final int mSampleSize;
    final int mScaleFactor;
    volatile HashMap<String, long[]> store = new HashMap<String, long[]>();

    /**
    * Creates a LatencyTimer object
    * @param sampleSize number of samples to collect before printing out the average
    * @param scaleFactor divisor used to make each sample smaller to prevent overflow when
    *        (sampleSize * average sample value)/scaleFactor > Long.MAX_VALUE
    */
    public LatencyTimer(int sampleSize, int scaleFactor) {
        if (scaleFactor == 0) {
            scaleFactor = 1;
        }
        mScaleFactor = scaleFactor;
        mSampleSize = sampleSize;
    }

    /**
     * Add a sample delay for averaging.
     * @param tag string used for printing out the result. This should be unique at each point of
     *  this called.
     * @param delta time difference from an unique point of reference for a particular iteration
     */
    public void sample(String tag, long delta) {
        long[] array = getArray(tag);

        // array[mSampleSize] holds the number of used entries
        final int index = (int) array[mSampleSize]++;
        array[index] = delta;
        if (array[mSampleSize] == mSampleSize) {
            long totalDelta = 0;
            for (long d : array) {
                totalDelta += d/mScaleFactor;
            }
            array[mSampleSize] = 0;
            Log.i(TAG, tag + " average = " + totalDelta / mSampleSize);
        }
    }

    private long[] getArray(String tag) {
        long[] data = store.get(tag);
        if (data == null) {
            synchronized(store) {
                data = store.get(tag);
                if (data == null) {
                    data = new long[mSampleSize + 1];
                    store.put(tag, data);
                    data[mSampleSize] = 0;
                }
            }
        }
        return data;
    }
}
Loading