From 9cf36b7a77bb8e821f9e593fdbb200f8a1742ff0 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Wed, 10 Oct 2012 17:17:58 -0700 Subject: [PATCH 1/3] Don't process UEvents in Dalvik unless they match a pattern. On some devices, vsync is delivered from the kernel to userspace over a netlink socket via UEvent. The result is that a thread wakes up, reads a message, creates a new String then searches it for matches against a pattern on every single frame. Reduce the overhead by performing the initial pattern matching in native code. Bug: 7326329 Change-Id: Icb22db1c38330694207bec1153840e0c06f502d6 --- core/java/android/os/UEventObserver.java | 46 ++++++++---- core/jni/android_os_UEventObserver.cpp | 96 +++++++++++++++++++----- 2 files changed, 112 insertions(+), 30 deletions(-) diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java index d33382b23c845..9dbfd50744a28 100644 --- a/core/java/android/os/UEventObserver.java +++ b/core/java/android/os/UEventObserver.java @@ -16,6 +16,8 @@ package android.os; +import android.util.Log; + import java.util.ArrayList; import java.util.HashMap; @@ -37,14 +39,20 @@ import java.util.HashMap; * @hide */ public abstract class UEventObserver { + private static final String TAG = "UEventObserver"; + private static final boolean DEBUG = false; + private static UEventThread sThread; - private static native void native_setup(); - private static native int next_event(byte[] buffer); + private static native void nativeSetup(); + private static native String nativeWaitForNextEvent(); + private static native void nativeAddMatch(String match); + private static native void nativeRemoveMatch(String match); public UEventObserver() { } + @Override protected void finalize() throws Throwable { try { stopObserving(); @@ -78,10 +86,18 @@ public abstract class UEventObserver { * This method can be called multiple times to register multiple matches. * Only one call to stopObserving is required even with multiple registered * matches. - * @param match A substring of the UEvent to match. Use "" to match all - * UEvent's + * + * @param match A substring of the UEvent to match. Try to be as specific + * as possible to avoid incurring unintended additional cost from processing + * irrelevant messages. Netlink messages can be moderately high bandwidth and + * are expensive to parse. For example, some devices may send one netlink message + * for each vsync period. */ public final void startObserving(String match) { + if (match == null || match.isEmpty()) { + throw new IllegalArgumentException("match substring must be non-empty"); + } + final UEventThread t = getThread(); t.addObserver(match, this); } @@ -117,7 +133,7 @@ public abstract class UEventObserver { while (offset < length) { int equals = message.indexOf('=', offset); - int at = message.indexOf(0, offset); + int at = message.indexOf('\0', offset); if (at < 0) break; if (equals > offset && equals < at) { @@ -158,15 +174,17 @@ public abstract class UEventObserver { super("UEventObserver"); } + @Override public void run() { - native_setup(); + nativeSetup(); - byte[] buffer = new byte[1024]; - int len; while (true) { - len = next_event(buffer); - if (len > 0) { - sendEvent(new String(buffer, 0, len)); + String message = nativeWaitForNextEvent(); + if (message != null) { + if (DEBUG) { + Log.d(TAG, message); + } + sendEvent(message); } } } @@ -176,7 +194,7 @@ public abstract class UEventObserver { final int N = mKeysAndObservers.size(); for (int i = 0; i < N; i += 2) { final String key = (String)mKeysAndObservers.get(i); - if (message.indexOf(key) != -1) { + if (message.contains(key)) { final UEventObserver observer = (UEventObserver)mKeysAndObservers.get(i + 1); mTempObserversToSignal.add(observer); @@ -199,6 +217,7 @@ public abstract class UEventObserver { synchronized (mKeysAndObservers) { mKeysAndObservers.add(match); mKeysAndObservers.add(observer); + nativeAddMatch(match); } } @@ -208,7 +227,8 @@ public abstract class UEventObserver { for (int i = 0; i < mKeysAndObservers.size(); ) { if (mKeysAndObservers.get(i + 1) == observer) { mKeysAndObservers.remove(i + 1); - mKeysAndObservers.remove(i); + final String match = (String)mKeysAndObservers.remove(i); + nativeRemoveMatch(match); } else { i += 2; } diff --git a/core/jni/android_os_UEventObserver.cpp b/core/jni/android_os_UEventObserver.cpp index 5639f4f8a01f0..7033ff3c518f9 100644 --- a/core/jni/android_os_UEventObserver.cpp +++ b/core/jni/android_os_UEventObserver.cpp @@ -15,6 +15,8 @@ */ #define LOG_TAG "UEventObserver" +//#define LOG_NDEBUG 0 + #include "utils/Log.h" #include "hardware_legacy/uevent.h" @@ -22,34 +24,94 @@ #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" -namespace android -{ +#include +#include +#include +#include -static void -android_os_UEventObserver_native_setup(JNIEnv *env, jclass clazz) -{ +namespace android { + +static Mutex gMatchesMutex; +static Vector gMatches; + +static void nativeSetup(JNIEnv *env, jclass clazz) { if (!uevent_init()) { jniThrowException(env, "java/lang/RuntimeException", - "Unable to open socket for UEventObserver"); + "Unable to open socket for UEventObserver"); } } -static int -android_os_UEventObserver_next_event(JNIEnv *env, jclass clazz, jbyteArray jbuffer) -{ - int buf_sz = env->GetArrayLength(jbuffer); - char *buffer = (char*)env->GetByteArrayElements(jbuffer, NULL); +static bool isMatch(const char* buffer, size_t length) { + AutoMutex _l(gMatchesMutex); - int length = uevent_next_event(buffer, buf_sz - 1); + for (size_t i = 0; i < gMatches.size(); i++) { + const String8& match = gMatches.itemAt(i); - env->ReleaseByteArrayElements(jbuffer, (jbyte*)buffer, 0); + // Consider all zero-delimited fields of the buffer. + const char* field = buffer; + const char* end = buffer + length; + do { + if (strstr(field, match.string())) { + ALOGV("Matched uevent message with pattern: %s", match.string()); + return true; + } + field += strlen(field) + 1; + } while (field != end); + } + return false; +} - return length; +static jstring nativeWaitForNextEvent(JNIEnv *env, jclass clazz) { + char buffer[1024]; + + for (;;) { + int length = uevent_next_event(buffer, sizeof(buffer) - 1); + if (length <= 0) { + return NULL; + } + buffer[length] = '\0'; + + ALOGV("Received uevent message: %s", buffer); + + if (isMatch(buffer, length)) { + // Assume the message is ASCII. + jchar message[length]; + for (int i = 0; i < length; i++) { + message[i] = buffer[i]; + } + return env->NewString(message, length); + } + } +} + +static void nativeAddMatch(JNIEnv* env, jclass clazz, jstring matchStr) { + ScopedUtfChars match(env, matchStr); + + AutoMutex _l(gMatchesMutex); + gMatches.add(String8(match.c_str())); +} + +static void nativeRemoveMatch(JNIEnv* env, jclass clazz, jstring matchStr) { + ScopedUtfChars match(env, matchStr); + + AutoMutex _l(gMatchesMutex); + for (size_t i = 0; i < gMatches.size(); i++) { + if (gMatches.itemAt(i) == match.c_str()) { + gMatches.removeAt(i); + break; // only remove first occurrence + } + } } static JNINativeMethod gMethods[] = { - {"native_setup", "()V", (void *)android_os_UEventObserver_native_setup}, - {"next_event", "([B)I", (void *)android_os_UEventObserver_next_event}, + { "nativeSetup", "()V", + (void *)nativeSetup }, + { "nativeWaitForNextEvent", "()Ljava/lang/String;", + (void *)nativeWaitForNextEvent }, + { "nativeAddMatch", "(Ljava/lang/String;)V", + (void *)nativeAddMatch }, + { "nativeRemoveMatch", "(Ljava/lang/String;)V", + (void *)nativeRemoveMatch }, }; @@ -64,7 +126,7 @@ int register_android_os_UEventObserver(JNIEnv *env) } return AndroidRuntime::registerNativeMethods(env, - "android/os/UEventObserver", gMethods, NELEM(gMethods)); + "android/os/UEventObserver", gMethods, NELEM(gMethods)); } } // namespace android From 78eb122450f127d66d4e8cf7f65cad80ea85d3ac Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Wed, 10 Oct 2012 18:27:44 -0700 Subject: [PATCH 2/3] Dejank electron beam. On some devices it can take hundreds of milliseconds to get a brand new EGL surface performing in tip-top shape. To get it ready make it do a few pushups before the show begins. Bug: 7318962 Change-Id: I7ae92ce100c368327042a29ffa65faee9b567c8d --- .../com/android/server/power/ElectronBeam.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/services/java/com/android/server/power/ElectronBeam.java b/services/java/com/android/server/power/ElectronBeam.java index 2abdceb5de41d..9a536485ae1b9 100644 --- a/services/java/com/android/server/power/ElectronBeam.java +++ b/services/java/com/android/server/power/ElectronBeam.java @@ -63,6 +63,11 @@ final class ElectronBeam { private static final float HSTRETCH_DURATION = 0.5f; private static final float VSTRETCH_DURATION = 1.0f - HSTRETCH_DURATION; + // The number of frames to draw when preparing the animation so that it will + // be ready to run smoothly. We use 3 frames because we are triple-buffered. + // See code for details. + private static final int DEJANK_FRAMES = 3; + // Set to true when the animation context has been fully prepared. private boolean mPrepared; private int mMode; @@ -145,6 +150,19 @@ final class ElectronBeam { // Done. mPrepared = true; + + // Dejanking optimization. + // Some GL drivers can introduce a lot of lag in the first few frames as they + // initialize their state and allocate graphics buffers for rendering. + // Work around this problem by rendering the first frame of the animation a few + // times. The rest of the animation should run smoothly thereafter. + // The frames we draw here aren't visible because we are essentially just + // painting the screenshot as-is. + if (mode == MODE_COOL_DOWN) { + for (int i = 0; i < DEJANK_FRAMES; i++) { + draw(1.0f); + } + } return true; } From 4fc452795f8f1ad1a9c26720037836e9d5d0db2f Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Wed, 10 Oct 2012 18:28:14 -0700 Subject: [PATCH 3/3] Don't enable input dispatch until the screen is visible. When we defer making the screen visible (waiting for the lock screen to be ready) the screen may actually be on but covered by a black surface. We need to make sure to ignore any touches on the screen during this time until the black surface is about to be removed. Bug: 7318962 Change-Id: I50eb7dcf05295cd276925625240996c4b80c5fe2 --- .../internal/policy/impl/PhoneWindowManager.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 7e047fdfdf961..e8af0a567f4e7 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -3692,11 +3692,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { updateLockScreenTimeout(); } - try { - mWindowManager.setEventDispatching(true); - } catch (RemoteException unhandled) { - } - waitForKeyguard(screenOnListener); } @@ -3747,6 +3742,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenOnFully = true; } + try { + mWindowManager.setEventDispatching(true); + } catch (RemoteException unhandled) { + } + if (screenOnListener != null) { screenOnListener.onScreenOn(); }