diff options
36 files changed, 1417 insertions, 1418 deletions
diff --git a/api/current.txt b/api/current.txt index 86b6d8fc39bc..5dc32aed7761 100644 --- a/api/current.txt +++ b/api/current.txt @@ -14788,6 +14788,7 @@ package android.os { field public static final int FLAG_ONEWAY = 1; // 0x1 field public static final int INTERFACE_TRANSACTION = 1598968902; // 0x5f4e5446 field public static final int LAST_CALL_TRANSACTION = 16777215; // 0xffffff + field public static final int LIKE_TRANSACTION = 1598835019; // 0x5f4c494b field public static final int PING_TRANSACTION = 1599098439; // 0x5f504e47 field public static final int TWEET_TRANSACTION = 1599362900; // 0x5f545754 } diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index 34c0c2a97761..db7258517379 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -184,6 +184,7 @@ int free_cache(int64_t free_size) DIR *d; struct dirent *de; int64_t avail; + char datadir[PKG_PATH_MAX]; avail = disk_free(); if (avail < 0) return -1; @@ -191,9 +192,14 @@ int free_cache(int64_t free_size) LOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", free_size, avail); if (avail >= free_size) return 0; - d = opendir(android_data_dir.path); + if (create_persona_path(datadir, 0)) { + LOGE("couldn't get directory for persona 0"); + return -1; + } + + d = opendir(datadir); if (d == NULL) { - LOGE("cannot open %s: %s\n", android_data_dir.path, strerror(errno)); + LOGE("cannot open %s: %s\n", datadir, strerror(errno)); return -1; } dfd = dirfd(d); @@ -578,19 +584,6 @@ fail: return -1; } -int create_move_path(char path[PKG_PATH_MAX], - const char* pkgname, - const char* leaf, - uid_t persona) -{ - if ((android_data_dir.len + strlen(pkgname) + strlen(leaf) + 1) >= PKG_PATH_MAX) { - return -1; - } - - sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf); - return 0; -} - void mkinnerdirs(char* path, int basepos, mode_t mode, int uid, int gid, struct stat* statbuf) { diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index c5872b8efa09..173cabfb77a7 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -105,6 +105,11 @@ int create_pkg_path(char path[PKG_PATH_MAX], int create_persona_path(char path[PKG_PATH_MAX], uid_t persona); +int create_move_path(char path[PKG_PATH_MAX], + const char* pkgname, + const char* leaf, + uid_t persona); + int is_valid_package_name(const char* pkgname); int create_cache_path(char path[PKG_PATH_MAX], const char *src); diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp index 1128fceca0af..7cb9b37fe038 100644 --- a/cmds/installd/tests/installd_utils_test.cpp +++ b/cmds/installd/tests/installd_utils_test.cpp @@ -34,6 +34,16 @@ extern "C" { #define TEST_SYSTEM_DIR1 "/system/app/" #define TEST_SYSTEM_DIR2 "/vendor/app/" +#define REALLY_LONG_APP_NAME "com.example." \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +#define REALLY_LONG_LEAF_NAME "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \ + "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \ + "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \ + "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" + namespace android { class UtilsTest : public testing::Test { @@ -210,7 +220,7 @@ TEST_F(UtilsTest, CheckSystemApp_BadPathEscapeFail) { TEST_F(UtilsTest, GetPathFromString_NullPathFail) { dir_rec_t test1; - EXPECT_EQ(-1, get_path_from_string(&test1, NULL)) + EXPECT_EQ(-1, get_path_from_string(&test1, (const char *) NULL)) << "Should not allow NULL as a path."; } @@ -327,6 +337,50 @@ TEST_F(UtilsTest, CreatePkgPathInDir_ProtectedDir) { << "Package path should be in /data/app-private/"; } +TEST_F(UtilsTest, CreatePersonaPath_Primary) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_persona_path(path, 0)) + << "Should successfully build primary user path."; + + EXPECT_STREQ("/data/data/", path) + << "Primary user should have correct path"; +} + +TEST_F(UtilsTest, CreatePersonaPath_Secondary) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_persona_path(path, 1)) + << "Should successfully build primary user path."; + + EXPECT_STREQ("/data/user/1/", path) + << "Primary user should have correct path"; +} + +TEST_F(UtilsTest, CreateMovePath_Primary) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_move_path(path, "com.android.test", "shared_prefs", 0)) + << "Should be able to create move path for primary user"; + + EXPECT_STREQ("/data/data/com.android.test/shared_prefs", path) + << "Primary user package directory should be created correctly"; +} + +TEST_F(UtilsTest, CreateMovePath_Fail_AppTooLong) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(-1, create_move_path(path, REALLY_LONG_APP_NAME, "shared_prefs", 0)) + << "Should fail to create move path for primary user"; +} + +TEST_F(UtilsTest, CreateMovePath_Fail_LeafTooLong) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(-1, create_move_path(path, "com.android.test", REALLY_LONG_LEAF_NAME, 0)) + << "Should fail to create move path for primary user"; +} + TEST_F(UtilsTest, CopyAndAppend_Normal) { //int copy_and_append(dir_rec_t* dst, dir_rec_t* src, char* suffix) dir_rec_t dst; diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c index 3099b8341f6e..a53a93ce1073 100644 --- a/cmds/installd/utils.c +++ b/cmds/installd/utils.c @@ -109,7 +109,7 @@ int create_persona_path(char path[PKG_PATH_MAX], uid_len = 0; } else { persona_prefix = SECONDARY_USER_PREFIX; - uid_len = snprintf(NULL, 0, "%d", persona); + uid_len = snprintf(NULL, 0, "%d/", persona); } char *dst = path; @@ -126,7 +126,7 @@ int create_persona_path(char path[PKG_PATH_MAX], LOGE("Error building user path"); return -1; } - int ret = snprintf(dst, dst_size, "%d", persona); + int ret = snprintf(dst, dst_size, "%d/", persona); if (ret < 0 || (size_t) ret != uid_len) { LOGE("Error appending persona id to path"); return -1; @@ -135,6 +135,20 @@ int create_persona_path(char path[PKG_PATH_MAX], return 0; } +int create_move_path(char path[PKG_PATH_MAX], + const char* pkgname, + const char* leaf, + uid_t persona) +{ + if ((android_data_dir.len + strlen(PRIMARY_USER_PREFIX) + strlen(pkgname) + strlen(leaf) + 1) + >= PKG_PATH_MAX) { + return -1; + } + + sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf); + return 0; +} + /** * Checks whether the package name is valid. Returns -1 on error and * 0 on success. diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 81defd690af8..0586d9ed1eeb 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -128,6 +128,19 @@ public interface IBinder { int TWEET_TRANSACTION = ('_'<<24)|('T'<<16)|('W'<<8)|'T'; /** + * IBinder protocol transaction code: tell an app asynchronously that the + * caller likes it. The app is responsible for incrementing and maintaining + * its own like counter, and may display this value to the user to indicate the + * quality of the app. This is an optional command that applications do not + * need to handle, so the default implementation is to do nothing. + * + * <p>There is no response returned and nothing about the + * system will be functionally affected by it, but it will improve the + * app's self-esteem. + */ + int LIKE_TRANSACTION = ('_'<<24)|('L'<<16)|('I'<<8)|'K'; + + /** * Flag to {@link #transact}: this is a one-way call, meaning that the * caller returns immediately, without waiting for a result from the * callee. Applies only if the caller and callee are in different diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 18167b601d32..7ce96c0e31bd 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -18,7 +18,6 @@ package android.service.wallpaper; import com.android.internal.os.HandlerCaller; import com.android.internal.view.BaseIWindow; -import com.android.internal.view.BaseInputHandler; import com.android.internal.view.BaseSurfaceHolder; import android.annotation.SdkConstant; @@ -45,8 +44,8 @@ import android.view.Gravity; import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; -import android.view.InputHandler; -import android.view.InputQueue; +import android.view.InputEvent; +import android.view.InputEventReceiver; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; @@ -228,24 +227,29 @@ public abstract class WallpaperService extends Service { } }; - - final InputHandler mInputHandler = new BaseInputHandler() { + + final class WallpaperInputEventReceiver extends InputEventReceiver { + public WallpaperInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleMotion(MotionEvent event, - InputQueue.FinishedCallback finishedCallback) { + public void onInputEvent(InputEvent event) { boolean handled = false; try { - int source = event.getSource(); - if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { - dispatchPointer(event); + if (event instanceof MotionEvent + && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + MotionEvent dup = MotionEvent.obtainNoHistory((MotionEvent)event); + dispatchPointer(dup); handled = true; } } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } - }; - + } + WallpaperInputEventReceiver mInputEventReceiver; + final BaseIWindow mWindow = new BaseIWindow() { @Override public void resized(int w, int h, Rect coveredInsets, @@ -534,6 +538,8 @@ public abstract class WallpaperService extends Service { } Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event); mCaller.sendMessage(msg); + } else { + event.recycle(); } } @@ -599,8 +605,8 @@ public abstract class WallpaperService extends Service { } mCreated = true; - InputQueue.registerInputChannel(mInputChannel, mInputHandler, - Looper.myQueue()); + mInputEventReceiver = new WallpaperInputEventReceiver( + mInputChannel, Looper.myLooper()); } mSurfaceHolder.mSurfaceLock.lock(); @@ -902,8 +908,9 @@ public abstract class WallpaperService extends Service { if (DEBUG) Log.v(TAG, "Removing window and destroying surface " + mSurfaceHolder.getSurface() + " of: " + this); - if (mInputChannel != null) { - InputQueue.unregisterInputChannel(mInputChannel); + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; } mSession.remove(mWindow); @@ -970,6 +977,8 @@ public abstract class WallpaperService extends Service { public void dispatchPointer(MotionEvent event) { if (mEngine != null) { mEngine.dispatchPointer(event); + } else { + event.recycle(); } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index ccb64895703c..443acf68411c 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -837,10 +837,37 @@ public abstract class HardwareRenderer { (view.mPrivateFlags & View.INVALIDATED) == View.INVALIDATED; view.mPrivateFlags &= ~View.INVALIDATED; + final long getDisplayListStartTime; + if (ViewDebug.DEBUG_LATENCY) { + getDisplayListStartTime = System.nanoTime(); + } + DisplayList displayList = view.getDisplayList(); + + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- getDisplayList() took " + + ((now - getDisplayListStartTime) * 0.000001f) + "ms"); + } + if (displayList != null) { - if (canvas.drawDisplayList(displayList, view.getWidth(), - view.getHeight(), mRedrawClip)) { + final long drawDisplayListStartTime; + if (ViewDebug.DEBUG_LATENCY) { + drawDisplayListStartTime = System.nanoTime(); + } + + boolean invalidateNeeded = canvas.drawDisplayList( + displayList, view.getWidth(), + view.getHeight(), mRedrawClip); + + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- drawDisplayList() took " + + ((now - drawDisplayListStartTime) * 0.000001f) + + "ms, invalidateNeeded=" + invalidateNeeded + "."); + } + + if (invalidateNeeded) { if (mRedrawClip.isEmpty() || view.getParent() == null) { view.invalidate(); } else { @@ -872,7 +899,19 @@ public abstract class HardwareRenderer { attachInfo.mIgnoreDirtyState = false; + final long eglSwapBuffersStartTime; + if (ViewDebug.DEBUG_LATENCY) { + eglSwapBuffersStartTime = System.nanoTime(); + } + sEgl.eglSwapBuffers(sEglDisplay, mEglSurface); + + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- eglSwapBuffers() took " + + ((now - eglSwapBuffersStartTime) * 0.000001f) + "ms"); + } + checkEglErrors(); return dirty == null; diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index 01ddcc9bf1a4..c42bbdcadf04 100755 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -19,6 +19,8 @@ package android.view; import android.os.Parcel; import android.os.Parcelable; +import java.util.concurrent.atomic.AtomicInteger; + /** * Common base class for input events. */ @@ -27,8 +29,21 @@ public abstract class InputEvent implements Parcelable { protected static final int PARCEL_TOKEN_MOTION_EVENT = 1; /** @hide */ protected static final int PARCEL_TOKEN_KEY_EVENT = 2; - + + // Next sequence number. + private static final AtomicInteger mNextSeq = new AtomicInteger(); + + /** @hide */ + protected int mSeq; + + /** @hide */ + protected boolean mRecycled; + + private static final boolean TRACK_RECYCLED_LOCATION = false; + private RuntimeException mRecycledLocation; + /*package*/ InputEvent() { + mSeq = mNextSeq.getAndIncrement(); } /** @@ -82,7 +97,29 @@ public abstract class InputEvent implements Parcelable { * objects are fine. See {@link KeyEvent#recycle()} for details. * @hide */ - public abstract void recycle(); + public void recycle() { + if (TRACK_RECYCLED_LOCATION) { + if (mRecycledLocation != null) { + throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation); + } + mRecycledLocation = new RuntimeException("Last recycled here"); + } else { + if (mRecycled) { + throw new RuntimeException(toString() + " recycled twice!"); + } + mRecycled = true; + } + } + + /** + * Reinitializes the event on reuse (after recycling). + * @hide + */ + protected void prepareForReuse() { + mRecycled = false; + mRecycledLocation = null; + mSeq = mNextSeq.getAndIncrement(); + } /** * Gets a private flag that indicates when the system has detected that this input event @@ -113,6 +150,22 @@ public abstract class InputEvent implements Parcelable { */ public abstract long getEventTimeNano(); + /** + * Gets the unique sequence number of this event. + * Every input event that is created or received by a process has a + * unique sequence number. Moreover, a new sequence number is obtained + * each time an event object is recycled. + * + * Sequence numbers are only guaranteed to be locally unique within a process. + * Sequence numbers are not preserved when events are parceled. + * + * @return The unique sequence number of this event. + * @hide + */ + public int getSequenceNumber() { + return mSeq; + } + public int describeContents() { return 0; } diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java index 9b081b207dff..fafe416defdf 100644 --- a/core/java/android/view/InputEventConsistencyVerifier.java +++ b/core/java/android/view/InputEventConsistencyVerifier.java @@ -58,7 +58,7 @@ public final class InputEventConsistencyVerifier { // so that the verifier can detect when it has been asked to verify the same event twice. // It does not make sense to examine the contents of the last event since it may have // been recycled. - private InputEvent mLastEvent; + private int mLastEventSeq; private String mLastEventType; private int mLastNestingLevel; @@ -140,7 +140,7 @@ public final class InputEventConsistencyVerifier { * Resets the state of the input event consistency verifier. */ public void reset() { - mLastEvent = null; + mLastEventSeq = -1; mLastNestingLevel = 0; mTrackballDown = false; mTrackballUnhandled = false; @@ -573,17 +573,18 @@ public final class InputEventConsistencyVerifier { private boolean startEvent(InputEvent event, int nestingLevel, String eventType) { // Ignore the event if we already checked it at a higher nesting level. - if (event == mLastEvent && nestingLevel < mLastNestingLevel + final int seq = event.getSequenceNumber(); + if (seq == mLastEventSeq && nestingLevel < mLastNestingLevel && eventType == mLastEventType) { return false; } if (nestingLevel > 0) { - mLastEvent = event; + mLastEventSeq = seq; mLastEventType = eventType; mLastNestingLevel = nestingLevel; } else { - mLastEvent = null; + mLastEventSeq = -1; mLastEventType = null; mLastNestingLevel = 0; } diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java new file mode 100644 index 000000000000..abb52810c446 --- /dev/null +++ b/core/java/android/view/InputEventReceiver.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2011 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.view; + +import dalvik.system.CloseGuard; + +import android.os.Looper; +import android.os.MessageQueue; +import android.util.Log; + +/** + * Provides a low-level mechanism for an application to receive input events. + * @hide + */ +public abstract class InputEventReceiver { + private static final String TAG = "InputEventReceiver"; + + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private int mReceiverPtr; + + // We keep references to the input channel and message queue objects here so that + // they are not GC'd while the native peer of the receiver is using them. + private InputChannel mInputChannel; + private MessageQueue mMessageQueue; + + // The sequence number of the event that is in progress. + private int mEventSequenceNumberInProgress = -1; + + private static native int nativeInit(InputEventReceiver receiver, + InputChannel inputChannel, MessageQueue messageQueue); + private static native void nativeDispose(int receiverPtr); + private static native void nativeFinishInputEvent(int receiverPtr, boolean handled); + + /** + * Creates an input event receiver bound to the specified input channel. + * + * @param inputChannel The input channel. + * @param looper The looper to use when invoking callbacks. + */ + public InputEventReceiver(InputChannel inputChannel, Looper looper) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null"); + } + if (looper == null) { + throw new IllegalArgumentException("looper must not be null"); + } + + mInputChannel = inputChannel; + mMessageQueue = looper.getQueue(); + mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue); + + mCloseGuard.open("dispose"); + } + + @Override + protected void finalize() throws Throwable { + try { + dispose(); + } finally { + super.finalize(); + } + } + + /** + * Disposes the receiver. + */ + public void dispose() { + if (mCloseGuard != null) { + mCloseGuard.close(); + } + if (mReceiverPtr != 0) { + nativeDispose(mReceiverPtr); + mReceiverPtr = 0; + } + mInputChannel = null; + mMessageQueue = null; + } + + /** + * Called when an input event is received. + * The recipient should process the input event and then call {@link #finishInputEvent} + * to indicate whether the event was handled. No new input events will be received + * until {@link #finishInputEvent} is called. + * + * @param event The input event that was received. + */ + public void onInputEvent(InputEvent event) { + finishInputEvent(event, false); + } + + /** + * Finishes an input event and indicates whether it was handled. + * + * @param event The input event that was finished. + * @param handled True if the event was handled. + */ + public void finishInputEvent(InputEvent event, boolean handled) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (mReceiverPtr == 0) { + Log.w(TAG, "Attempted to finish an input event but the input event " + + "receiver has already been disposed."); + } else { + if (event.getSequenceNumber() != mEventSequenceNumberInProgress) { + Log.w(TAG, "Attempted to finish an input event that is not in progress."); + } else { + mEventSequenceNumberInProgress = -1; + nativeFinishInputEvent(mReceiverPtr, handled); + } + } + recycleInputEvent(event); + } + + // Called from native code. + @SuppressWarnings("unused") + private void dispatchInputEvent(InputEvent event) { + mEventSequenceNumberInProgress = event.getSequenceNumber(); + onInputEvent(event); + } + + private static void recycleInputEvent(InputEvent event) { + if (event instanceof MotionEvent) { + // Event though key events are also recyclable, we only recycle motion events. + // Historically, key events were not recyclable and applications expect + // them to be immutable. We only ever recycle key events behind the + // scenes where an application never sees them (so, not here). + event.recycle(); + } + } + + public static interface Factory { + public InputEventReceiver createInputEventReceiver( + InputChannel inputChannel, Looper looper); + } +} diff --git a/core/java/android/view/InputHandler.java b/core/java/android/view/InputHandler.java deleted file mode 100644 index 14ce14c4b87a..000000000000 --- a/core/java/android/view/InputHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.view; - -/** - * Handles input messages that arrive on an input channel. - * @hide - */ -public interface InputHandler { - /** - * Handle a key event. - * It is the responsibility of the callee to ensure that the finished callback is - * eventually invoked when the event processing is finished and the input system - * can send the next event. - * @param event The key event data. - * @param finishedCallback The callback to invoke when event processing is finished. - */ - public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback); - - /** - * Handle a motion event. - * It is the responsibility of the callee to ensure that the finished callback is - * eventually invoked when the event processing is finished and the input system - * can send the next event. - * @param event The motion event data. - * @param finishedCallback The callback to invoke when event processing is finished. - */ - public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback); -} diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java index 5735b6397cf0..909a3b2b9d24 100644 --- a/core/java/android/view/InputQueue.java +++ b/core/java/android/view/InputQueue.java @@ -16,18 +16,11 @@ package android.view; -import android.os.MessageQueue; -import android.util.Slog; - /** * An input queue provides a mechanism for an application to receive incoming * input events. Currently only usable from native code. */ public final class InputQueue { - private static final String TAG = "InputQueue"; - - private static final boolean DEBUG = false; - /** * Interface to receive notification of when an InputQueue is associated * and dissociated with a thread. @@ -48,13 +41,6 @@ public final class InputQueue { final InputChannel mChannel; - private static final Object sLock = new Object(); - - private static native void nativeRegisterInputChannel(InputChannel inputChannel, - InputHandler inputHandler, MessageQueue messageQueue); - private static native void nativeUnregisterInputChannel(InputChannel inputChannel); - private static native void nativeFinished(long finishedToken, boolean handled); - /** @hide */ public InputQueue(InputChannel channel) { mChannel = channel; @@ -64,121 +50,4 @@ public final class InputQueue { public InputChannel getInputChannel() { return mChannel; } - - /** - * Registers an input channel and handler. - * @param inputChannel The input channel to register. - * @param inputHandler The input handler to input events send to the target. - * @param messageQueue The message queue on whose thread the handler should be invoked. - * @hide - */ - public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler, - MessageQueue messageQueue) { - if (inputChannel == null) { - throw new IllegalArgumentException("inputChannel must not be null"); - } - if (inputHandler == null) { - throw new IllegalArgumentException("inputHandler must not be null"); - } - if (messageQueue == null) { - throw new IllegalArgumentException("messageQueue must not be null"); - } - - synchronized (sLock) { - if (DEBUG) { - Slog.d(TAG, "Registering input channel '" + inputChannel + "'"); - } - - nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue); - } - } - - /** - * Unregisters an input channel. - * Does nothing if the channel is not currently registered. - * @param inputChannel The input channel to unregister. - * @hide - */ - public static void unregisterInputChannel(InputChannel inputChannel) { - if (inputChannel == null) { - throw new IllegalArgumentException("inputChannel must not be null"); - } - - synchronized (sLock) { - if (DEBUG) { - Slog.d(TAG, "Unregistering input channel '" + inputChannel + "'"); - } - - nativeUnregisterInputChannel(inputChannel); - } - } - - @SuppressWarnings("unused") - private static void dispatchKeyEvent(InputHandler inputHandler, - KeyEvent event, long finishedToken) { - FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken); - inputHandler.handleKey(event, finishedCallback); - } - - @SuppressWarnings("unused") - private static void dispatchMotionEvent(InputHandler inputHandler, - MotionEvent event, long finishedToken) { - FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken); - inputHandler.handleMotion(event, finishedCallback); - } - - /** - * A callback that must be invoked to when finished processing an event. - * @hide - */ - public static final class FinishedCallback { - private static final boolean DEBUG_RECYCLING = false; - - private static final int RECYCLE_MAX_COUNT = 4; - - private static FinishedCallback sRecycleHead; - private static int sRecycleCount; - - private FinishedCallback mRecycleNext; - private long mFinishedToken; - - private FinishedCallback() { - } - - public static FinishedCallback obtain(long finishedToken) { - synchronized (sLock) { - FinishedCallback callback = sRecycleHead; - if (callback != null) { - sRecycleHead = callback.mRecycleNext; - sRecycleCount -= 1; - callback.mRecycleNext = null; - } else { - callback = new FinishedCallback(); - } - callback.mFinishedToken = finishedToken; - return callback; - } - } - - public void finished(boolean handled) { - synchronized (sLock) { - if (mFinishedToken == -1) { - throw new IllegalStateException("Event finished callback already invoked."); - } - - nativeFinished(mFinishedToken, handled); - mFinishedToken = -1; - - if (sRecycleCount < RECYCLE_MAX_COUNT) { - mRecycleNext = sRecycleHead; - sRecycleHead = this; - sRecycleCount += 1; - - if (DEBUG_RECYCLING) { - Slog.d(TAG, "Recycled finished callbacks: " + sRecycleCount); - } - } - } - } - } } diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index f53e42cb4603..9a46aab76922 100755 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -1225,7 +1225,6 @@ public class KeyEvent extends InputEvent implements Parcelable { private static KeyEvent gRecyclerTop; private KeyEvent mNext; - private boolean mRecycled; private int mDeviceId; private int mSource; @@ -1535,8 +1534,8 @@ public class KeyEvent extends InputEvent implements Parcelable { gRecyclerTop = ev.mNext; gRecyclerUsed -= 1; } - ev.mRecycled = false; ev.mNext = null; + ev.prepareForReuse(); return ev; } @@ -1598,10 +1597,7 @@ public class KeyEvent extends InputEvent implements Parcelable { * @hide */ public final void recycle() { - if (mRecycled) { - throw new RuntimeException(toString() + " recycled twice!"); - } - mRecycled = true; + super.recycle(); mCharacters = null; synchronized (gRecyclerLock) { diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 8e0ab1a12502..e49193e2c69c 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -167,7 +167,6 @@ import android.util.SparseArray; */ public final class MotionEvent extends InputEvent implements Parcelable { private static final long NS_PER_MS = 1000000; - private static final boolean TRACK_RECYCLED_LOCATION = false; /** * An invalid pointer id. @@ -1315,8 +1314,6 @@ public final class MotionEvent extends InputEvent implements Parcelable { private int mNativePtr; private MotionEvent mNext; - private RuntimeException mRecycledLocation; - private boolean mRecycled; private static native int nativeInitialize(int nativePtr, int deviceId, int source, int action, int flags, int edgeFlags, @@ -1397,9 +1394,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { gRecyclerTop = ev.mNext; gRecyclerUsed -= 1; } - ev.mRecycledLocation = null; - ev.mRecycled = false; ev.mNext = null; + ev.prepareForReuse(); return ev; } @@ -1647,19 +1643,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * this function you must not ever touch the event again. */ public final void recycle() { - // Ensure recycle is only called once! - if (TRACK_RECYCLED_LOCATION) { - if (mRecycledLocation != null) { - throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation); - } - mRecycledLocation = new RuntimeException("Last recycled here"); - //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation); - } else { - if (mRecycled) { - throw new RuntimeException(toString() + " recycled twice!"); - } - mRecycled = true; - } + super.recycle(); synchronized (gRecyclerLock) { if (gRecyclerUsed < MAX_RECYCLED) { diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 65e72c9c8123..c1db5720e2f1 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -127,16 +127,19 @@ public class ViewDebug { * Logs the relative difference between the time an event was created and the time it * was delivered. * - * Logs the time spent waiting for Surface.lockCanvas() or eglSwapBuffers(). - * This is time that the event loop spends blocked and unresponsive. Ideally, drawing - * and animations should be perfectly synchronized with VSYNC so that swap buffers - * is instantaneous. + * Logs the time spent waiting for Surface.lockCanvas(), Surface.unlockCanvasAndPost() + * or eglSwapBuffers(). This is time that the event loop spends blocked and unresponsive. + * Ideally, drawing and animations should be perfectly synchronized with VSYNC so that + * dequeuing and queueing buffers is instantaneous. * - * Logs the time spent in ViewRoot.performTraversals() or ViewRoot.draw(). + * Logs the time spent in ViewRoot.performTraversals() and ViewRoot.performDraw(). * @hide */ public static final boolean DEBUG_LATENCY = false; + /** @hide */ + public static final String DEBUG_LATENCY_TAG = "ViewLatency"; + /** * <p>Enables or disables views consistency check. Even when this property is enabled, * view consistency checks happen only if {@link false} is set diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 5060f83e7a99..f23c3127e3a9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -154,9 +154,6 @@ public final class ViewRootImpl extends Handler implements ViewParent, final TypedValue mTmpValue = new TypedValue(); final InputMethodCallback mInputMethodCallback; - final SparseArray<Object> mPendingEvents = new SparseArray<Object>(); - int mPendingEventSeq = 0; - final Thread mThread; final WindowLeaked mLocation; @@ -219,7 +216,16 @@ public final class ViewRootImpl extends Handler implements ViewParent, boolean mNewSurfaceNeeded; boolean mHasHadWindowFocus; boolean mLastWasImTarget; - InputEventMessage mPendingInputEvents = null; + + // Pool of queued input events. + private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10; + private QueuedInputEvent mQueuedInputEventPool; + private int mQueuedInputEventPoolSize; + + // Input event queue. + QueuedInputEvent mFirstPendingInputEvent; + QueuedInputEvent mCurrentInputEvent; + boolean mProcessInputEventsPending; boolean mWindowAttributesChanged = false; int mWindowAttributesChangesFlag = 0; @@ -552,8 +558,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, mInputQueue = new InputQueue(mInputChannel); mInputQueueCallback.onInputQueueCreated(mInputQueue); } else { - InputQueue.registerInputChannel(mInputChannel, mInputHandler, - Looper.myQueue()); + mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, + Looper.myLooper()); } } @@ -841,23 +847,11 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - private void processInputEvents(boolean outOfOrder) { - while (mPendingInputEvents != null) { - handleMessage(mPendingInputEvents.mMessage); - InputEventMessage tmpMessage = mPendingInputEvents; - mPendingInputEvents = mPendingInputEvents.mNext; - tmpMessage.recycle(); - if (outOfOrder) { - removeMessages(PROCESS_INPUT_EVENTS); - } - } - } - private void performTraversals() { // cache mView since it is used so much below... final View host = mView; - processInputEvents(true); + processInputEvents(); if (DBG) { System.out.println("======================================"); @@ -2024,7 +2018,19 @@ public final class ViewRootImpl extends Handler implements ViewParent, canvas.setScreenDensity(scalingRequired ? DisplayMetrics.DENSITY_DEVICE : 0); mAttachInfo.mSetIgnoreDirtyState = false; + + final long drawStartTime; + if (ViewDebug.DEBUG_LATENCY) { + drawStartTime = System.nanoTime(); + } + mView.draw(canvas); + + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took " + + ((now - drawStartTime) * 0.000001f) + "ms"); + } } finally { if (!mAttachInfo.mSetIgnoreDirtyState) { // Only clear the flag if it was not set during the mView.draw() call @@ -2040,14 +2046,24 @@ public final class ViewRootImpl extends Handler implements ViewParent, EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); } } - } finally { + final long unlockCanvasAndPostStartTime; + if (ViewDebug.DEBUG_LATENCY) { + unlockCanvasAndPostStartTime = System.nanoTime(); + } + surface.unlockCanvasAndPost(canvas); - } - } - if (LOCAL_LOGV) { - Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took " + + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms"); + } + + if (LOCAL_LOGV) { + Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); + } + } } if (animating) { @@ -2266,8 +2282,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, mInputQueueCallback.onInputQueueDestroyed(mInputQueue); mInputQueueCallback = null; mInputQueue = null; - } else if (mInputChannel != null) { - InputQueue.unregisterInputChannel(mInputChannel); + } else if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; } try { sWindowSession.remove(mWindow); @@ -2344,7 +2361,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, public final static int DISPATCH_TRACKBALL = 1007; public final static int DISPATCH_APP_VISIBILITY = 1008; public final static int DISPATCH_GET_NEW_SURFACE = 1009; - public final static int FINISHED_EVENT = 1010; + public final static int IME_FINISHED_EVENT = 1010; public final static int DISPATCH_KEY_FROM_IME = 1011; public final static int FINISH_INPUT_CONNECTION = 1012; public final static int CHECK_FOCUS = 1013; @@ -2358,7 +2375,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1021; public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1022; public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 1023; - public final static int PROCESS_INPUT_EVENTS = 1024; + public final static int DO_PROCESS_INPUT_EVENTS = 1024; @Override public String getMessageName(Message message) { @@ -2383,8 +2400,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, return "DISPATCH_APP_VISIBILITY"; case DISPATCH_GET_NEW_SURFACE: return "DISPATCH_GET_NEW_SURFACE"; - case FINISHED_EVENT: - return "FINISHED_EVENT"; + case IME_FINISHED_EVENT: + return "IME_FINISHED_EVENT"; case DISPATCH_KEY_FROM_IME: return "DISPATCH_KEY_FROM_IME"; case FINISH_INPUT_CONNECTION: @@ -2411,8 +2428,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID"; case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT"; - case PROCESS_INPUT_EVENTS: - return "PROCESS_INPUT_EVENTS"; + case DO_PROCESS_INPUT_EVENTS: + return "DO_PROCESS_INPUT_EVENTS"; } return super.getMessageName(message); } @@ -2437,17 +2454,22 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (ViewDebug.DEBUG_LATENCY) { traversalStartTime = System.nanoTime(); mLastDrawDurationNanos = 0; + if (mLastTraversalFinishedTimeNanos != 0) { + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals(); it has been " + + ((traversalStartTime - mLastTraversalFinishedTimeNanos) * 0.000001f) + + "ms since the last traversals finished."); + } else { + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals()."); + } } performTraversals(); if (ViewDebug.DEBUG_LATENCY) { long now = System.nanoTime(); - Log.d(TAG, "Latency: Spent " + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "performTraversals() took " + ((now - traversalStartTime) * 0.000001f) - + "ms in performTraversals(), with " - + (mLastDrawDurationNanos * 0.000001f) - + "ms of that time in draw()"); + + "ms."); mLastTraversalFinishedTimeNanos = now; } @@ -2456,23 +2478,12 @@ public final class ViewRootImpl extends Handler implements ViewParent, mProfile = false; } break; - case FINISHED_EVENT: - handleFinishedEvent(msg.arg1, msg.arg2 != 0); - break; - case DISPATCH_KEY: - deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0); - break; - case DISPATCH_POINTER: - deliverPointerEvent((MotionEvent) msg.obj, msg.arg1 != 0); + case IME_FINISHED_EVENT: + handleImeFinishedEvent(msg.arg1, msg.arg2 != 0); break; - case DISPATCH_TRACKBALL: - deliverTrackballEvent((MotionEvent) msg.obj, msg.arg1 != 0); - break; - case DISPATCH_GENERIC_MOTION: - deliverGenericMotionEvent((MotionEvent) msg.obj, msg.arg1 != 0); - break; - case PROCESS_INPUT_EVENTS: - processInputEvents(false); + case DO_PROCESS_INPUT_EVENTS: + mProcessInputEventsPending = false; + processInputEvents(); break; case DISPATCH_APP_VISIBILITY: handleAppVisibility(msg.arg1 != 0); @@ -2594,7 +2605,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, //noinspection UnusedAssignment event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM); } - deliverKeyEventPostIme((KeyEvent)msg.obj, false); + enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME); } break; case FINISH_INPUT_CONNECTION: { InputMethodManager imm = InputMethodManager.peekInstance(); @@ -2656,70 +2667,6 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - private void startInputEvent(InputQueue.FinishedCallback finishedCallback) { - if (mFinishedCallback != null) { - Slog.w(TAG, "Received a new input event from the input queue but there is " - + "already an unfinished input event in progress."); - } - - if (ViewDebug.DEBUG_LATENCY) { - mInputEventReceiveTimeNanos = System.nanoTime(); - mInputEventDeliverTimeNanos = 0; - mInputEventDeliverPostImeTimeNanos = 0; - } - - mFinishedCallback = finishedCallback; - } - - private void finishInputEvent(InputEvent event, boolean handled) { - if (LOCAL_LOGV) Log.v(TAG, "Telling window manager input event is finished"); - - if (mFinishedCallback == null) { - Slog.w(TAG, "Attempted to tell the input queue that the current input event " - + "is finished but there is no input event actually in progress."); - return; - } - - if (ViewDebug.DEBUG_LATENCY) { - final long now = System.nanoTime(); - final long eventTime = event.getEventTimeNano(); - final StringBuilder msg = new StringBuilder(); - msg.append("Latency: Spent "); - msg.append((now - mInputEventReceiveTimeNanos) * 0.000001f); - msg.append("ms processing "); - if (event instanceof KeyEvent) { - final KeyEvent keyEvent = (KeyEvent)event; - msg.append("key event, action="); - msg.append(KeyEvent.actionToString(keyEvent.getAction())); - } else { - final MotionEvent motionEvent = (MotionEvent)event; - msg.append("motion event, action="); - msg.append(MotionEvent.actionToString(motionEvent.getAction())); - msg.append(", historySize="); - msg.append(motionEvent.getHistorySize()); - } - msg.append(", handled="); - msg.append(handled); - msg.append(", received at +"); - msg.append((mInputEventReceiveTimeNanos - eventTime) * 0.000001f); - if (mInputEventDeliverTimeNanos != 0) { - msg.append("ms, delivered at +"); - msg.append((mInputEventDeliverTimeNanos - eventTime) * 0.000001f); - } - if (mInputEventDeliverPostImeTimeNanos != 0) { - msg.append("ms, delivered post IME at +"); - msg.append((mInputEventDeliverPostImeTimeNanos - eventTime) * 0.000001f); - } - msg.append("ms, finished at +"); - msg.append((now - eventTime) * 0.000001f); - msg.append("ms."); - Log.d(TAG, msg.toString()); - } - - mFinishedCallback.finished(handled); - mFinishedCallback = null; - } - /** * Something in the current window tells us we need to change the touch mode. For * example, we are not in touch mode, and the user touches the screen. @@ -2841,11 +2788,27 @@ public final class ViewRootImpl extends Handler implements ViewParent, return false; } - private void deliverPointerEvent(MotionEvent event, boolean sendDone) { + private void deliverInputEvent(QueuedInputEvent q) { if (ViewDebug.DEBUG_LATENCY) { - mInputEventDeliverTimeNanos = System.nanoTime(); + q.mDeliverTimeNanos = System.nanoTime(); } + if (q.mEvent instanceof KeyEvent) { + deliverKeyEvent(q); + } else { + final int source = q.mEvent.getSource(); + if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { + deliverPointerEvent(q); + } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { + deliverTrackballEvent(q); + } else { + deliverGenericMotionEvent(q); + } + } + } + + private void deliverPointerEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; final boolean isTouchEvent = event.isTouchEvent(); if (mInputEventConsistencyVerifier != null) { if (isTouchEvent) { @@ -2857,7 +2820,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { - finishMotionEvent(event, sendDone, false); + finishInputEvent(q, false); return; } @@ -2892,41 +2855,23 @@ public final class ViewRootImpl extends Handler implements ViewParent, lt.sample("B Dispatched PointerEvents ", System.nanoTime() - event.getEventTimeNano()); } if (handled) { - finishMotionEvent(event, sendDone, true); + finishInputEvent(q, true); return; } // Pointer event was unhandled. - finishMotionEvent(event, sendDone, false); - } - - private void finishMotionEvent(MotionEvent event, boolean sendDone, boolean handled) { - event.recycle(); - if (sendDone) { - finishInputEvent(event, handled); - } - //noinspection ConstantConditions - if (LOCAL_LOGV || WATCH_POINTER) { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { - Log.i(TAG, "Done dispatching!"); - } - } + finishInputEvent(q, false); } - private void deliverTrackballEvent(MotionEvent event, boolean sendDone) { - if (ViewDebug.DEBUG_LATENCY) { - mInputEventDeliverTimeNanos = System.nanoTime(); - } - - if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event); - + private void deliverTrackballEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTrackballEvent(event, 0); } // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { - finishMotionEvent(event, sendDone, false); + finishInputEvent(q, false); return; } @@ -2938,7 +2883,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, // touch mode here. ensureTouchMode(false); - finishMotionEvent(event, sendDone, true); + finishInputEvent(q, true); mLastTrackballTime = Integer.MIN_VALUE; return; } @@ -2962,18 +2907,18 @@ public final class ViewRootImpl extends Handler implements ViewParent, case MotionEvent.ACTION_DOWN: x.reset(2); y.reset(2); - deliverKeyEvent(new KeyEvent(curTime, curTime, + dispatchKey(new KeyEvent(curTime, curTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD), false); + InputDevice.SOURCE_KEYBOARD)); break; case MotionEvent.ACTION_UP: x.reset(2); y.reset(2); - deliverKeyEvent(new KeyEvent(curTime, curTime, + dispatchKey(new KeyEvent(curTime, curTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD), false); + InputDevice.SOURCE_KEYBOARD)); break; } @@ -3024,38 +2969,35 @@ public final class ViewRootImpl extends Handler implements ViewParent, + keycode); movement--; int repeatCount = accelMovement - movement; - deliverKeyEvent(new KeyEvent(curTime, curTime, + dispatchKey(new KeyEvent(curTime, curTime, KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD), false); + InputDevice.SOURCE_KEYBOARD)); } while (movement > 0) { if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: " + keycode); movement--; curTime = SystemClock.uptimeMillis(); - deliverKeyEvent(new KeyEvent(curTime, curTime, + dispatchKey(new KeyEvent(curTime, curTime, KeyEvent.ACTION_DOWN, keycode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD), false); - deliverKeyEvent(new KeyEvent(curTime, curTime, + InputDevice.SOURCE_KEYBOARD)); + dispatchKey(new KeyEvent(curTime, curTime, KeyEvent.ACTION_UP, keycode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD), false); - } + InputDevice.SOURCE_KEYBOARD)); + } mLastTrackballTime = curTime; } // Unfortunately we can't tell whether the application consumed the keys, so // we always consider the trackball event handled. - finishMotionEvent(event, sendDone, true); + finishInputEvent(q, true); } - private void deliverGenericMotionEvent(MotionEvent event, boolean sendDone) { - if (ViewDebug.DEBUG_LATENCY) { - mInputEventDeliverTimeNanos = System.nanoTime(); - } - + private void deliverGenericMotionEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0); } @@ -3068,7 +3010,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (isJoystick) { updateJoystickDirection(event, false); } - finishMotionEvent(event, sendDone, false); + finishInputEvent(q, false); return; } @@ -3077,16 +3019,16 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (isJoystick) { updateJoystickDirection(event, false); } - finishMotionEvent(event, sendDone, true); + finishInputEvent(q, true); return; } if (isJoystick) { // Translate the joystick event into DPAD keys and try to deliver those. updateJoystickDirection(event, true); - finishMotionEvent(event, sendDone, true); + finishInputEvent(q, true); } else { - finishMotionEvent(event, sendDone, false); + finishInputEvent(q, false); } } @@ -3108,9 +3050,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (xDirection != mLastJoystickXDirection) { if (mLastJoystickXKeyCode != 0) { - deliverKeyEvent(new KeyEvent(time, time, + dispatchKey(new KeyEvent(time, time, KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState, - deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false); + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); mLastJoystickXKeyCode = 0; } @@ -3119,17 +3061,17 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (xDirection != 0 && synthesizeNewKeys) { mLastJoystickXKeyCode = xDirection > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT; - deliverKeyEvent(new KeyEvent(time, time, + dispatchKey(new KeyEvent(time, time, KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState, - deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false); + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); } } if (yDirection != mLastJoystickYDirection) { if (mLastJoystickYKeyCode != 0) { - deliverKeyEvent(new KeyEvent(time, time, + dispatchKey(new KeyEvent(time, time, KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState, - deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false); + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); mLastJoystickYKeyCode = 0; } @@ -3138,9 +3080,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (yDirection != 0 && synthesizeNewKeys) { mLastJoystickYKeyCode = yDirection > 0 ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP; - deliverKeyEvent(new KeyEvent(time, time, + dispatchKey(new KeyEvent(time, time, KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState, - deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false); + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); } } } @@ -3231,91 +3173,81 @@ public final class ViewRootImpl extends Handler implements ViewParent, return false; } - int enqueuePendingEvent(Object event, boolean sendDone) { - int seq = mPendingEventSeq+1; - if (seq < 0) seq = 0; - mPendingEventSeq = seq; - mPendingEvents.put(seq, event); - return sendDone ? seq : -seq; - } - - Object retrievePendingEvent(int seq) { - if (seq < 0) seq = -seq; - Object event = mPendingEvents.get(seq); - if (event != null) { - mPendingEvents.remove(seq); - } - return event; - } - - private void deliverKeyEvent(KeyEvent event, boolean sendDone) { - if (ViewDebug.DEBUG_LATENCY) { - mInputEventDeliverTimeNanos = System.nanoTime(); - } - + private void deliverKeyEvent(QueuedInputEvent q) { + final KeyEvent event = (KeyEvent)q.mEvent; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 0); } - // If there is no view, then the event will not be handled. - if (mView == null || !mAdded) { - finishKeyEvent(event, sendDone, false); - return; - } - - if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView); + if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) { + // If there is no view, then the event will not be handled. + if (mView == null || !mAdded) { + finishInputEvent(q, false); + return; + } - // Perform predispatching before the IME. - if (mView.dispatchKeyEventPreIme(event)) { - finishKeyEvent(event, sendDone, true); - return; - } + if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView); - // Dispatch to the IME before propagating down the view hierarchy. - // The IME will eventually call back into handleFinishedEvent. - if (mLastWasImTarget) { - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - int seq = enqueuePendingEvent(event, sendDone); - if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq=" - + seq + " event=" + event); - imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback); + // Perform predispatching before the IME. + if (mView.dispatchKeyEventPreIme(event)) { + finishInputEvent(q, true); return; } + + // Dispatch to the IME before propagating down the view hierarchy. + // The IME will eventually call back into handleImeFinishedEvent. + if (mLastWasImTarget) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + final int seq = event.getSequenceNumber(); + if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq=" + + seq + " event=" + event); + imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback); + return; + } + } } // Not dispatching to IME, continue with post IME actions. - deliverKeyEventPostIme(event, sendDone); + deliverKeyEventPostIme(q); } - private void handleFinishedEvent(int seq, boolean handled) { - final KeyEvent event = (KeyEvent)retrievePendingEvent(seq); - if (DEBUG_IMF) Log.v(TAG, "IME finished event: seq=" + seq - + " handled=" + handled + " event=" + event); - if (event != null) { - final boolean sendDone = seq >= 0; + void handleImeFinishedEvent(int seq, boolean handled) { + final QueuedInputEvent q = mCurrentInputEvent; + if (q != null && q.mEvent.getSequenceNumber() == seq) { + final KeyEvent event = (KeyEvent)q.mEvent; + if (DEBUG_IMF) { + Log.v(TAG, "IME finished event: seq=" + seq + + " handled=" + handled + " event=" + event); + } if (handled) { - finishKeyEvent(event, sendDone, true); + finishInputEvent(q, true); } else { - deliverKeyEventPostIme(event, sendDone); + deliverKeyEventPostIme(q); + } + } else { + if (DEBUG_IMF) { + Log.v(TAG, "IME finished event: seq=" + seq + + " handled=" + handled + ", event not found!"); } } } - private void deliverKeyEventPostIme(KeyEvent event, boolean sendDone) { + private void deliverKeyEventPostIme(QueuedInputEvent q) { + final KeyEvent event = (KeyEvent)q.mEvent; if (ViewDebug.DEBUG_LATENCY) { - mInputEventDeliverPostImeTimeNanos = System.nanoTime(); + q.mDeliverPostImeTimeNanos = System.nanoTime(); } // If the view went away, then the event will not be handled. if (mView == null || !mAdded) { - finishKeyEvent(event, sendDone, false); + finishInputEvent(q, false); return; } // If the key's purpose is to exit touch mode then we consume it and consider it handled. if (checkForLeavingTouchModeAndConsume(event)) { - finishKeyEvent(event, sendDone, true); + finishInputEvent(q, true); return; } @@ -3325,7 +3257,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { - finishKeyEvent(event, sendDone, true); + finishInputEvent(q, true); return; } @@ -3334,14 +3266,14 @@ public final class ViewRootImpl extends Handler implements ViewParent, && event.isCtrlPressed() && !KeyEvent.isModifierKey(event.getKeyCode())) { if (mView.dispatchKeyShortcutEvent(event)) { - finishKeyEvent(event, sendDone, true); + finishInputEvent(q, true); return; } } // Apply the fallback event policy. if (mFallbackEventHandler.dispatchKeyEvent(event)) { - finishKeyEvent(event, sendDone, true); + finishInputEvent(q, true); return; } @@ -3396,14 +3328,14 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (v.requestFocus(direction, mTempRect)) { playSoundEffect( SoundEffectConstants.getContantForFocusDirection(direction)); - finishKeyEvent(event, sendDone, true); + finishInputEvent(q, true); return; } } // Give the focused view a last chance to handle the dpad key. if (mView.dispatchUnhandledMove(focused, direction)) { - finishKeyEvent(event, sendDone, true); + finishInputEvent(q, true); return; } } @@ -3411,13 +3343,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, } // Key was unhandled. - finishKeyEvent(event, sendDone, false); - } - - private void finishKeyEvent(KeyEvent event, boolean sendDone, boolean handled) { - if (sendDone) { - finishInputEvent(event, handled); - } + finishInputEvent(q, false); } /* drag/drop */ @@ -3742,8 +3668,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - public void dispatchFinishedEvent(int seq, boolean handled) { - Message msg = obtainMessage(FINISHED_EVENT); + void dispatchImeFinishedEvent(int seq, boolean handled) { + Message msg = obtainMessage(IME_FINISHED_EVENT); msg.arg1 = seq; msg.arg2 = handled ? 1 : 0; sendMessage(msg); @@ -3772,152 +3698,186 @@ public final class ViewRootImpl extends Handler implements ViewParent, sendMessage(msg); } - private long mInputEventReceiveTimeNanos; - private long mInputEventDeliverTimeNanos; - private long mInputEventDeliverPostImeTimeNanos; - private InputQueue.FinishedCallback mFinishedCallback; - - private final InputHandler mInputHandler = new InputHandler() { - public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) { - startInputEvent(finishedCallback); - dispatchKey(event, true); - } - - public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { - startInputEvent(finishedCallback); - dispatchMotion(event, true); - } - }; - /** - * Utility class used to queue up input events which are then handled during - * performTraversals(). Doing it this way allows us to ensure that we are up to date with - * all input events just prior to drawing, instead of placing those events on the regular - * handler queue, potentially behind a drawing event. + * Represents a pending input event that is waiting in a queue. + * + * Input events are processed in serial order by the timestamp specified by + * {@link InputEvent#getEventTime()}. In general, the input dispatcher delivers + * one input event to the application at a time and waits for the application + * to finish handling it before delivering the next one. + * + * However, because the application or IME can synthesize and inject multiple + * key events at a time without going through the input dispatcher, we end up + * needing a queue on the application's side. */ - static class InputEventMessage { - Message mMessage; - InputEventMessage mNext; + private static final class QueuedInputEvent { + public static final int FLAG_DELIVER_POST_IME = 1 << 0; - private static final Object sPoolSync = new Object(); - private static InputEventMessage sPool; - private static int sPoolSize = 0; + public QueuedInputEvent mNext; - private static final int MAX_POOL_SIZE = 10; + public InputEvent mEvent; + public InputEventReceiver mReceiver; + public int mFlags; - private InputEventMessage(Message m) { - mMessage = m; - mNext = null; - } + // Used for latency calculations. + public long mReceiveTimeNanos; + public long mDeliverTimeNanos; + public long mDeliverPostImeTimeNanos; + } - /** - * Return a new Message instance from the global pool. Allows us to - * avoid allocating new objects in many cases. - */ - public static InputEventMessage obtain(Message msg) { - synchronized (sPoolSync) { - if (sPool != null) { - InputEventMessage m = sPool; - sPool = m.mNext; - m.mNext = null; - sPoolSize--; - m.mMessage = msg; - return m; - } - } - return new InputEventMessage(msg); + private QueuedInputEvent obtainQueuedInputEvent(InputEvent event, + InputEventReceiver receiver, int flags) { + QueuedInputEvent q = mQueuedInputEventPool; + if (q != null) { + mQueuedInputEventPoolSize -= 1; + mQueuedInputEventPool = q.mNext; + q.mNext = null; + } else { + q = new QueuedInputEvent(); } - /** - * Return the message to the pool. - */ - public void recycle() { - mMessage.recycle(); - synchronized (sPoolSync) { - if (sPoolSize < MAX_POOL_SIZE) { - mNext = sPool; - sPool = this; - sPoolSize++; - } - } + q.mEvent = event; + q.mReceiver = receiver; + q.mFlags = flags; + return q; + } + + private void recycleQueuedInputEvent(QueuedInputEvent q) { + q.mEvent = null; + q.mReceiver = null; + if (mQueuedInputEventPoolSize < MAX_QUEUED_INPUT_EVENT_POOL_SIZE) { + mQueuedInputEventPoolSize += 1; + q.mNext = mQueuedInputEventPool; + mQueuedInputEventPool = q; } } - /** - * Place the input event message at the end of the current pending list - */ - private void enqueueInputEvent(Message msg, long when) { - InputEventMessage inputMessage = InputEventMessage.obtain(msg); - if (mPendingInputEvents == null) { - mPendingInputEvents = inputMessage; + void enqueueInputEvent(InputEvent event, + InputEventReceiver receiver, int flags) { + QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); + + if (ViewDebug.DEBUG_LATENCY) { + q.mReceiveTimeNanos = System.nanoTime(); + q.mDeliverTimeNanos = 0; + q.mDeliverPostImeTimeNanos = 0; + } + + // Always enqueue the input event in order, regardless of its time stamp. + // We do this because the application or the IME may inject key events + // in response to touch events and we want to ensure that the injected keys + // are processed in the order they were received and we cannot trust that + // the time stamp of injected events are monotonic. + QueuedInputEvent last = mFirstPendingInputEvent; + if (last == null) { + mFirstPendingInputEvent = q; } else { - InputEventMessage currMessage = mPendingInputEvents; - while (currMessage.mNext != null) { - currMessage = currMessage.mNext; + while (last.mNext != null) { + last = last.mNext; } - currMessage.mNext = inputMessage; + last.mNext = q; } - sendEmptyMessageAtTime(PROCESS_INPUT_EVENTS, when); + + scheduleProcessInputEvents(); } - public void dispatchKey(KeyEvent event) { - dispatchKey(event, false); + private void scheduleProcessInputEvents() { + if (!mProcessInputEventsPending) { + mProcessInputEventsPending = true; + sendEmptyMessage(DO_PROCESS_INPUT_EVENTS); + } } - private void dispatchKey(KeyEvent event, boolean sendDone) { - //noinspection ConstantConditions - if (false && event.getAction() == KeyEvent.ACTION_DOWN) { - if (event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) { - if (DBG) Log.d("keydisp", "==================================================="); - if (DBG) Log.d("keydisp", "Focused view Hierarchy is:"); + void processInputEvents() { + while (mCurrentInputEvent == null && mFirstPendingInputEvent != null) { + QueuedInputEvent q = mFirstPendingInputEvent; + mFirstPendingInputEvent = q.mNext; + q.mNext = null; + mCurrentInputEvent = q; + deliverInputEvent(q); + } - debug(); + // We are done processing all input events that we can process right now + // so we can clear the pending flag immediately. + if (mProcessInputEventsPending) { + mProcessInputEventsPending = false; + removeMessages(DO_PROCESS_INPUT_EVENTS); + } + } + + private void finishInputEvent(QueuedInputEvent q, boolean handled) { + if (q != mCurrentInputEvent) { + throw new IllegalStateException("finished input event out of order"); + } - if (DBG) Log.d("keydisp", "==================================================="); + if (ViewDebug.DEBUG_LATENCY) { + final long now = System.nanoTime(); + final long eventTime = q.mEvent.getEventTimeNano(); + final StringBuilder msg = new StringBuilder(); + msg.append("Spent "); + msg.append((now - q.mReceiveTimeNanos) * 0.000001f); + msg.append("ms processing "); + if (q.mEvent instanceof KeyEvent) { + final KeyEvent keyEvent = (KeyEvent)q.mEvent; + msg.append("key event, action="); + msg.append(KeyEvent.actionToString(keyEvent.getAction())); + } else { + final MotionEvent motionEvent = (MotionEvent)q.mEvent; + msg.append("motion event, action="); + msg.append(MotionEvent.actionToString(motionEvent.getAction())); + msg.append(", historySize="); + msg.append(motionEvent.getHistorySize()); } + msg.append(", handled="); + msg.append(handled); + msg.append(", received at +"); + msg.append((q.mReceiveTimeNanos - eventTime) * 0.000001f); + if (q.mDeliverTimeNanos != 0) { + msg.append("ms, delivered at +"); + msg.append((q.mDeliverTimeNanos - eventTime) * 0.000001f); + } + if (q.mDeliverPostImeTimeNanos != 0) { + msg.append("ms, delivered post IME at +"); + msg.append((q.mDeliverPostImeTimeNanos - eventTime) * 0.000001f); + } + msg.append("ms, finished at +"); + msg.append((now - eventTime) * 0.000001f); + msg.append("ms."); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, msg.toString()); } - Message msg = obtainMessage(DISPATCH_KEY); - msg.obj = event; - msg.arg1 = sendDone ? 1 : 0; + if (q.mReceiver != null) { + q.mReceiver.finishInputEvent(q.mEvent, handled); + } else if (q.mEvent instanceof MotionEvent) { + // Event though key events are also recyclable, we only recycle motion events. + // Historically, key events were not recyclable and applications expect + // them to be immutable. We only ever recycle key events behind the + // scenes where an application never sees them (so, not here). + q.mEvent.recycle(); + } - if (LOCAL_LOGV) Log.v( - TAG, "sending key " + event + " to " + mView); + recycleQueuedInputEvent(q); - enqueueInputEvent(msg, event.getEventTime()); - } - - private void dispatchMotion(MotionEvent event, boolean sendDone) { - int source = event.getSource(); - if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { - dispatchPointer(event, sendDone); - } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { - dispatchTrackball(event, sendDone); - } else { - dispatchGenericMotion(event, sendDone); + mCurrentInputEvent = null; + if (mFirstPendingInputEvent != null) { + scheduleProcessInputEvents(); } } - private void dispatchPointer(MotionEvent event, boolean sendDone) { - Message msg = obtainMessage(DISPATCH_POINTER); - msg.obj = event; - msg.arg1 = sendDone ? 1 : 0; - enqueueInputEvent(msg, event.getEventTime()); - } + final class WindowInputEventReceiver extends InputEventReceiver { + public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } - private void dispatchTrackball(MotionEvent event, boolean sendDone) { - Message msg = obtainMessage(DISPATCH_TRACKBALL); - msg.obj = event; - msg.arg1 = sendDone ? 1 : 0; - enqueueInputEvent(msg, event.getEventTime()); + @Override + public void onInputEvent(InputEvent event) { + enqueueInputEvent(event, this, 0); + } } + WindowInputEventReceiver mInputEventReceiver; - private void dispatchGenericMotion(MotionEvent event, boolean sendDone) { - Message msg = obtainMessage(DISPATCH_GENERIC_MOTION); - msg.obj = event; - msg.arg1 = sendDone ? 1 : 0; - enqueueInputEvent(msg, event.getEventTime()); + public void dispatchKey(KeyEvent event) { + enqueueInputEvent(event, null, 0); } public void dispatchAppVisibility(boolean visible) { @@ -4099,7 +4059,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void finishedEvent(int seq, boolean handled) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { - viewAncestor.dispatchFinishedEvent(seq, handled); + viewAncestor.dispatchImeFinishedEvent(seq, handled); } } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 2e19bf64d3e1..7d729c68ab4d 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -340,7 +340,8 @@ public interface WindowManagerPolicy { * Add a fake window to the window manager. This window sits * at the top of the other windows and consumes events. */ - public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler, + public FakeWindow addFakeWindow(Looper looper, + InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 787bcdd2c7f3..1fab1cab5e49 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9384,40 +9384,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mPositionY = mTempCoords[1]; } - public boolean isVisible(int positionX, int positionY) { - final TextView textView = TextView.this; + public void onScrollChanged() { + mScrollHasChanged = true; + } + } - if (mTempRect == null) mTempRect = new Rect(); - final Rect clip = mTempRect; - clip.left = getCompoundPaddingLeft(); - clip.top = getExtendedPaddingTop(); - clip.right = textView.getWidth() - getCompoundPaddingRight(); - clip.bottom = textView.getHeight() - getExtendedPaddingBottom(); - - final ViewParent parent = textView.getParent(); - if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) { - return false; - } + private boolean isPositionVisible(int positionX, int positionY) { + synchronized (sTmpPosition) { + final float[] position = sTmpPosition; + position[0] = positionX; + position[1] = positionY; + View view = this; - int posX = mPositionX + positionX; - int posY = mPositionY + positionY; + while (view != null) { + if (view != this) { + // Local scroll is already taken into account in positionX/Y + position[0] -= view.getScrollX(); + position[1] -= view.getScrollY(); + } - // Offset by 1 to take into account 0.5 and int rounding around getPrimaryHorizontal. - return posX >= clip.left - 1 && posX <= clip.right + 1 && - posY >= clip.top && posY <= clip.bottom; - } + if (position[0] < 0 || position[1] < 0 || + position[0] > view.getWidth() || position[1] > view.getHeight()) { + return false; + } - public boolean isOffsetVisible(int offset) { - final int line = mLayout.getLineForOffset(offset); - final int lineBottom = mLayout.getLineBottom(line); - final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset); - return isVisible(primaryHorizontal + viewportToContentHorizontalOffset(), - lineBottom + viewportToContentVerticalOffset()); - } + if (!view.getMatrix().isIdentity()) { + view.getMatrix().mapPoints(position); + } - public void onScrollChanged() { - mScrollHasChanged = true; + position[0] += view.getLeft(); + position[1] += view.getTop(); + + final ViewParent parent = view.getParent(); + if (parent instanceof View) { + view = (View) parent; + } else { + // We've reached the ViewRoot, stop iterating + view = null; + } + } } + + // We've been able to walk up the view hierarchy and the position was never clipped + return true; + } + + private boolean isOffsetVisible(int offset) { + final int line = mLayout.getLineForOffset(offset); + final int lineBottom = mLayout.getLineBottom(line); + final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset); + return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(), + lineBottom + viewportToContentVerticalOffset()); } @Override @@ -9518,7 +9535,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled) { // Either parentPositionChanged or parentScrolled is true, check if still visible - if (isShowing() && getPositionListener().isOffsetVisible(getTextOffset())) { + if (isShowing() && isOffsetVisible(getTextOffset())) { if (parentScrolled) computeLocalPosition(); updatePosition(parentPositionX, parentPositionY); } else { @@ -10542,7 +10559,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - return getPositionListener().isVisible(mPositionX + mHotspotX, mPositionY); + return TextView.this.isPositionVisible(mPositionX + mHotspotX, mPositionY); } public abstract int getCurrentCursorOffset(); @@ -11518,6 +11535,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Path mHighlightPath; private boolean mHighlightPathBogus = true; private static final RectF sTempRect = new RectF(); + private static final float[] sTmpPosition = new float[2]; // XXX should be much larger private static final int VERY_WIDE = 1024*1024; diff --git a/core/java/com/android/internal/view/BaseInputHandler.java b/core/java/com/android/internal/view/BaseInputHandler.java deleted file mode 100644 index 74b4b06eaa25..000000000000 --- a/core/java/com/android/internal/view/BaseInputHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 com.android.internal.view; - -import android.view.InputHandler; -import android.view.InputQueue; -import android.view.KeyEvent; -import android.view.MotionEvent; - -/** - * Base do-nothing implementation of an input handler. - * @hide - */ -public abstract class BaseInputHandler implements InputHandler { - public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) { - finishedCallback.finished(false); - } - - public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { - finishedCallback.finished(false); - } -} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 71c5d2662292..f20fbbb6019e 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -50,7 +50,7 @@ LOCAL_SRC_FILES:= \ android_view_Surface.cpp \ android_view_TextureView.cpp \ android_view_InputChannel.cpp \ - android_view_InputQueue.cpp \ + android_view_InputEventReceiver.cpp \ android_view_KeyEvent.cpp \ android_view_KeyCharacterMap.cpp \ android_view_HardwareRenderer.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 6d1410cc84f6..c6447e198b17 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -168,7 +168,7 @@ extern int register_android_app_backup_FullBackup(JNIEnv *env); extern int register_android_app_ActivityThread(JNIEnv *env); extern int register_android_app_NativeActivity(JNIEnv *env); extern int register_android_view_InputChannel(JNIEnv* env); -extern int register_android_view_InputQueue(JNIEnv* env); +extern int register_android_view_InputEventReceiver(JNIEnv* env); extern int register_android_view_KeyEvent(JNIEnv* env); extern int register_android_view_MotionEvent(JNIEnv* env); extern int register_android_view_PointerIcon(JNIEnv* env); @@ -1192,7 +1192,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_app_ActivityThread), REG_JNI(register_android_app_NativeActivity), REG_JNI(register_android_view_InputChannel), - REG_JNI(register_android_view_InputQueue), + REG_JNI(register_android_view_InputEventReceiver), REG_JNI(register_android_view_KeyEvent), REG_JNI(register_android_view_MotionEvent), REG_JNI(register_android_view_PointerIcon), diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp new file mode 100644 index 000000000000..9ae63dd61db2 --- /dev/null +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "InputEventReceiver" + +//#define LOG_NDEBUG 0 + +// Log debug messages about the dispatch cycle. +#define DEBUG_DISPATCH_CYCLE 0 + + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <utils/Log.h> +#include <utils/Looper.h> +#include <utils/KeyedVector.h> +#include <utils/threads.h> +#include <ui/InputTransport.h> +#include "android_os_MessageQueue.h" +#include "android_view_InputChannel.h" +#include "android_view_KeyEvent.h" +#include "android_view_MotionEvent.h" + +namespace android { + +static struct { + jclass clazz; + + jmethodID dispatchInputEvent; +} gInputEventReceiverClassInfo; + + +class NativeInputEventReceiver : public RefBase { +public: + NativeInputEventReceiver(JNIEnv* env, + jobject receiverObj, const sp<InputChannel>& inputChannel, + const sp<Looper>& looper); + + status_t initialize(); + status_t finishInputEvent(bool handled); + static int handleReceiveCallback(int receiveFd, int events, void* data); + +protected: + virtual ~NativeInputEventReceiver(); + +private: + jobject mReceiverObjGlobal; + InputConsumer mInputConsumer; + sp<Looper> mLooper; + bool mEventInProgress; + PreallocatedInputEventFactory mInputEventFactory; + + const char* getInputChannelName() { + return mInputConsumer.getChannel()->getName().string(); + } +}; + + +NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, + jobject receiverObj, const sp<InputChannel>& inputChannel, const sp<Looper>& looper) : + mReceiverObjGlobal(env->NewGlobalRef(receiverObj)), + mInputConsumer(inputChannel), mLooper(looper), mEventInProgress(false) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName()); +#endif +} + +NativeInputEventReceiver::~NativeInputEventReceiver() { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName()); +#endif + + mLooper->removeFd(mInputConsumer.getChannel()->getReceivePipeFd()); + if (mEventInProgress) { + mInputConsumer.sendFinishedSignal(false); // ignoring result + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mReceiverObjGlobal); +} + +status_t NativeInputEventReceiver::initialize() { + status_t result = mInputConsumer.initialize(); + if (result) { + LOGW("Failed to initialize input consumer for input channel '%s', status=%d", + getInputChannelName(), result); + return result; + } + + int32_t receiveFd = mInputConsumer.getChannel()->getReceivePipeFd(); + mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); + return OK; +} + +status_t NativeInputEventReceiver::finishInputEvent(bool handled) { + if (mEventInProgress) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Finished input event.", getInputChannelName()); +#endif + mEventInProgress = false; + + status_t status = mInputConsumer.sendFinishedSignal(handled); + if (status) { + LOGW("Failed to send finished signal on channel '%s'. status=%d", + getInputChannelName(), status); + } + return status; + } else { + LOGW("Ignoring attempt to finish input event while no event is in progress."); + return OK; + } +} + +int NativeInputEventReceiver::handleReceiveCallback(int receiveFd, int events, void* data) { + sp<NativeInputEventReceiver> r = static_cast<NativeInputEventReceiver*>(data); + + if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { + LOGE("channel '%s' ~ Publisher closed input channel or an error occurred. " + "events=0x%x", r->getInputChannelName(), events); + return 0; // remove the callback + } + + if (!(events & ALOOPER_EVENT_INPUT)) { + LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " + "events=0x%x", r->getInputChannelName(), events); + return 1; + } + + status_t status = r->mInputConsumer.receiveDispatchSignal(); + if (status) { + LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", + r->getInputChannelName(), status); + return 0; // remove the callback + } + + if (r->mEventInProgress) { + LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.", + r->getInputChannelName()); + return 1; + } + + InputEvent* inputEvent; + status = r->mInputConsumer.consume(&r->mInputEventFactory, &inputEvent); + if (status) { + LOGW("channel '%s' ~ Failed to consume input event. status=%d", + r->getInputChannelName(), status); + r->mInputConsumer.sendFinishedSignal(false); + return 1; + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jobject inputEventObj; + switch (inputEvent->getType()) { + case AINPUT_EVENT_TYPE_KEY: +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Received key event.", + r->getInputChannelName()); +#endif + inputEventObj = android_view_KeyEvent_fromNative(env, + static_cast<KeyEvent*>(inputEvent)); + break; + + case AINPUT_EVENT_TYPE_MOTION: +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Received motion event.", + r->getInputChannelName()); +#endif + inputEventObj = android_view_MotionEvent_obtainAsCopy(env, + static_cast<MotionEvent*>(inputEvent)); + break; + + default: + assert(false); // InputConsumer should prevent this from ever happening + inputEventObj = NULL; + } + + if (!inputEventObj) { + LOGW("channel '%s' ~ Failed to obtain event object.", + r->getInputChannelName()); + r->mInputConsumer.sendFinishedSignal(false); + return 1; + } + + r->mEventInProgress = true; + +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Invoking input handler.", r->getInputChannelName()); +#endif + env->CallVoidMethod(r->mReceiverObjGlobal, + gInputEventReceiverClassInfo.dispatchInputEvent, inputEventObj); +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Returned from input handler.", r->getInputChannelName()); +#endif + + if (env->ExceptionCheck()) { + LOGE("channel '%s' ~ An exception occurred while dispatching an event.", + r->getInputChannelName()); + LOGE_EX(env); + env->ExceptionClear(); + + if (r->mEventInProgress) { + r->mInputConsumer.sendFinishedSignal(false); + r->mEventInProgress = false; + } + } + + env->DeleteLocalRef(inputEventObj); + return 1; +} + + +static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj, + jobject inputChannelObj, jobject messageQueueObj) { + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + jniThrowRuntimeException(env, "InputChannel is not initialized."); + return 0; + } + + sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj); + if (looper == NULL) { + jniThrowRuntimeException(env, "MessageQueue is not initialized."); + return 0; + } + + sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env, + receiverObj, inputChannel, looper); + status_t status = receiver->initialize(); + if (status) { + String8 message; + message.appendFormat("Failed to initialize input event receiver. status=%d", status); + jniThrowRuntimeException(env, message.string()); + return 0; + } + + receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object + return reinterpret_cast<jint>(receiver.get()); +} + +static void nativeDispose(JNIEnv* env, jclass clazz, jint receiverPtr) { + sp<NativeInputEventReceiver> receiver = + reinterpret_cast<NativeInputEventReceiver*>(receiverPtr); + receiver->decStrong(gInputEventReceiverClassInfo.clazz); // drop reference held by the object +} + +static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr, jboolean handled) { + sp<NativeInputEventReceiver> receiver = + reinterpret_cast<NativeInputEventReceiver*>(receiverPtr); + status_t status = receiver->finishInputEvent(handled); + if (status) { + String8 message; + message.appendFormat("Failed to finish input event. status=%d", status); + jniThrowRuntimeException(env, message.string()); + } +} + + +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "nativeInit", + "(Landroid/view/InputEventReceiver;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I", + (void*)nativeInit }, + { "nativeDispose", + "(I)V", + (void*)nativeDispose }, + { "nativeFinishInputEvent", "(IZ)V", + (void*)nativeFinishInputEvent } +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method " methodName); + +int register_android_view_InputEventReceiver(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/InputEventReceiver", + gMethods, NELEM(gMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputEventReceiverClassInfo.clazz, "android/view/InputEventReceiver"); + + GET_METHOD_ID(gInputEventReceiverClassInfo.dispatchInputEvent, + gInputEventReceiverClassInfo.clazz, + "dispatchInputEvent", "(Landroid/view/InputEvent;)V"); + return 0; +} + +} // namespace android diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp deleted file mode 100644 index 300c04a03a03..000000000000 --- a/core/jni/android_view_InputQueue.cpp +++ /dev/null @@ -1,530 +0,0 @@ -/* - * 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. - */ - -#define LOG_TAG "InputQueue-JNI" - -//#define LOG_NDEBUG 0 - -// Log debug messages about the dispatch cycle. -#define DEBUG_DISPATCH_CYCLE 0 - -// Log debug messages about registrations. -#define DEBUG_REGISTRATION 0 - - -#include "JNIHelp.h" - -#include <android_runtime/AndroidRuntime.h> -#include <utils/Log.h> -#include <utils/Looper.h> -#include <utils/KeyedVector.h> -#include <utils/threads.h> -#include <ui/InputTransport.h> -#include "android_os_MessageQueue.h" -#include "android_view_InputChannel.h" -#include "android_view_KeyEvent.h" -#include "android_view_MotionEvent.h" - -namespace android { - -// ---------------------------------------------------------------------------- - -static struct { - jclass clazz; - - jmethodID dispatchKeyEvent; - jmethodID dispatchMotionEvent; -} gInputQueueClassInfo; - -// ---------------------------------------------------------------------------- - -class NativeInputQueue { -public: - NativeInputQueue(); - ~NativeInputQueue(); - - status_t registerInputChannel(JNIEnv* env, jobject inputChannelObj, - jobject inputHandlerObj, jobject messageQueueObj); - - status_t unregisterInputChannel(JNIEnv* env, jobject inputChannelObj); - - status_t finished(JNIEnv* env, jlong finishedToken, bool handled, bool ignoreSpuriousFinish); - -private: - class Connection : public RefBase { - protected: - virtual ~Connection(); - - public: - enum Status { - // Everything is peachy. - STATUS_NORMAL, - // The input channel has been unregistered. - STATUS_ZOMBIE - }; - - Connection(uint16_t id, - const sp<InputChannel>& inputChannel, const sp<Looper>& looper); - - inline const char* getInputChannelName() const { return inputChannel->getName().string(); } - - // A unique id for this connection. - uint16_t id; - - Status status; - - sp<InputChannel> inputChannel; - InputConsumer inputConsumer; - sp<Looper> looper; - jobject inputHandlerObjGlobal; - PreallocatedInputEventFactory inputEventFactory; - - // The sequence number of the current event being dispatched. - // This is used as part of the finished token as a way to determine whether the finished - // token is still valid before sending a finished signal back to the publisher. - uint16_t messageSeqNum; - - // True if a message has been received from the publisher but not yet finished. - bool messageInProgress; - }; - - Mutex mLock; - uint16_t mNextConnectionId; - KeyedVector<int32_t, sp<Connection> > mConnectionsByReceiveFd; - - ssize_t getConnectionIndex(const sp<InputChannel>& inputChannel); - - static void handleInputChannelDisposed(JNIEnv* env, - jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data); - - static int handleReceiveCallback(int receiveFd, int events, void* data); - - static jlong generateFinishedToken(int32_t receiveFd, - uint16_t connectionId, uint16_t messageSeqNum); - - static void parseFinishedToken(jlong finishedToken, - int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex); -}; - -// ---------------------------------------------------------------------------- - -NativeInputQueue::NativeInputQueue() : - mNextConnectionId(0) { -} - -NativeInputQueue::~NativeInputQueue() { -} - -status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj, - jobject inputHandlerObj, jobject messageQueueObj) { - sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, - inputChannelObj); - if (inputChannel == NULL) { - LOGW("Input channel is not initialized."); - return BAD_VALUE; - } - -#if DEBUG_REGISTRATION - LOGD("channel '%s' - Registered", inputChannel->getName().string()); -#endif - - sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj); - - { // acquire lock - AutoMutex _l(mLock); - - if (getConnectionIndex(inputChannel) >= 0) { - LOGW("Attempted to register already registered input channel '%s'", - inputChannel->getName().string()); - return BAD_VALUE; - } - - uint16_t connectionId = mNextConnectionId++; - sp<Connection> connection = new Connection(connectionId, inputChannel, looper); - status_t result = connection->inputConsumer.initialize(); - if (result) { - LOGW("Failed to initialize input consumer for input channel '%s', status=%d", - inputChannel->getName().string(), result); - return result; - } - - connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj); - - int32_t receiveFd = inputChannel->getReceivePipeFd(); - mConnectionsByReceiveFd.add(receiveFd, connection); - - looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); - } // release lock - - android_view_InputChannel_setDisposeCallback(env, inputChannelObj, - handleInputChannelDisposed, this); - return OK; -} - -status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) { - sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, - inputChannelObj); - if (inputChannel == NULL) { - LOGW("Input channel is not initialized."); - return BAD_VALUE; - } - -#if DEBUG_REGISTRATION - LOGD("channel '%s' - Unregistered", inputChannel->getName().string()); -#endif - - { // acquire lock - AutoMutex _l(mLock); - - ssize_t connectionIndex = getConnectionIndex(inputChannel); - if (connectionIndex < 0) { - LOGW("Attempted to unregister already unregistered input channel '%s'", - inputChannel->getName().string()); - return BAD_VALUE; - } - - sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); - mConnectionsByReceiveFd.removeItemsAt(connectionIndex); - - connection->status = Connection::STATUS_ZOMBIE; - - connection->looper->removeFd(inputChannel->getReceivePipeFd()); - - env->DeleteGlobalRef(connection->inputHandlerObjGlobal); - connection->inputHandlerObjGlobal = NULL; - - if (connection->messageInProgress) { - LOGI("Sending finished signal for input channel '%s' since it is being unregistered " - "while an input message is still in progress.", - connection->getInputChannelName()); - connection->messageInProgress = false; - connection->inputConsumer.sendFinishedSignal(false); // ignoring result - } - } // release lock - - android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL); - return OK; -} - -ssize_t NativeInputQueue::getConnectionIndex(const sp<InputChannel>& inputChannel) { - ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd()); - if (connectionIndex >= 0) { - sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); - if (connection->inputChannel.get() == inputChannel.get()) { - return connectionIndex; - } - } - - return -1; -} - -status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, - bool handled, bool ignoreSpuriousFinish) { - int32_t receiveFd; - uint16_t connectionId; - uint16_t messageSeqNum; - parseFinishedToken(finishedToken, &receiveFd, &connectionId, &messageSeqNum); - - { // acquire lock - AutoMutex _l(mLock); - - ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); - if (connectionIndex < 0) { - if (! ignoreSpuriousFinish) { - LOGI("Ignoring finish signal on channel that is no longer registered."); - } - return DEAD_OBJECT; - } - - sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); - if (connectionId != connection->id) { - if (! ignoreSpuriousFinish) { - LOGI("Ignoring finish signal on channel that is no longer registered."); - } - return DEAD_OBJECT; - } - - if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) { - if (! ignoreSpuriousFinish) { - LOGW("Attempted to finish input twice on channel '%s'. " - "finished messageSeqNum=%d, current messageSeqNum=%d, messageInProgress=%d", - connection->getInputChannelName(), - messageSeqNum, connection->messageSeqNum, connection->messageInProgress); - } - return INVALID_OPERATION; - } - - connection->messageInProgress = false; - - status_t status = connection->inputConsumer.sendFinishedSignal(handled); - if (status) { - LOGW("Failed to send finished signal on channel '%s'. status=%d", - connection->getInputChannelName(), status); - return status; - } - -#if DEBUG_DISPATCH_CYCLE - LOGD("channel '%s' ~ Finished event.", - connection->getInputChannelName()); -#endif - } // release lock - - return OK; -} - -void NativeInputQueue::handleInputChannelDisposed(JNIEnv* env, - jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) { - LOGW("Input channel object '%s' was disposed without first being unregistered with " - "the input queue!", inputChannel->getName().string()); - - NativeInputQueue* q = static_cast<NativeInputQueue*>(data); - q->unregisterInputChannel(env, inputChannelObj); -} - -int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) { - NativeInputQueue* q = static_cast<NativeInputQueue*>(data); - JNIEnv* env = AndroidRuntime::getJNIEnv(); - - sp<Connection> connection; - InputEvent* inputEvent; - jobject inputHandlerObjLocal; - jlong finishedToken; - { // acquire lock - AutoMutex _l(q->mLock); - - ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd); - if (connectionIndex < 0) { - LOGE("Received spurious receive callback for unknown input channel. " - "fd=%d, events=0x%x", receiveFd, events); - return 0; // remove the callback - } - - connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex); - if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { - LOGE("channel '%s' ~ Publisher closed input channel or an error occurred. " - "events=0x%x", connection->getInputChannelName(), events); - return 0; // remove the callback - } - - if (! (events & ALOOPER_EVENT_INPUT)) { - LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " - "events=0x%x", connection->getInputChannelName(), events); - return 1; - } - - status_t status = connection->inputConsumer.receiveDispatchSignal(); - if (status) { - LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", - connection->getInputChannelName(), status); - return 0; // remove the callback - } - - if (connection->messageInProgress) { - LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.", - connection->getInputChannelName()); - return 1; - } - - status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent); - if (status) { - LOGW("channel '%s' ~ Failed to consume input event. status=%d", - connection->getInputChannelName(), status); - connection->inputConsumer.sendFinishedSignal(false); - return 1; - } - - connection->messageInProgress = true; - connection->messageSeqNum += 1; - - finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum); - - inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal); - } // release lock - - // Invoke the handler outside of the lock. - // - // Note: inputEvent is stored in a field of the connection object which could potentially - // become disposed due to the input channel being unregistered concurrently. - // For this reason, we explicitly keep the connection object alive by holding - // a strong pointer to it within this scope. We also grabbed a local reference to - // the input handler object itself for the same reason. - - int32_t inputEventType = inputEvent->getType(); - - jobject inputEventObj; - jmethodID dispatchMethodId; - switch (inputEventType) { - case AINPUT_EVENT_TYPE_KEY: -#if DEBUG_DISPATCH_CYCLE - LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName()); -#endif - inputEventObj = android_view_KeyEvent_fromNative(env, - static_cast<KeyEvent*>(inputEvent)); - dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent; - break; - - case AINPUT_EVENT_TYPE_MOTION: -#if DEBUG_DISPATCH_CYCLE - LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName()); -#endif - inputEventObj = android_view_MotionEvent_obtainAsCopy(env, - static_cast<MotionEvent*>(inputEvent)); - dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent; - break; - - default: - assert(false); // InputConsumer should prevent this from ever happening - inputEventObj = NULL; - } - - if (! inputEventObj) { - LOGW("channel '%s' ~ Failed to obtain DVM event object.", - connection->getInputChannelName()); - env->DeleteLocalRef(inputHandlerObjLocal); - q->finished(env, finishedToken, false, false); - return 1; - } - -#if DEBUG_DISPATCH_CYCLE - LOGD("Invoking input handler."); -#endif - env->CallStaticVoidMethod(gInputQueueClassInfo.clazz, - dispatchMethodId, inputHandlerObjLocal, inputEventObj, - jlong(finishedToken)); -#if DEBUG_DISPATCH_CYCLE - LOGD("Returned from input handler."); -#endif - - if (env->ExceptionCheck()) { - LOGE("An exception occurred while invoking the input handler for an event."); - LOGE_EX(env); - env->ExceptionClear(); - - q->finished(env, finishedToken, false, true /*ignoreSpuriousFinish*/); - } - - env->DeleteLocalRef(inputEventObj); - env->DeleteLocalRef(inputHandlerObjLocal); - return 1; -} - -jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId, - uint16_t messageSeqNum) { - return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum); -} - -void NativeInputQueue::parseFinishedToken(jlong finishedToken, - int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex) { - *outReceiveFd = int32_t(finishedToken >> 32); - *outConnectionId = uint16_t(finishedToken >> 16); - *outMessageIndex = uint16_t(finishedToken); -} - -// ---------------------------------------------------------------------------- - -NativeInputQueue::Connection::Connection(uint16_t id, - const sp<InputChannel>& inputChannel, const sp<Looper>& looper) : - id(id), status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel), - looper(looper), inputHandlerObjGlobal(NULL), - messageSeqNum(0), messageInProgress(false) { -} - -NativeInputQueue::Connection::~Connection() { -} - -// ---------------------------------------------------------------------------- - -static NativeInputQueue gNativeInputQueue; - -static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, - jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) { - status_t status = gNativeInputQueue.registerInputChannel( - env, inputChannelObj, inputHandlerObj, messageQueueObj); - - if (status) { - String8 message; - message.appendFormat("Failed to register input channel. status=%d", status); - jniThrowRuntimeException(env, message.string()); - } -} - -static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, - jobject inputChannelObj) { - status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj); - - if (status) { - String8 message; - message.appendFormat("Failed to unregister input channel. status=%d", status); - jniThrowRuntimeException(env, message.string()); - } -} - -static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz, - jlong finishedToken, bool handled) { - status_t status = gNativeInputQueue.finished( - env, finishedToken, handled, false /*ignoreSpuriousFinish*/); - - // We ignore the case where an event could not be finished because the input channel - // was no longer registered (DEAD_OBJECT) since it is a common race that can occur - // during application shutdown. The input dispatcher recovers gracefully anyways. - if (status != OK && status != DEAD_OBJECT) { - String8 message; - message.appendFormat("Failed to finish input event. status=%d", status); - jniThrowRuntimeException(env, message.string()); - } -} - -// ---------------------------------------------------------------------------- - -static JNINativeMethod gInputQueueMethods[] = { - /* name, signature, funcPtr */ - { "nativeRegisterInputChannel", - "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V", - (void*)android_view_InputQueue_nativeRegisterInputChannel }, - { "nativeUnregisterInputChannel", - "(Landroid/view/InputChannel;)V", - (void*)android_view_InputQueue_nativeUnregisterInputChannel }, - { "nativeFinished", "(JZ)V", - (void*)android_view_InputQueue_nativeFinished } -}; - -#define FIND_CLASS(var, className) \ - var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); - -#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \ - var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find static method " methodName); - -int register_android_view_InputQueue(JNIEnv* env) { - int res = jniRegisterNativeMethods(env, "android/view/InputQueue", - gInputQueueMethods, NELEM(gInputQueueMethods)); - LOG_FATAL_IF(res < 0, "Unable to register native methods."); - - FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue"); - - GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz, - "dispatchKeyEvent", - "(Landroid/view/InputHandler;Landroid/view/KeyEvent;J)V"); - - GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz, - "dispatchMotionEvent", - "(Landroid/view/InputHandler;Landroid/view/MotionEvent;J)V"); - return 0; -} - -} // namespace android diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp index 6965702ab098..641134a5d2e1 100644 --- a/libs/binder/IPCThreadState.cpp +++ b/libs/binder/IPCThreadState.cpp @@ -773,6 +773,7 @@ status_t IPCThreadState::talkWithDriver(bool doReceive) bwr.read_buffer = (long unsigned int)mIn.data(); } else { bwr.read_size = 0; + bwr.read_buffer = 0; } IF_LOG_COMMANDS() { diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 3372d1c249bf..ae7a3b5b27b4 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -477,8 +477,9 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { float x = getFloat(); float y = getFloat(); SkPaint* paint = getPaint(); - LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op], - text.text(), text.length(), count, x, y, paint); + float length = getFloat(); + LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p, %.2f", (char*) indent, OP_NAMES[op], + text.text(), text.length(), count, x, y, paint, length); } break; case ResetShader: { @@ -837,9 +838,10 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) float x = getFloat(); float y = getFloat(); SkPaint* paint = getPaint(); - DISPLAY_LIST_LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op], - text.text(), text.length(), count, x, y, paint); - renderer.drawText(text.text(), text.length(), count, x, y, paint); + float length = getFloat(); + DISPLAY_LIST_LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p, %.2f", (char*) indent, + OP_NAMES[op], text.text(), text.length(), count, x, y, paint, length); + renderer.drawText(text.text(), text.length(), count, x, y, paint, length); } break; case ResetShader: { @@ -1196,13 +1198,14 @@ void DisplayListRenderer::drawPoints(float* points, int count, SkPaint* paint) { } void DisplayListRenderer::drawText(const char* text, int bytesCount, int count, - float x, float y, SkPaint* paint) { + float x, float y, SkPaint* paint, float length) { if (count <= 0) return; addOp(DisplayList::DrawText); addText(text, bytesCount); addInt(count); addPoint(x, y); addPaint(paint); + addFloat(length < 0.0f ? paint->measureText(text, bytesCount) : length); } void DisplayListRenderer::resetShader() { diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index ab475bf53beb..ab483fbc2ff5 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -290,7 +290,7 @@ public: virtual void drawLines(float* points, int count, SkPaint* paint); virtual void drawPoints(float* points, int count, SkPaint* paint); virtual void drawText(const char* text, int bytesCount, int count, float x, float y, - SkPaint* paint); + SkPaint* paint, float length); virtual void resetShader(); virtual void setupShader(SkiaShader* shader); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 3c838fcf8a61..a60ac0804af4 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -2063,7 +2063,7 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, } void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, - float x, float y, SkPaint* paint) { + float x, float y, SkPaint* paint, float length) { if (text == NULL || count == 0) { return; } @@ -2080,20 +2080,26 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); #endif - float length = -1.0f; switch (paint->getTextAlign()) { case SkPaint::kCenter_Align: - length = paint->measureText(text, bytesCount); + if (length < 0.0f) length = paint->measureText(text, bytesCount); x -= length / 2.0f; break; case SkPaint::kRight_Align: - length = paint->measureText(text, bytesCount); + if (length < 0.0f) length = paint->measureText(text, bytesCount); x -= length; break; default: break; } + SkPaint::FontMetrics metrics; + paint->getFontMetrics(&metrics, 0.0f); + if (quickReject(x, y + metrics.fTop, + x + (length >= 0.0f ? length : INT_MAX / 2), y + metrics.fBottom)) { + return; + } + const float oldX = x; const float oldY = y; const bool pureTranslate = mSnapshot->transform->isPureTranslate(); diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 2fc88e1d9674..cd9ff936f4f1 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -123,7 +123,7 @@ public: virtual void drawLines(float* points, int count, SkPaint* paint); virtual void drawPoints(float* points, int count, SkPaint* paint); virtual void drawText(const char* text, int bytesCount, int count, float x, float y, - SkPaint* paint); + SkPaint* paint, float length = -1.0f); virtual void resetShader(); virtual void setupShader(SkiaShader* shader); diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index f13a6a2b8887..687e2f6ce8c6 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -1572,49 +1572,83 @@ public class AudioService extends IAudioService.Stub { private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = new BluetoothProfile.ServiceListener() { public void onServiceConnected(int profile, BluetoothProfile proxy) { - synchronized (mScoClients) { - // Discard timeout message - mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); - mBluetoothHeadset = (BluetoothHeadset) proxy; - List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices(); + BluetoothDevice btDevice; + List<BluetoothDevice> deviceList; + switch(profile) { + case BluetoothProfile.A2DP: + BluetoothA2dp a2dp = (BluetoothA2dp) proxy; + deviceList = a2dp.getConnectedDevices(); if (deviceList.size() > 0) { - mBluetoothHeadsetDevice = deviceList.get(0); - } else { - mBluetoothHeadsetDevice = null; - } - // Refresh SCO audio state - checkScoAudioState(); - // Continue pending action if any - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) { - boolean status = false; - if (mBluetoothHeadsetDevice != null) { - switch (mScoAudioState) { - case SCO_STATE_ACTIVATE_REQ: - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - status = mBluetoothHeadset.startScoUsingVirtualVoiceCall( - mBluetoothHeadsetDevice); - break; - case SCO_STATE_DEACTIVATE_REQ: - status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( - mBluetoothHeadsetDevice); - break; - case SCO_STATE_DEACTIVATE_EXT_REQ: - status = mBluetoothHeadset.stopVoiceRecognition( - mBluetoothHeadsetDevice); - } + btDevice = deviceList.get(0); + handleA2dpConnectionStateChange(btDevice, a2dp.getConnectionState(btDevice)); + } + break; + + case BluetoothProfile.HEADSET: + synchronized (mScoClients) { + // Discard timeout message + mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); + mBluetoothHeadset = (BluetoothHeadset) proxy; + deviceList = mBluetoothHeadset.getConnectedDevices(); + if (deviceList.size() > 0) { + mBluetoothHeadsetDevice = deviceList.get(0); + } else { + mBluetoothHeadsetDevice = null; } - if (!status) { - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0, - SENDMSG_REPLACE, 0, 0, null, 0); + // Refresh SCO audio state + checkScoAudioState(); + // Continue pending action if any + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ || + mScoAudioState == SCO_STATE_DEACTIVATE_REQ || + mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) { + boolean status = false; + if (mBluetoothHeadsetDevice != null) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVATE_REQ: + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + status = mBluetoothHeadset.startScoUsingVirtualVoiceCall( + mBluetoothHeadsetDevice); + break; + case SCO_STATE_DEACTIVATE_REQ: + status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( + mBluetoothHeadsetDevice); + break; + case SCO_STATE_DEACTIVATE_EXT_REQ: + status = mBluetoothHeadset.stopVoiceRecognition( + mBluetoothHeadsetDevice); + } + } + if (!status) { + sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0, + SENDMSG_REPLACE, 0, 0, null, 0); + } } } + break; + + default: + break; } } public void onServiceDisconnected(int profile) { - synchronized (mScoClients) { - mBluetoothHeadset = null; + switch(profile) { + case BluetoothProfile.A2DP: + synchronized (mConnectedDevices) { + if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) { + makeA2dpDeviceUnavailableNow( + mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); + } + } + break; + + case BluetoothProfile.HEADSET: + synchronized (mScoClients) { + mBluetoothHeadset = null; + } + break; + + default: + break; } } }; @@ -2191,15 +2225,17 @@ public class AudioService extends IAudioService.Stub { AudioSystem.setParameters("restarting=true"); // Restore device connection states - Set set = mConnectedDevices.entrySet(); - Iterator i = set.iterator(); - while(i.hasNext()){ - Map.Entry device = (Map.Entry)i.next(); - AudioSystem.setDeviceConnectionState(((Integer)device.getKey()).intValue(), - AudioSystem.DEVICE_STATE_AVAILABLE, - (String)device.getValue()); + synchronized (mConnectedDevices) { + Set set = mConnectedDevices.entrySet(); + Iterator i = set.iterator(); + while(i.hasNext()){ + Map.Entry device = (Map.Entry)i.next(); + AudioSystem.setDeviceConnectionState( + ((Integer)device.getKey()).intValue(), + AudioSystem.DEVICE_STATE_AVAILABLE, + (String)device.getValue()); + } } - // Restore call state AudioSystem.setPhoneState(mMode); @@ -2238,7 +2274,9 @@ public class AudioService extends IAudioService.Stub { case MSG_BTA2DP_DOCK_TIMEOUT: // msg.obj == address of BTA2DP device - makeA2dpDeviceUnavailableNow( (String) msg.obj ); + synchronized (mConnectedDevices) { + makeA2dpDeviceUnavailableNow( (String) msg.obj ); + } break; case MSG_SET_FORCE_USE: @@ -2298,6 +2336,7 @@ public class AudioService extends IAudioService.Stub { } } + // must be called synchronized on mConnectedDevices private void makeA2dpDeviceAvailable(String address) { AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, AudioSystem.DEVICE_STATE_AVAILABLE, @@ -2308,6 +2347,7 @@ public class AudioService extends IAudioService.Stub { address); } + // must be called synchronized on mConnectedDevices private void makeA2dpDeviceUnavailableNow(String address) { Intent noisyIntent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); mContext.sendBroadcast(noisyIntent); @@ -2317,6 +2357,7 @@ public class AudioService extends IAudioService.Stub { mConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); } + // must be called synchronized on mConnectedDevices private void makeA2dpDeviceUnavailableLater(String address) { // prevent any activity on the A2DP audio output to avoid unwanted // reconnection of the sink. @@ -2329,14 +2370,60 @@ public class AudioService extends IAudioService.Stub { } + // must be called synchronized on mConnectedDevices private void cancelA2dpDeviceTimeout() { mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT); } + // must be called synchronized on mConnectedDevices private boolean hasScheduledA2dpDockTimeout() { return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT); } + private void handleA2dpConnectionStateChange(BluetoothDevice btDevice, int state) + { + if (btDevice == null) { + return; + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + synchronized (mConnectedDevices) { + boolean isConnected = + (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) && + mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP).equals(address)); + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { + // introduction of a delay for transient disconnections of docks when + // power is rapidly turned off/on, this message will be canceled if + // we reconnect the dock under a preset delay + makeA2dpDeviceUnavailableLater(address); + // the next time isConnected is evaluated, it will be false for the dock + } + } else { + makeA2dpDeviceUnavailableNow(address); + } + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + // this could be a reconnection after a transient disconnection + cancelA2dpDeviceTimeout(); + mDockAddress = address; + } else { + // this could be a connection of another A2DP device before the timeout of + // a dock: cancel the dock timeout, and make the dock unavailable now + if(hasScheduledA2dpDockTimeout()) { + cancelA2dpDeviceTimeout(); + makeA2dpDeviceUnavailableNow(mDockAddress); + } + } + makeA2dpDeviceAvailable(address); + } + } + } + /* cache of the address of the last dock the device was connected to */ private String mDockAddress; @@ -2374,44 +2461,8 @@ public class AudioService extends IAudioService.Stub { int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (btDevice == null) { - return; - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - boolean isConnected = - (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) && - mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP).equals(address)); - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - if (state == BluetoothProfile.STATE_DISCONNECTED) { - // introduction of a delay for transient disconnections of docks when - // power is rapidly turned off/on, this message will be canceled if - // we reconnect the dock under a preset delay - makeA2dpDeviceUnavailableLater(address); - // the next time isConnected is evaluated, it will be false for the dock - } - } else { - makeA2dpDeviceUnavailableNow(address); - } - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - // this could be a reconnection after a transient disconnection - cancelA2dpDeviceTimeout(); - mDockAddress = address; - } else { - // this could be a connection of another A2DP device before the timeout of - // a dock: cancel the dock timeout, and make the dock unavailable now - if(hasScheduledA2dpDockTimeout()) { - cancelA2dpDeviceTimeout(); - makeA2dpDeviceUnavailableNow(mDockAddress); - } - } - makeA2dpDeviceAvailable(address); - } + + handleA2dpConnectionStateChange(btDevice, state); } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); @@ -2440,103 +2491,126 @@ public class AudioService extends IAudioService.Stub { if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } - boolean isConnected = (mConnectedDevices.containsKey(device) && - mConnectedDevices.get(device).equals(address)); - synchronized (mScoClients) { - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - AudioSystem.setDeviceConnectionState(device, + synchronized (mConnectedDevices) { + boolean isConnected = (mConnectedDevices.containsKey(device) && + mConnectedDevices.get(device).equals(address)); + + synchronized (mScoClients) { + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE, address); - mConnectedDevices.remove(device); - mBluetoothHeadsetDevice = null; - resetBluetoothSco(); - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - AudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_AVAILABLE, - address); - mConnectedDevices.put(new Integer(device), address); - mBluetoothHeadsetDevice = btDevice; + mConnectedDevices.remove(device); + mBluetoothHeadsetDevice = null; + resetBluetoothSco(); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + AudioSystem.setDeviceConnectionState(device, + AudioSystem.DEVICE_STATE_AVAILABLE, + address); + mConnectedDevices.put(new Integer(device), address); + mBluetoothHeadsetDevice = btDevice; + } } } } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) { int state = intent.getIntExtra("state", 0); int microphone = intent.getIntExtra("microphone", 0); - if (microphone != 0) { - boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADSET); - if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, - AudioSystem.DEVICE_STATE_UNAVAILABLE, - ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADSET); - } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, - AudioSystem.DEVICE_STATE_AVAILABLE, - ""); - mConnectedDevices.put( - new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADSET), ""); + synchronized (mConnectedDevices) { + if (microphone != 0) { + boolean isConnected = + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADSET); + if (state == 0 && isConnected) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADSET); + } else if (state == 1 && !isConnected) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); + mConnectedDevices.put( + new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADSET), ""); + } + } else { + boolean isConnected = + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); + if (state == 0 && isConnected) { + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); + } else if (state == 1 && !isConnected) { + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); + mConnectedDevices.put( + new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), ""); + } } - } else { + } + } else if (action.equals(Intent.ACTION_USB_ANLG_HEADSET_PLUG)) { + int state = intent.getIntExtra("state", 0); + Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_ANLG_HEADSET_PLUG, state = "+state); + synchronized (mConnectedDevices) { boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, - AudioSystem.DEVICE_STATE_UNAVAILABLE, - ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, - AudioSystem.DEVICE_STATE_AVAILABLE, - ""); + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); mConnectedDevices.put( - new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), ""); + new Integer(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET), ""); } } - } else if (action.equals(Intent.ACTION_USB_ANLG_HEADSET_PLUG)) { - int state = intent.getIntExtra("state", 0); - Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_ANLG_HEADSET_PLUG, state = "+state); - boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); - if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, - AudioSystem.DEVICE_STATE_UNAVAILABLE, ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); - } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, - AudioSystem.DEVICE_STATE_AVAILABLE, ""); - mConnectedDevices.put( - new Integer(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET), ""); - } } else if (action.equals(Intent.ACTION_HDMI_AUDIO_PLUG)) { int state = intent.getIntExtra("state", 0); Log.v(TAG, "Broadcast Receiver: Got ACTION_HDMI_AUDIO_PLUG, state = "+state); - boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_AUX_DIGITAL); - if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL, - AudioSystem.DEVICE_STATE_UNAVAILABLE, ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_AUX_DIGITAL); - } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL, - AudioSystem.DEVICE_STATE_AVAILABLE, ""); - mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_AUX_DIGITAL), ""); + synchronized (mConnectedDevices) { + boolean isConnected = + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_AUX_DIGITAL); + if (state == 0 && isConnected) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_AUX_DIGITAL); + } else if (state == 1 && !isConnected) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); + mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_AUX_DIGITAL), ""); + } } } else if (action.equals(Intent.ACTION_USB_DGTL_HEADSET_PLUG)) { int state = intent.getIntExtra("state", 0); Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_DGTL_HEADSET_PLUG, state = "+state); - boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET); - if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, - AudioSystem.DEVICE_STATE_UNAVAILABLE, ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET); - } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, - AudioSystem.DEVICE_STATE_AVAILABLE, ""); - mConnectedDevices.put( - new Integer(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET), ""); + synchronized (mConnectedDevices) { + boolean isConnected = + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET); + if (state == 0 && isConnected) { + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET); + } else if (state == 1 && !isConnected) { + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); + mConnectedDevices.put( + new Integer(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET), ""); + } } } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { boolean broadcast = false; @@ -2600,6 +2674,12 @@ public class AudioService extends IAudioService.Stub { newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_DISCONNECTED); mContext.sendStickyBroadcast(newIntent); + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.A2DP); + } } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) { if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { // a package is being removed, not replaced @@ -3401,7 +3481,7 @@ public class AudioService extends IAudioService.Stub { updateRemoteControlDisplay_syncAfRcs(infoChangedFlags); } - /** + /** * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) * precondition: mediaIntent != null, target != null */ @@ -3417,7 +3497,7 @@ public class AudioService extends IAudioService.Stub { } } - /** + /** * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) * precondition: mediaIntent != null, eventReceiver != null */ diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 2dcd80d74d1b..2232995c5c64 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -34,6 +34,7 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.PointF; +import android.hardware.CameraSound; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; @@ -49,6 +50,7 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.Interpolator; import android.widget.ImageView; + import com.android.systemui.R; import java.io.File; @@ -254,6 +256,8 @@ class GlobalScreenshot { private float mBgPadding; private float mBgPaddingScale; + private CameraSound mCameraSound; + /** * @param context everything needs a context :( @@ -303,6 +307,9 @@ class GlobalScreenshot { // Scale has to account for both sides of the bg mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding); mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels; + + // Setup the Camera shutter sound + mCameraSound = new CameraSound(); } /** @@ -413,6 +420,9 @@ class GlobalScreenshot { mScreenshotLayout.post(new Runnable() { @Override public void run() { + // Play the shutter sound to notify that we've taken a screenshot + mCameraSound.playSound(CameraSound.SHUTTER_CLICK); + mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotView.buildLayer(); mScreenshotAnimation.start(); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 7684c34fc167..e6b86fcf7c5f 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -45,6 +45,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.LocalPowerManager; +import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; @@ -61,7 +62,6 @@ import com.android.internal.app.ShutdownThread; import com.android.internal.policy.PolicyManager; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.telephony.ITelephony; -import com.android.internal.view.BaseInputHandler; import com.android.internal.widget.PointerLocationView; import android.util.DisplayMetrics; @@ -75,8 +75,8 @@ import android.view.IApplicationToken; import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputDevice; -import android.view.InputQueue; -import android.view.InputHandler; +import android.view.InputEvent; +import android.view.InputEventReceiver; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -345,25 +345,32 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowState mFocusedWindow; IApplicationToken mFocusedApp; - private final InputHandler mPointerLocationInputHandler = new BaseInputHandler() { + final class PointerLocationInputEventReceiver extends InputEventReceiver { + public PointerLocationInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { + public void onInputEvent(InputEvent event) { boolean handled = false; try { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + if (event instanceof MotionEvent + && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + final MotionEvent motionEvent = (MotionEvent)event; synchronized (mLock) { if (mPointerLocationView != null) { - mPointerLocationView.addPointerEvent(event); + mPointerLocationView.addPointerEvent(motionEvent); handled = true; } } } } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } - }; - + } + PointerLocationInputEventReceiver mPointerLocationInputEventReceiver; + // The current size of the screen; really; (ir)regardless of whether the status // bar can be hidden or not int mUnrestrictedScreenLeft, mUnrestrictedScreenTop; @@ -1000,9 +1007,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mPointerLocationInputChannel == null) { try { mPointerLocationInputChannel = - mWindowManager.monitorInput("PointerLocationView"); - InputQueue.registerInputChannel(mPointerLocationInputChannel, - mPointerLocationInputHandler, mHandler.getLooper().getQueue()); + mWindowManager.monitorInput("PointerLocationView"); + mPointerLocationInputEventReceiver = + new PointerLocationInputEventReceiver( + mPointerLocationInputChannel, mHandler.getLooper()); } catch (RemoteException ex) { Slog.e(TAG, "Could not set up input monitoring channel for PointerLocation.", ex); @@ -1010,8 +1018,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } if (removeView != null) { + if (mPointerLocationInputEventReceiver != null) { + mPointerLocationInputEventReceiver.dispose(); + mPointerLocationInputEventReceiver = null; + } if (mPointerLocationInputChannel != null) { - InputQueue.unregisterInputChannel(mPointerLocationInputChannel); mPointerLocationInputChannel.dispose(); mPointerLocationInputChannel = null; } @@ -1836,13 +1847,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { * to determine when the nav bar should be shown and prevent applications from * receiving those touches. */ - final InputHandler mHideNavInputHandler = new BaseInputHandler() { + final class HideNavInputEventReceiver extends InputEventReceiver { + public HideNavInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { + public void onInputEvent(InputEvent event) { boolean handled = false; try { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (event instanceof MotionEvent + && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + final MotionEvent motionEvent = (MotionEvent)event; + if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { // When the user taps down, we re-show the nav bar. boolean changed = false; synchronized (mLock) { @@ -1879,9 +1896,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } + } + final InputEventReceiver.Factory mHideNavInputEventReceiverFactory = + new InputEventReceiver.Factory() { + @Override + public InputEventReceiver createInputEventReceiver( + InputChannel inputChannel, Looper looper) { + return new HideNavInputEventReceiver(inputChannel, looper); + } }; @Override @@ -1945,7 +1970,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } else if (mHideNavFakeWindow == null) { mHideNavFakeWindow = mWindowManagerFuncs.addFakeWindow( - mHandler.getLooper(), mHideNavInputHandler, + mHandler.getLooper(), mHideNavInputEventReceiverFactory, "hidden nav", WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER, 0, false, false, true); } diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index d34087f959d0..16eeb7babb35 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -163,7 +163,6 @@ public class DeviceStorageMonitorService extends Binder { } catch (IllegalArgumentException e) { // ignore; report -1 } - mCacheFileStats.restat(CACHE_PATH); EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT, mFreeMem, mFreeSystem, mFreeCache); } diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java index 73cd64e9f735..a19035afabae 100644 --- a/services/java/com/android/server/wm/DragState.java +++ b/services/java/com/android/server/wm/DragState.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import com.android.server.wm.WindowManagerService.DragInputEventReceiver; import com.android.server.wm.WindowManagerService.H; import android.content.ClipData; @@ -28,7 +29,6 @@ import android.os.RemoteException; import android.util.Slog; import android.view.DragEvent; import android.view.InputChannel; -import android.view.InputQueue; import android.view.Surface; import android.view.View; import android.view.WindowManager; @@ -50,6 +50,7 @@ class DragState { float mCurrentX, mCurrentY; float mThumbOffsetX, mThumbOffsetY; InputChannel mServerChannel, mClientChannel; + DragInputEventReceiver mInputEventReceiver; InputApplicationHandle mDragApplicationHandle; InputWindowHandle mDragWindowHandle; WindowState mTargetWindow; @@ -90,8 +91,8 @@ class DragState { mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel, null); - InputQueue.registerInputChannel(mClientChannel, mService.mDragInputHandler, - mService.mH.getLooper().getQueue()); + mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel, + mService.mH.getLooper()); mDragApplicationHandle = new InputApplicationHandle(null); mDragApplicationHandle.name = "drag"; @@ -139,7 +140,8 @@ class DragState { Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel"); } else { mService.mInputManager.unregisterInputChannel(mServerChannel); - InputQueue.unregisterInputChannel(mClientChannel); + mInputEventReceiver.dispose(); + mInputEventReceiver = null; mClientChannel.dispose(); mServerChannel.dispose(); mClientChannel = null; diff --git a/services/java/com/android/server/wm/FakeWindowImpl.java b/services/java/com/android/server/wm/FakeWindowImpl.java index 0e72f7d989a2..121ce1861bd4 100644 --- a/services/java/com/android/server/wm/FakeWindowImpl.java +++ b/services/java/com/android/server/wm/FakeWindowImpl.java @@ -20,7 +20,7 @@ import android.os.Looper; import android.os.Process; import android.util.Slog; import android.view.InputChannel; -import android.view.InputHandler; +import android.view.InputEventReceiver; import android.view.InputQueue; import android.view.WindowManagerPolicy; @@ -29,11 +29,13 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { final InputChannel mServerChannel, mClientChannel; final InputApplicationHandle mApplicationHandle; final InputWindowHandle mWindowHandle; + final InputEventReceiver mInputEventReceiver; final int mWindowLayer; boolean mTouchFullscreen; - public FakeWindowImpl(WindowManagerService service, Looper looper, InputHandler inputHandler, + public FakeWindowImpl(WindowManagerService service, + Looper looper, InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { mService = service; @@ -42,7 +44,9 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel, null); - InputQueue.registerInputChannel(mClientChannel, inputHandler, looper.getQueue()); + + mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver( + mClientChannel, looper); mApplicationHandle = new InputApplicationHandle(null); mApplicationHandle.name = name; @@ -87,8 +91,8 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { public void dismiss() { synchronized (mService.mWindowMap) { if (mService.removeFakeWindowLocked(this)) { + mInputEventReceiver.dispose(); mService.mInputManager.unregisterInputChannel(mServerChannel); - InputQueue.unregisterInputChannel(mClientChannel); mClientChannel.dispose(); mServerChannel.dispose(); } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index f5c2de912710..75ace4f3082f 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -36,7 +36,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import com.android.internal.app.IBatteryStats; import com.android.internal.policy.PolicyManager; import com.android.internal.policy.impl.PhoneWindowManager; -import com.android.internal.view.BaseInputHandler; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; @@ -107,8 +106,7 @@ import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; -import android.view.InputHandler; -import android.view.InputQueue; +import android.view.InputEventReceiver; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -571,18 +569,25 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; DragState mDragState = null; - final InputHandler mDragInputHandler = new BaseInputHandler() { + + final class DragInputEventReceiver extends InputEventReceiver { + public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { + public void onInputEvent(InputEvent event) { boolean handled = false; try { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 + if (event instanceof MotionEvent + && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 && mDragState != null) { + final MotionEvent motionEvent = (MotionEvent)event; boolean endDrag = false; - final float newX = event.getRawX(); - final float newY = event.getRawY(); + final float newX = motionEvent.getRawX(); + final float newY = motionEvent.getRawY(); - switch (event.getAction()) { + switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: { if (DEBUG_DRAG) { Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer"); @@ -623,10 +628,10 @@ public class WindowManagerService extends IWindowManager.Stub } catch (Exception e) { Slog.e(TAG, "Exception caught by drag handleMotion", e); } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } - }; + } /** * Whether the UI is currently running in touch mode (not showing @@ -9378,11 +9383,13 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler, + public FakeWindow addFakeWindow(Looper looper, + InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { synchronized (mWindowMap) { - FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputHandler, name, windowType, + FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputEventReceiverFactory, + name, windowType, layoutParamsFlags, canReceiveKeys, hasFocus, touchFullscreen); int i=0; while (i<mFakeWindows.size()) { |