Merge "Allow polling for WindowInsets."
diff --git a/api/current.txt b/api/current.txt
index 20ff37a..c075029e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -413,6 +413,7 @@
field public static final int colorActivatedHighlight = 16843664; // 0x1010390
field public static final int colorBackground = 16842801; // 0x1010031
field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
+ field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
field public static final int colorButtonNormal = 16843819; // 0x101042b
field public static final int colorControlActivated = 16843818; // 0x101042a
field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -1723,6 +1724,7 @@
field public static final int primary = 16908300; // 0x102000c
field public static final int progress = 16908301; // 0x102000d
field public static final int redo = 16908339; // 0x1020033
+ field public static final int replaceText = 16908340; // 0x1020034
field public static final int secondaryProgress = 16908303; // 0x102000f
field public static final int selectAll = 16908319; // 0x102001f
field public static final int selectTextMode = 16908333; // 0x102002d
@@ -22310,7 +22312,9 @@
public final class Looper {
method public void dump(android.util.Printer, java.lang.String);
method public static android.os.Looper getMainLooper();
+ method public android.os.MessageQueue getQueue();
method public java.lang.Thread getThread();
+ method public boolean isCurrentThread();
method public static void loop();
method public static android.os.Looper myLooper();
method public static android.os.MessageQueue myQueue();
@@ -22368,7 +22372,18 @@
public final class MessageQueue {
method public void addIdleHandler(android.os.MessageQueue.IdleHandler);
+ method public boolean isIdle();
+ method public void registerFileDescriptorCallback(java.io.FileDescriptor, int, android.os.MessageQueue.FileDescriptorCallback);
method public void removeIdleHandler(android.os.MessageQueue.IdleHandler);
+ method public void unregisterFileDescriptorCallback(java.io.FileDescriptor);
+ }
+
+ public static abstract class MessageQueue.FileDescriptorCallback {
+ ctor public MessageQueue.FileDescriptorCallback();
+ method public int onFileDescriptorEvents(java.io.FileDescriptor, int);
+ field public static final int EVENT_ERROR = 4; // 0x4
+ field public static final int EVENT_INPUT = 1; // 0x1
+ field public static final int EVENT_OUTPUT = 2; // 0x2
}
public static abstract interface MessageQueue.IdleHandler {
@@ -36926,6 +36941,26 @@
method public abstract void onReceivedIcon(java.lang.String, android.graphics.Bitmap);
}
+ public class WebMessage {
+ ctor public WebMessage(java.lang.String);
+ ctor public WebMessage(java.lang.String, android.webkit.WebMessagePort[]);
+ method public java.lang.String getData();
+ method public android.webkit.WebMessagePort[] getPorts();
+ }
+
+ public abstract class WebMessagePort {
+ ctor public WebMessagePort();
+ method public abstract void close();
+ method public abstract void postMessage(android.webkit.WebMessage);
+ method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback);
+ method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback, android.os.Handler);
+ }
+
+ public static abstract class WebMessagePort.WebMessageCallback {
+ ctor public WebMessagePort.WebMessageCallback();
+ method public void onMessage(android.webkit.WebMessagePort, android.webkit.WebMessage);
+ }
+
public abstract class WebResourceError {
ctor public WebResourceError();
method public abstract java.lang.String getDescription();
@@ -37169,6 +37204,7 @@
method public android.webkit.WebBackForwardList copyBackForwardList();
method public deprecated android.print.PrintDocumentAdapter createPrintDocumentAdapter();
method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
+ method public android.webkit.WebMessagePort[] createWebMessageChannel();
method public void destroy();
method public void documentHasImages(android.os.Message);
method public static void enableSlowWholeDocumentDraw();
@@ -37209,6 +37245,7 @@
method public boolean pageDown(boolean);
method public boolean pageUp(boolean);
method public void pauseTimers();
+ method public void postMessageToMainFrame(android.webkit.WebMessage, android.net.Uri);
method public void postUrl(java.lang.String, byte[]);
method public void reload();
method public void removeJavascriptInterface(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 11ae552..eb47c5e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -485,6 +485,7 @@
field public static final int colorActivatedHighlight = 16843664; // 0x1010390
field public static final int colorBackground = 16842801; // 0x1010031
field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
+ field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
field public static final int colorButtonNormal = 16843819; // 0x101042b
field public static final int colorControlActivated = 16843818; // 0x101042a
field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -1799,6 +1800,7 @@
field public static final int primary = 16908300; // 0x102000c
field public static final int progress = 16908301; // 0x102000d
field public static final int redo = 16908339; // 0x1020033
+ field public static final int replaceText = 16908340; // 0x1020034
field public static final int secondaryProgress = 16908303; // 0x102000f
field public static final int selectAll = 16908319; // 0x102001f
field public static final int selectTextMode = 16908333; // 0x102002d
@@ -23905,7 +23907,9 @@
public final class Looper {
method public void dump(android.util.Printer, java.lang.String);
method public static android.os.Looper getMainLooper();
+ method public android.os.MessageQueue getQueue();
method public java.lang.Thread getThread();
+ method public boolean isCurrentThread();
method public static void loop();
method public static android.os.Looper myLooper();
method public static android.os.MessageQueue myQueue();
@@ -23963,7 +23967,18 @@
public final class MessageQueue {
method public void addIdleHandler(android.os.MessageQueue.IdleHandler);
+ method public boolean isIdle();
+ method public void registerFileDescriptorCallback(java.io.FileDescriptor, int, android.os.MessageQueue.FileDescriptorCallback);
method public void removeIdleHandler(android.os.MessageQueue.IdleHandler);
+ method public void unregisterFileDescriptorCallback(java.io.FileDescriptor);
+ }
+
+ public static abstract class MessageQueue.FileDescriptorCallback {
+ ctor public MessageQueue.FileDescriptorCallback();
+ method public int onFileDescriptorEvents(java.io.FileDescriptor, int);
+ field public static final int EVENT_ERROR = 4; // 0x4
+ field public static final int EVENT_INPUT = 1; // 0x1
+ field public static final int EVENT_OUTPUT = 2; // 0x2
}
public static abstract interface MessageQueue.IdleHandler {
@@ -39155,6 +39170,26 @@
method public abstract void onReceivedIcon(java.lang.String, android.graphics.Bitmap);
}
+ public class WebMessage {
+ ctor public WebMessage(java.lang.String);
+ ctor public WebMessage(java.lang.String, android.webkit.WebMessagePort[]);
+ method public java.lang.String getData();
+ method public android.webkit.WebMessagePort[] getPorts();
+ }
+
+ public abstract class WebMessagePort {
+ ctor public WebMessagePort();
+ method public abstract void close();
+ method public abstract void postMessage(android.webkit.WebMessage);
+ method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback);
+ method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback, android.os.Handler);
+ }
+
+ public static abstract class WebMessagePort.WebMessageCallback {
+ ctor public WebMessagePort.WebMessageCallback();
+ method public void onMessage(android.webkit.WebMessagePort, android.webkit.WebMessage);
+ }
+
public abstract class WebResourceError {
ctor public WebResourceError();
method public abstract java.lang.String getDescription();
@@ -39412,6 +39447,7 @@
method public android.webkit.WebBackForwardList copyBackForwardList();
method public deprecated android.print.PrintDocumentAdapter createPrintDocumentAdapter();
method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
+ method public android.webkit.WebMessagePort[] createWebMessageChannel();
method public void destroy();
method public void documentHasImages(android.os.Message);
method public static void enableSlowWholeDocumentDraw();
@@ -39453,6 +39489,7 @@
method public boolean pageDown(boolean);
method public boolean pageUp(boolean);
method public void pauseTimers();
+ method public void postMessageToMainFrame(android.webkit.WebMessage, android.net.Uri);
method public void postUrl(java.lang.String, byte[]);
method public void reload();
method public void removeJavascriptInterface(java.lang.String);
@@ -39665,6 +39702,7 @@
method public abstract void clearView();
method public abstract android.webkit.WebBackForwardList copyBackForwardList();
method public abstract android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
+ method public abstract android.webkit.WebMessagePort[] createWebMessageChannel();
method public abstract void destroy();
method public abstract void documentHasImages(android.os.Message);
method public abstract void dumpViewHierarchyWithProperties(java.io.BufferedWriter, int);
@@ -39711,6 +39749,7 @@
method public abstract boolean pageDown(boolean);
method public abstract boolean pageUp(boolean);
method public abstract void pauseTimers();
+ method public abstract void postMessageToMainFrame(android.webkit.WebMessage, android.net.Uri);
method public abstract void postUrl(java.lang.String, byte[]);
method public abstract void reload();
method public abstract void removeJavascriptInterface(java.lang.String);
diff --git a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
index 0699ffb..e19ebf2 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
@@ -96,15 +96,15 @@
// Blocks until thread is idling
public void waitUntilIdle() {
Handler handler = waitAndGetHandler();
- Looper looper = handler.getLooper();
- if (looper.isIdling()) {
+ MessageQueue queue = handler.getLooper().getQueue();
+ if (queue.isIdle()) {
return;
}
mIdle.close();
- looper.getQueue().addIdleHandler(mIdleHandler);
+ queue.addIdleHandler(mIdleHandler);
// Ensure that the idle handler gets run even if the looper already went idle
handler.sendEmptyMessage(MSG_POKE_IDLE_HANDLER);
- if (looper.isIdling()) {
+ if (queue.isIdle()) {
return;
}
mIdle.block();
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 6d7c9cf..34c880f 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Log;
import android.util.Printer;
@@ -24,10 +26,10 @@
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
- *
+ *
* <p>Most interaction with a message loop is through the
* {@link Handler} class.
- *
+ *
* <p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
@@ -50,6 +52,16 @@
* }</pre>
*/
public final class Looper {
+ /*
+ * API Implementation Note:
+ *
+ * This class contains the code required to set up and manage an event loop
+ * based on MessageQueue. APIs that affect the state of the queue should be
+ * defined on MessageQueue or Handler rather than on Looper itself. For example,
+ * idle handlers and sync barriers are defined on the queue whereas preparing the
+ * thread, looping, and quitting are defined on the looper.
+ */
+
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
@@ -94,7 +106,8 @@
}
}
- /** Returns the application's main looper, which lives in the main thread of the application.
+ /**
+ * Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
@@ -157,29 +170,16 @@
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
- public static Looper myLooper() {
+ public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/**
- * Control logging of messages as they are processed by this Looper. If
- * enabled, a log message will be written to <var>printer</var>
- * at the beginning and ending of each message dispatch, identifying the
- * target Handler and message contents.
- *
- * @param printer A Printer object that will receive log messages, or
- * null to disable message logging.
- */
- public void setMessageLogging(Printer printer) {
- mLogging = printer;
- }
-
- /**
* Return the {@link MessageQueue} object associated with the current
* thread. This must be called from a thread running a Looper, or a
* NullPointerException will be thrown.
*/
- public static MessageQueue myQueue() {
+ public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
@@ -190,13 +190,25 @@
/**
* Returns true if the current thread is this looper's thread.
- * @hide
*/
public boolean isCurrentThread() {
return Thread.currentThread() == mThread;
}
/**
+ * Control logging of messages as they are processed by this Looper. If
+ * enabled, a log message will be written to <var>printer</var>
+ * at the beginning and ending of each message dispatch, identifying the
+ * target Handler and message contents.
+ *
+ * @param printer A Printer object that will receive log messages, or
+ * null to disable message logging.
+ */
+ public void setMessageLogging(@Nullable Printer printer) {
+ mLogging = printer;
+ }
+
+ /**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any
@@ -233,74 +245,35 @@
}
/**
- * Posts a synchronization barrier to the Looper's message queue.
+ * Gets the Thread associated with this Looper.
*
- * Message processing occurs as usual until the message queue encounters the
- * synchronization barrier that has been posted. When the barrier is encountered,
- * later synchronous messages in the queue are stalled (prevented from being executed)
- * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
- * the token that identifies the synchronization barrier.
- *
- * This method is used to immediately postpone execution of all subsequently posted
- * synchronous messages until a condition is met that releases the barrier.
- * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
- * and continue to be processed as usual.
- *
- * This call must be always matched by a call to {@link #removeSyncBarrier} with
- * the same token to ensure that the message queue resumes normal operation.
- * Otherwise the application will probably hang!
- *
- * @return A token that uniquely identifies the barrier. This token must be
- * passed to {@link #removeSyncBarrier} to release the barrier.
- *
- * @hide
+ * @return The looper's thread.
*/
- public int postSyncBarrier() {
- return mQueue.enqueueSyncBarrier(SystemClock.uptimeMillis());
- }
-
-
- /**
- * Removes a synchronization barrier.
- *
- * @param token The synchronization barrier token that was returned by
- * {@link #postSyncBarrier}.
- *
- * @throws IllegalStateException if the barrier was not found.
- *
- * @hide
- */
- public void removeSyncBarrier(int token) {
- mQueue.removeSyncBarrier(token);
- }
-
- /**
- * Return the Thread associated with this Looper.
- */
- public Thread getThread() {
+ public @NonNull Thread getThread() {
return mThread;
}
- /** @hide */
- public MessageQueue getQueue() {
+ /**
+ * Gets this looper's message queue.
+ *
+ * @return The looper's message queue.
+ */
+ public @NonNull MessageQueue getQueue() {
return mQueue;
}
/**
- * Return whether this looper's thread is currently idle, waiting for new work
- * to do. This is intrinsically racy, since its state can change before you get
- * the result back.
- * @hide
+ * Dumps the state of the looper for debugging purposes.
+ *
+ * @param pw A printer to receive the contents of the dump.
+ * @param prefix A prefix to prepend to each line which is printed.
*/
- public boolean isIdling() {
- return mQueue.isIdling();
- }
-
- public void dump(Printer pw, String prefix) {
+ public void dump(@NonNull Printer pw, @NonNull String prefix) {
pw.println(prefix + toString());
mQueue.dump(pw, prefix + " ");
}
+ @Override
public String toString() {
return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
+ ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 01a23ce..7dd4f94 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -16,9 +16,15 @@
package android.os;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.util.Log;
import android.util.Printer;
+import android.util.SparseArray;
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
@@ -30,6 +36,9 @@
* {@link Looper#myQueue() Looper.myQueue()}.
*/
public final class MessageQueue {
+ private static final String TAG = "MessageQueue";
+ private static final boolean DEBUG = false;
+
// True if the message queue can be quit.
private final boolean mQuitAllowed;
@@ -38,6 +47,7 @@
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
+ private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
@@ -50,56 +60,10 @@
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
- private native static void nativePollOnce(long ptr, int timeoutMillis);
+ private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
- private native static boolean nativeIsIdling(long ptr);
-
- /**
- * Callback interface for discovering when a thread is going to block
- * waiting for more messages.
- */
- public static interface IdleHandler {
- /**
- * Called when the message queue has run out of messages and will now
- * wait for more. Return true to keep your idle handler active, false
- * to have it removed. This may be called if there are still messages
- * pending in the queue, but they are all scheduled to be dispatched
- * after the current time.
- */
- boolean queueIdle();
- }
-
- /**
- * Add a new {@link IdleHandler} to this message queue. This may be
- * removed automatically for you by returning false from
- * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
- * invoked, or explicitly removing it with {@link #removeIdleHandler}.
- *
- * <p>This method is safe to call from any thread.
- *
- * @param handler The IdleHandler to be added.
- */
- public void addIdleHandler(IdleHandler handler) {
- if (handler == null) {
- throw new NullPointerException("Can't add a null IdleHandler");
- }
- synchronized (this) {
- mIdleHandlers.add(handler);
- }
- }
-
- /**
- * Remove an {@link IdleHandler} from the queue that was previously added
- * with {@link #addIdleHandler}. If the given object is not currently
- * in the idle list, nothing is done.
- *
- * @param handler The IdleHandler to be removed.
- */
- public void removeIdleHandler(IdleHandler handler) {
- synchronized (this) {
- mIdleHandlers.remove(handler);
- }
- }
+ private native static boolean nativeIsPolling(long ptr);
+ private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
@@ -124,6 +88,222 @@
}
}
+ /**
+ * Returns true if the looper has no pending messages which are due to be processed.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is idle.
+ */
+ public boolean isIdle() {
+ synchronized (this) {
+ final long now = SystemClock.uptimeMillis();
+ return mMessages == null || now < mMessages.when;
+ }
+ }
+
+ /**
+ * Add a new {@link IdleHandler} to this message queue. This may be
+ * removed automatically for you by returning false from
+ * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
+ * invoked, or explicitly removing it with {@link #removeIdleHandler}.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be added.
+ */
+ public void addIdleHandler(@NonNull IdleHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("Can't add a null IdleHandler");
+ }
+ synchronized (this) {
+ mIdleHandlers.add(handler);
+ }
+ }
+
+ /**
+ * Remove an {@link IdleHandler} from the queue that was previously added
+ * with {@link #addIdleHandler}. If the given object is not currently
+ * in the idle list, nothing is done.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be removed.
+ */
+ public void removeIdleHandler(@NonNull IdleHandler handler) {
+ synchronized (this) {
+ mIdleHandlers.remove(handler);
+ }
+ }
+
+ /**
+ * Returns whether this looper's thread is currently polling for more work to do.
+ * This is a good signal that the loop is still alive rather than being stuck
+ * handling a callback. Note that this method is intrinsically racy, since the
+ * state of the loop can change before you get the result back.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is currently polling for events.
+ * @hide
+ */
+ public boolean isPolling() {
+ synchronized (this) {
+ return isPollingLocked();
+ }
+ }
+
+ private boolean isPollingLocked() {
+ // If the loop is quitting then it must not be idling.
+ // We can assume mPtr != 0 when mQuitting is false.
+ return !mQuitting && nativeIsPolling(mPtr);
+ }
+
+ /**
+ * Registers a file descriptor callback to receive notification when file descriptor
+ * related events occur.
+ * <p>
+ * If the file descriptor has already been registered, the specified events
+ * and callback will replace any that were previously associated with it.
+ * It is not possible to set more than one callback per file descriptor.
+ * </p><p>
+ * It is important to always unregister the callback when the file descriptor
+ * is no longer of use.
+ * </p>
+ *
+ * @param fd The file descriptor for which a callback will be registered.
+ * @param events The set of events to receive: a combination of the
+ * {@link FileDescriptorCallback#EVENT_INPUT},
+ * {@link FileDescriptorCallback#EVENT_OUTPUT}, and
+ * {@link FileDescriptorCallback#EVENT_ERROR} event masks. If the requested
+ * set of events is zero, then the callback is unregistered.
+ * @param callback The callback to invoke when file descriptor events occur.
+ *
+ * @see FileDescriptorCallback
+ * @see #unregisterFileDescriptorCallback
+ */
+ public void registerFileDescriptorCallback(@NonNull FileDescriptor fd,
+ @FileDescriptorCallback.Events int events,
+ @NonNull FileDescriptorCallback callback) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+
+ synchronized (this) {
+ setFileDescriptorCallbackLocked(fd, events, callback);
+ }
+ }
+
+ /**
+ * Unregisters a file descriptor callback.
+ * <p>
+ * This method does nothing if no callback has been registered for the
+ * specified file descriptor.
+ * </p>
+ *
+ * @param fd The file descriptor whose callback will be unregistered.
+ *
+ * @see FileDescriptorCallback
+ * @see #registerFileDescriptorCallback
+ */
+ public void unregisterFileDescriptorCallback(@NonNull FileDescriptor fd) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+
+ synchronized (this) {
+ setFileDescriptorCallbackLocked(fd, 0, null);
+ }
+ }
+
+ private void setFileDescriptorCallbackLocked(FileDescriptor fd, int events,
+ FileDescriptorCallback callback) {
+ final int fdNum = fd.getInt$();
+
+ int index = -1;
+ FileDescriptorRecord record = null;
+ if (mFileDescriptorRecords != null) {
+ index = mFileDescriptorRecords.indexOfKey(fdNum);
+ if (index >= 0) {
+ record = mFileDescriptorRecords.valueAt(index);
+ if (record != null && record.mEvents == events) {
+ return;
+ }
+ }
+ }
+
+ if (events != 0) {
+ events |= FileDescriptorCallback.EVENT_ERROR;
+ if (record == null) {
+ if (mFileDescriptorRecords == null) {
+ mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
+ }
+ record = new FileDescriptorRecord(fd, events, callback);
+ mFileDescriptorRecords.put(fdNum, record);
+ } else {
+ record.mCallback = callback;
+ record.mEvents = events;
+ record.mSeq += 1;
+ }
+ nativeSetFileDescriptorEvents(mPtr, fdNum, events);
+ } else if (record != null) {
+ record.mEvents = 0;
+ mFileDescriptorRecords.removeAt(index);
+ }
+ }
+
+ // Called from native code.
+ private int dispatchEvents(int fd, int events) {
+ // Get the file descriptor record and any state that might change.
+ final FileDescriptorRecord record;
+ final int oldWatchedEvents;
+ final FileDescriptorCallback callback;
+ final int seq;
+ synchronized (this) {
+ record = mFileDescriptorRecords.get(fd);
+ if (record == null) {
+ return 0; // spurious, no callback registered
+ }
+
+ oldWatchedEvents = record.mEvents;
+ events &= oldWatchedEvents; // filter events based on current watched set
+ if (events == 0) {
+ return oldWatchedEvents; // spurious, watched events changed
+ }
+
+ callback = record.mCallback;
+ seq = record.mSeq;
+ }
+
+ // Invoke the callback outside of the lock.
+ int newWatchedEvents = callback.onFileDescriptorEvents(
+ record.mDescriptor, events);
+ if (newWatchedEvents != 0) {
+ newWatchedEvents |= FileDescriptorCallback.EVENT_ERROR;
+ }
+
+ // Update the file descriptor record if the callback changed the set of
+ // events to watch and the callback itself hasn't been updated since.
+ if (newWatchedEvents != oldWatchedEvents) {
+ synchronized (this) {
+ int index = mFileDescriptorRecords.indexOfKey(fd);
+ if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+ && record.mSeq == seq) {
+ record.mEvents = newWatchedEvents;
+ if (newWatchedEvents == 0) {
+ mFileDescriptorRecords.removeAt(index);
+ }
+ }
+ }
+ }
+
+ // Return the new set of events to watch for native code to take care of.
+ return newWatchedEvents;
+ }
+
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
@@ -167,7 +347,8 @@
mMessages = msg.next;
}
msg.next = null;
- if (false) Log.v("MessageQueue", "Returning message: " + msg);
+ if (DEBUG) Log.v(TAG, "Returning message: " + msg);
+ msg.markInUse();
return msg;
}
} else {
@@ -210,7 +391,7 @@
try {
keep = idler.queueIdle();
} catch (Throwable t) {
- Log.wtf("MessageQueue", "IdleHandler threw exception", t);
+ Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
@@ -251,7 +432,34 @@
}
}
- int enqueueSyncBarrier(long when) {
+ /**
+ * Posts a synchronization barrier to the Looper's message queue.
+ *
+ * Message processing occurs as usual until the message queue encounters the
+ * synchronization barrier that has been posted. When the barrier is encountered,
+ * later synchronous messages in the queue are stalled (prevented from being executed)
+ * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
+ * the token that identifies the synchronization barrier.
+ *
+ * This method is used to immediately postpone execution of all subsequently posted
+ * synchronous messages until a condition is met that releases the barrier.
+ * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
+ * and continue to be processed as usual.
+ *
+ * This call must be always matched by a call to {@link #removeSyncBarrier} with
+ * the same token to ensure that the message queue resumes normal operation.
+ * Otherwise the application will probably hang!
+ *
+ * @return A token that uniquely identifies the barrier. This token must be
+ * passed to {@link #removeSyncBarrier} to release the barrier.
+ *
+ * @hide
+ */
+ public int postSyncBarrier() {
+ return postSyncBarrier(SystemClock.uptimeMillis());
+ }
+
+ private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
@@ -280,7 +488,17 @@
}
}
- void removeSyncBarrier(int token) {
+ /**
+ * Removes a synchronization barrier.
+ *
+ * @param token The synchronization barrier token that was returned by
+ * {@link #postSyncBarrier}.
+ *
+ * @throws IllegalStateException if the barrier was not found.
+ *
+ * @hide
+ */
+ public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
@@ -324,7 +542,7 @@
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
- Log.w("MessageQueue", e.getMessage(), e);
+ Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
@@ -400,18 +618,6 @@
}
}
- boolean isIdling() {
- synchronized (this) {
- return isIdlingLocked();
- }
- }
-
- private boolean isIdlingLocked() {
- // If the loop is quitting then it must not be idling.
- // We can assume mPtr != 0 when mQuitting is false.
- return !mQuitting && nativeIsIdling(mPtr);
- }
-
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
@@ -559,8 +765,113 @@
pw.println(prefix + "Message " + n + ": " + msg.toString(now));
n++;
}
- pw.println(prefix + "(Total messages: " + n + ", idling=" + isIdlingLocked()
+ pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
+ ", quitting=" + mQuitting + ")");
}
}
+
+ /**
+ * Callback interface for discovering when a thread is going to block
+ * waiting for more messages.
+ */
+ public static interface IdleHandler {
+ /**
+ * Called when the message queue has run out of messages and will now
+ * wait for more. Return true to keep your idle handler active, false
+ * to have it removed. This may be called if there are still messages
+ * pending in the queue, but they are all scheduled to be dispatched
+ * after the current time.
+ */
+ boolean queueIdle();
+ }
+
+ /**
+ * A callback which is invoked when file descriptor related events occur.
+ */
+ public static abstract class FileDescriptorCallback {
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for input
+ * operations, such as reading.
+ * <p>
+ * The callback should read all available data from the file descriptor
+ * then return <code>true</code> to keep the callback active or <code>false</code>
+ * to remove the callback.
+ * </p><p>
+ * In the case of a socket, this event may be generated to indicate
+ * that there is at least one incoming connection that the callback
+ * should accept.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_INPUT} event mask was
+ * specified when the callback was added.
+ * </p>
+ */
+ public static final int EVENT_INPUT = 1 << 0;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for output
+ * operations, such as writing.
+ * <p>
+ * The callback should write as much data as it needs. If it could not
+ * write everything at once, then it should return <code>true</code> to
+ * keep the callback active. Otherwise, it should return <code>false</code>
+ * to remove the callback then re-register it later when it needs to write
+ * something else.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
+ * specified when the callback was added.
+ * </p>
+ */
+ public static final int EVENT_OUTPUT = 1 << 1;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor encountered a
+ * fatal error.
+ * <p>
+ * File descriptor errors can occur for various reasons. One common error
+ * is when the remote peer of a socket or pipe closes its end of the connection.
+ * </p><p>
+ * This event may be generated at any time regardless of whether the
+ * {@link #EVENT_ERROR} event mask was specified when the callback was added.
+ * </p>
+ */
+ public static final int EVENT_ERROR = 1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag=true, value={EVENT_INPUT, EVENT_OUTPUT, EVENT_ERROR})
+ public @interface Events {}
+
+ /**
+ * Called when a file descriptor receives events.
+ * <p>
+ * The default implementation does nothing and returns 0 to unregister the callback.
+ * </p>
+ *
+ * @param fd The file descriptor.
+ * @param events The set of events that occurred: a combination of the
+ * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
+ * @return The new set of events to watch, or 0 to unregister the callback.
+ *
+ * @see #EVENT_INPUT
+ * @see #EVENT_OUTPUT
+ * @see #EVENT_ERROR
+ */
+ public @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events) {
+ return 0;
+ }
+ }
+
+ private static final class FileDescriptorRecord {
+ public final FileDescriptor mDescriptor;
+ public int mEvents;
+ public FileDescriptorCallback mCallback;
+ public int mSeq;
+
+ public FileDescriptorRecord(FileDescriptor descriptor,
+ int events, FileDescriptorCallback callback) {
+ mDescriptor = descriptor;
+ mEvents = events;
+ mCallback = callback;
+ }
+ }
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 4e8ec890..ba1699e 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -19,11 +19,13 @@
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.SEEK_SET;
import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.SOCK_SEQPACKET;
import static android.system.OsConstants.S_ISLNK;
import static android.system.OsConstants.S_ISREG;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
+import android.os.MessageQueue.FileDescriptorCallback;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -31,7 +33,6 @@
import android.util.Log;
import dalvik.system.CloseGuard;
-
import libcore.io.IoUtils;
import libcore.io.Memory;
@@ -220,8 +221,8 @@
* be opened with the requested mode.
* @see #parseMode(String)
*/
- public static ParcelFileDescriptor open(
- File file, int mode, Handler handler, OnCloseListener listener) throws IOException {
+ public static ParcelFileDescriptor open(File file, int mode, Handler handler,
+ final OnCloseListener listener) throws IOException {
if (handler == null) {
throw new IllegalArgumentException("Handler must not be null");
}
@@ -235,10 +236,25 @@
final FileDescriptor[] comm = createCommSocketPair();
final ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd, comm[0]);
- // Kick off thread to watch for status updates
- IoUtils.setBlocking(comm[1], true);
- final ListenerBridge bridge = new ListenerBridge(comm[1], handler.getLooper(), listener);
- bridge.start();
+ handler.getLooper().getQueue().registerFileDescriptorCallback(comm[1],
+ FileDescriptorCallback.EVENT_INPUT, new FileDescriptorCallback() {
+ @Override
+ public int onFileDescriptorEvents(FileDescriptor fd, int events) {
+ Status status = null;
+ if ((events & FileDescriptorCallback.EVENT_INPUT) != 0) {
+ final byte[] buf = new byte[MAX_STATUS];
+ status = readCommStatus(fd, buf);
+ } else if ((events & FileDescriptorCallback.EVENT_ERROR) != 0) {
+ status = new Status(Status.DEAD);
+ }
+ if (status != null) {
+ IoUtils.closeQuietly(fd);
+ listener.onClose(status.asIOException());
+ return 0; // unregister the callback
+ }
+ return EVENT_INPUT;
+ }
+ });
return pfd;
}
@@ -446,9 +462,12 @@
private static FileDescriptor[] createCommSocketPair() throws IOException {
try {
+ // Use SOCK_SEQPACKET so that we have a guarantee that the status
+ // is written and read atomically as one unit and is not split
+ // across multiple IO operations.
final FileDescriptor comm1 = new FileDescriptor();
final FileDescriptor comm2 = new FileDescriptor();
- Os.socketpair(AF_UNIX, SOCK_STREAM, 0, comm1, comm2);
+ Os.socketpair(AF_UNIX, SOCK_SEQPACKET, 0, comm1, comm2);
IoUtils.setBlocking(comm1, false);
IoUtils.setBlocking(comm2, false);
return new FileDescriptor[] { comm1, comm2 };
@@ -709,6 +728,7 @@
writePtr += len;
}
+ // Must write the entire status as a single operation.
Os.write(mCommFd, buf, 0, writePtr);
} catch (ErrnoException e) {
// Reporting status is best-effort
@@ -726,6 +746,7 @@
private static Status readCommStatus(FileDescriptor comm, byte[] buf) {
try {
+ // Must read the entire status as a single operation.
final int n = Os.read(comm, buf, 0, buf.length);
if (n == 0) {
// EOF means they're dead
@@ -1014,39 +1035,10 @@
return new IOException("Unknown status: " + status);
}
}
- }
-
- /**
- * Bridge to watch for remote status, and deliver to listener. Currently
- * requires that communication socket is <em>blocking</em>.
- */
- private static final class ListenerBridge extends Thread {
- // TODO: switch to using Looper to avoid burning a thread
-
- private FileDescriptor mCommFd;
- private final Handler mHandler;
-
- public ListenerBridge(FileDescriptor comm, Looper looper, final OnCloseListener listener) {
- mCommFd = comm;
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- final Status s = (Status) msg.obj;
- listener.onClose(s != null ? s.asIOException() : null);
- }
- };
- }
@Override
- public void run() {
- try {
- final byte[] buf = new byte[MAX_STATUS];
- final Status status = readCommStatus(mCommFd, buf);
- mHandler.obtainMessage(0, status).sendToTarget();
- } finally {
- IoUtils.closeQuietly(mCommFd);
- mCommFd = null;
- }
+ public String toString() {
+ return "{" + status + ": " + msg + "}";
}
}
}
diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java
index 731709b..6375668 100644
--- a/core/java/android/service/fingerprint/FingerprintManager.java
+++ b/core/java/android/service/fingerprint/FingerprintManager.java
@@ -98,8 +98,8 @@
};
public static final class FingerprintItem {
- CharSequence name;
- int id;
+ public CharSequence name;
+ public int id;
FingerprintItem(CharSequence name, int id) {
this.name = name;
this.id = id;
@@ -291,4 +291,23 @@
}
return false;
}
+
+ /**
+ * Renames the given fingerprint template
+ * @param fpId the fingerprint id
+ * @param newName the new name
+ * @hide
+ */
+ public void rename(int fpId, String newName) {
+ // Renames the given fpId
+ if (mService != null) {
+ try {
+ mService.rename(fpId, newName);
+ } catch (RemoteException e) {
+ Log.v(TAG, "Remote exception in rename(): ", e);
+ }
+ } else {
+ Log.w(TAG, "rename(): Service not connected!");
+ }
+ }
}
\ No newline at end of file
diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl
index a7d4090..9b4750b 100644
--- a/core/java/android/service/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/service/fingerprint/IFingerprintService.aidl
@@ -41,4 +41,7 @@
// Determine if HAL is loaded and ready
boolean isHardwareDetected();
+
+ // Rename the given fingerprint id
+ void rename(int fpId, String name);
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 967e80c..0d35f9c 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -540,7 +540,7 @@
while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
int endPos = paraStart + breaks[breakIndex];
- boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this?
+ boolean moreChars = (endPos < bufEnd);
v = out(source, here, endPos,
fmAscent, fmDescent, fmTop, fmBottom,
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 69b4c47..51fefe9 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -102,7 +102,6 @@
private final float mLightRadius;
private final int mAmbientShadowAlpha;
private final int mSpotShadowAlpha;
- private final float mDensity;
private long mNativeProxy;
private boolean mInitialized = false;
@@ -119,7 +118,6 @@
(int) (255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0) + 0.5f);
mSpotShadowAlpha = (int) (255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0) + 0.5f);
a.recycle();
- mDensity = context.getResources().getDisplayMetrics().density;
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
@@ -128,10 +126,6 @@
AtlasInitializer.sInstance.init(context, mNativeProxy);
- // Setup timing
- mChoreographer = Choreographer.getInstance();
- nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos());
-
loadSystemProperties();
}
@@ -224,7 +218,7 @@
mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight,
lightX, mLightY, mLightZ, mLightRadius,
- mAmbientShadowAlpha, mSpotShadowAlpha, mDensity);
+ mAmbientShadowAlpha, mSpotShadowAlpha);
}
@Override
@@ -379,6 +373,7 @@
@Override
void setName(String name) {
+ nSetName(mNativeProxy, name);
}
@Override
@@ -487,15 +482,15 @@
private static native long nCreateProxy(boolean translucent, long rootRenderNode);
private static native void nDeleteProxy(long nativeProxy);
- private static native void nSetFrameInterval(long nativeProxy, long frameIntervalNanos);
private static native boolean nLoadSystemProperties(long nativeProxy);
+ private static native void nSetName(long nativeProxy, String name);
private static native boolean nInitialize(long nativeProxy, Surface window);
private static native void nUpdateSurface(long nativeProxy, Surface window);
private static native boolean nPauseSurface(long nativeProxy, Surface window);
private static native void nSetup(long nativeProxy, int width, int height,
float lightX, float lightY, float lightZ, float lightRadius,
- int ambientShadowAlpha, int spotShadowAlpha, float density);
+ int ambientShadowAlpha, int spotShadowAlpha);
private static native void nSetOpaque(long nativeProxy, boolean opaque);
private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
private static native void nDestroy(long nativeProxy);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c6f5d8a..f970e88 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1056,7 +1056,7 @@
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
- mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
+ mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
@@ -1070,7 +1070,7 @@
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
- mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
+ mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
@@ -1079,7 +1079,7 @@
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
- mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
+ mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
diff --git a/core/java/android/webkit/WebMessage.java b/core/java/android/webkit/WebMessage.java
new file mode 100644
index 0000000..7683a40
--- /dev/null
+++ b/core/java/android/webkit/WebMessage.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 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.webkit;
+
+/**
+ * The Java representation of the HTML5 PostMessage event. See
+ * https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces
+ * for definition of a MessageEvent in HTML5.
+ *
+ */
+public class WebMessage {
+
+ private String mData;
+ private WebMessagePort[] mPorts;
+
+ /**
+ * Creates a WebMessage.
+ * @param data the data of the message.
+ */
+ public WebMessage(String data) {
+ mData = data;
+ }
+
+ /**
+ * Creates a WebMessage.
+ * @param data the data of the message.
+ * @param ports the ports that are sent with the message.
+ */
+ public WebMessage(String data, WebMessagePort[] ports) {
+ mData = data;
+ mPorts = ports;
+ }
+
+ /**
+ * Returns the data of the message.
+ */
+ public String getData() {
+ return mData;
+ }
+
+ /**
+ * Returns the ports that are sent with the message, or null if no port
+ * is sent.
+ */
+ public WebMessagePort[] getPorts() {
+ return mPorts;
+ }
+}
diff --git a/core/java/android/webkit/WebMessagePort.java b/core/java/android/webkit/WebMessagePort.java
new file mode 100644
index 0000000..eab27bd
--- /dev/null
+++ b/core/java/android/webkit/WebMessagePort.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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.webkit;
+
+import android.os.Handler;
+
+/**
+ * The Java representation of the HTML5 Message Port. See
+ * https://html.spec.whatwg.org/multipage/comms.html#messageport
+ * for definition of MessagePort in HTML5.
+ *
+ * A Message port represents one endpoint of a Message Channel. In Android
+ * webview, there is no separate Message Channel object. When a message channel
+ * is created, both ports are tangled to each other and started, and then
+ * returned in a MessagePort array, see {@link WebView#createWebMessageChannel}
+ * for creating a message channel.
+ *
+ * When a message port is first created or received via transfer, it does not
+ * have a WebMessageCallback to receive web messages. The messages are queued until
+ * a WebMessageCallback is set.
+ */
+public abstract class WebMessagePort {
+
+ /**
+ * The listener for handling MessagePort events. The message callback
+ * methods are called on the main thread. If the embedder application
+ * wants to receive the messages on a different thread, it can do this
+ * by passing a Handler in
+ * {@link WebMessagePort#setWebMessageCallback(WebMessageCallback, Handler)}.
+ * In the latter case, the application should be extra careful for thread safety
+ * since WebMessagePort methods should be called on main thread.
+ */
+ public static abstract class WebMessageCallback {
+ /**
+ * Message callback for receiving onMessage events.
+ *
+ * @param port the WebMessagePort that the message is destined for
+ * @param message the message from the entangled port.
+ */
+ public void onMessage(WebMessagePort port, WebMessage message) { }
+ }
+
+ /**
+ * Post a WebMessage to the entangled port.
+ *
+ * @param message the message from Java to JS.
+ *
+ * @throws IllegalStateException If message port is already transferred or closed.
+ */
+ public abstract void postMessage(WebMessage message);
+
+ /**
+ * Close the message port and free any resources associated with it.
+ */
+ public abstract void close();
+
+ /**
+ * Sets a callback to receive message events on the main thread.
+ *
+ * @param callback the message callback.
+ */
+ public abstract void setWebMessageCallback(WebMessageCallback callback);
+
+ /**
+ * Sets a callback to receive message events on the handler that is provided
+ * by the application.
+ *
+ * @param callback the message callback.
+ * @param handler the handler to receive the message messages.
+ */
+ public abstract void setWebMessageCallback(WebMessageCallback callback, Handler handler);
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 01a506c..67ad642 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -27,6 +27,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
@@ -1825,6 +1826,37 @@
}
/**
+ * Creates a message channel to communicate with JS and returns the message
+ * ports that represent the endpoints of this message channel. The HTML5 message
+ * channel functionality is described here:
+ * https://html.spec.whatwg.org/multipage/comms.html#messagechannel
+ *
+ * The returned message channels are entangled and already in started state.
+ *
+ * @return the two message ports that form the message channel.
+ */
+ public WebMessagePort[] createWebMessageChannel() {
+ checkThread();
+ if (TRACE) Log.d(LOGTAG, "createWebMessageChannel");
+ return mProvider.createWebMessageChannel();
+ }
+
+ /**
+ * Post a message to main frame. The embedded application can restrict the
+ * messages to a certain target origin. See
+ * https://html.spec.whatwg.org/multipage/comms.html#posting-messages
+ * for how target origin can be used.
+ *
+ * @param message the WebMessage
+ * @param targetOrigin the target origin.
+ */
+ public void postMessageToMainFrame(WebMessage message, Uri targetOrigin) {
+ checkThread();
+ if (TRACE) Log.d(LOGTAG, "postMessageToMainFrame. TargetOrigin=" + targetOrigin);
+ mProvider.postMessageToMainFrame(message, targetOrigin);
+ }
+
+ /**
* Gets the WebSettings object used to control the settings for this
* WebView.
*
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 379a732..0cdb875 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -25,6 +25,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.print.PrintDocumentAdapter;
@@ -227,6 +228,10 @@
public void removeJavascriptInterface(String interfaceName);
+ public WebMessagePort[] createWebMessageChannel();
+
+ public void postMessageToMainFrame(WebMessage message, Uri targetOrigin);
+
public WebSettings getSettings();
public void setMapTrackballToArrowKeys(boolean setMap);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index cc44577..81d9d56 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2969,6 +2969,12 @@
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
+ if (mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) {
+ menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
+ setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+
styledAttributes.recycle();
if (mCustomSelectionActionModeCallback != null) {
@@ -3000,6 +3006,10 @@
mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
return true;
}
+ if (item.getItemId() == TextView.ID_REPLACE) {
+ onReplace();
+ return true;
+ }
return mTextView.onTextContextMenuItem(item.getItemId());
}
@@ -3028,6 +3038,13 @@
}
}
+ private void onReplace() {
+ int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
+ stopSelectionActionMode();
+ Selection.setSelection((Spannable) mTextView.getText(), middle);
+ showSuggestions();
+ }
+
private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
private static final int POPUP_TEXT_LAYOUT =
com.android.internal.R.layout.text_edit_action_popup_text;
@@ -3086,10 +3103,7 @@
mTextView.onTextContextMenuItem(TextView.ID_PASTE);
hide();
} else if (view == mReplaceTextView) {
- int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
- stopSelectionActionMode();
- Selection.setSelection((Spannable) mTextView.getText(), middle);
- showSuggestions();
+ onReplace();
}
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 399f4c5..f676254 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -29,6 +29,8 @@
import android.os.IBinder;
import android.transition.Transition;
import android.transition.Transition.EpicenterCallback;
+import android.transition.Transition.TransitionListener;
+import android.transition.Transition.TransitionListenerAdapter;
import android.transition.TransitionInflater;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
@@ -39,12 +41,13 @@
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.WindowManager;
import java.lang.ref.WeakReference;
-import java.util.List;
/**
* <p>A popup window that can be used to display an arbitrary view. The popup
@@ -96,14 +99,12 @@
private WindowManager mWindowManager;
private boolean mIsShowing;
+ private boolean mIsTransitioningToDismiss;
private boolean mIsDropdown;
/** View that handles event dispatch and content transitions. */
private PopupDecorView mDecorView;
- /** View that holds the popup background. May be the content view. */
- private View mBackgroundView;
-
/** The contents of the popup. */
private View mContentView;
@@ -1183,23 +1184,30 @@
+ "calling setContentView() before attempting to show the popup.");
}
- // When a background is available, we embed the content view within
- // another view that owns the background drawable.
- if (mBackground != null) {
- mBackgroundView = createBackgroundView(mContentView);
- mBackgroundView.setBackground(mBackground);
- } else {
- mBackgroundView = mContentView;
+ // The old decor view may be transitioning out. Make sure it finishes
+ // and cleans up before we try to create another one.
+ if (mDecorView != null) {
+ mDecorView.cancelTransitions();
}
- mDecorView = createDecorView(mBackgroundView);
+ // When a background is available, we embed the content view within
+ // another view that owns the background drawable.
+ final View backgroundView;
+ if (mBackground != null) {
+ backgroundView = createBackgroundView(mContentView);
+ backgroundView.setBackground(mBackground);
+ } else {
+ backgroundView = mContentView;
+ }
+
+ mDecorView = createDecorView(backgroundView);
// The background owner should be elevated so that it casts a shadow.
- mBackgroundView.setElevation(mElevation);
+ backgroundView.setElevation(mElevation);
// We may wrap that in another view, so we'll need to manually specify
// the surface insets.
- final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
+ final int surfaceInset = (int) Math.ceil(backgroundView.getZ() * 2);
p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
p.hasManualSurfaceInsets = true;
@@ -1268,26 +1276,13 @@
p.packageName = mContext.getPackageName();
}
- final View rootView = mContentView.getRootView();
- rootView.setFitsSystemWindows(mLayoutInsetDecor);
+ final PopupDecorView decorView = mDecorView;
+ decorView.setFitsSystemWindows(mLayoutInsetDecor);
+ decorView.requestEnterTransition(mEnterTransition);
+
setLayoutDirectionFromAnchor();
- mWindowManager.addView(rootView, p);
-
- // Postpone enter transition until the scene root has been laid out.
- if (mEnterTransition != null) {
- mEnterTransition.addTarget(mBackgroundView);
- mEnterTransition.addListener(new Transition.TransitionListenerAdapter() {
- @Override
- public void onTransitionEnd(Transition transition) {
- transition.removeListener(this);
- transition.removeTarget(mBackgroundView);
- }
- });
-
- mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(
- new PostLayoutTransitionListener(mDecorView, mEnterTransition));
- }
+ mWindowManager.addView(decorView, p);
}
private void setLayoutDirectionFromAnchor() {
@@ -1591,35 +1586,38 @@
* @see #showAsDropDown(android.view.View)
*/
public void dismiss() {
- if (!isShowing()) {
+ if (!isShowing() || mIsTransitioningToDismiss) {
return;
}
+ final PopupDecorView decorView = mDecorView;
+ final View contentView = mContentView;
+
+ final ViewGroup contentHolder;
+ final ViewParent contentParent = contentView.getParent();
+ if (contentParent instanceof ViewGroup) {
+ contentHolder = ((ViewGroup) contentParent);
+ } else {
+ contentHolder = null;
+ }
+
+ // Ensure any ongoing or pending transitions are canceled.
+ decorView.cancelTransitions();
+
unregisterForScrollChanged();
mIsShowing = false;
+ mIsTransitioningToDismiss = true;
- if (mExitTransition != null) {
- // Cache the content view, since it may change without notice.
- final View contentView = mContentView;
-
- mExitTransition.addTarget(mBackgroundView);
- mExitTransition.addListener(new Transition.TransitionListenerAdapter() {
+ if (mExitTransition != null && decorView.isLaidOut()) {
+ decorView.startExitTransition(mExitTransition, new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
- transition.removeListener(this);
- transition.removeTarget(mBackgroundView);
-
- dismissImmediate(contentView);
+ dismissImmediate(decorView, contentHolder, contentView);
}
});
-
- TransitionManager.beginDelayedTransition(mDecorView, mExitTransition);
-
- // Transition to invisible.
- mBackgroundView.setVisibility(View.INVISIBLE);
} else {
- dismissImmediate(mContentView);
+ dismissImmediate(decorView, contentHolder, contentView);
}
if (mOnDismissListener != null) {
@@ -1631,24 +1629,22 @@
* Removes the popup from the window manager and tears down the supporting
* view hierarchy, if necessary.
*/
- private void dismissImmediate(View contentView) {
- if (mDecorView == null || mBackgroundView == null) {
- throw new RuntimeException("Popup window already dismissed");
+ private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
+ // If this method gets called and the decor view doesn't have a parent,
+ // then it was either never added or was already removed. That should
+ // never happen, but it's worth checking to avoid potential crashes.
+ if (decorView.getParent() != null) {
+ mWindowManager.removeViewImmediate(decorView);
}
- try {
- if (mDecorView.isAttachedToWindow()) {
- mWindowManager.removeViewImmediate(mDecorView);
- }
- } finally {
- mDecorView.removeView(mBackgroundView);
- mDecorView = null;
-
- if (mBackgroundView != contentView) {
- ((ViewGroup) mBackgroundView).removeView(contentView);
- }
- mBackgroundView = null;
+ if (contentHolder != null) {
+ contentHolder.removeView(contentView);
}
+
+ // This needs to stay until after all transitions have ended since we
+ // need the reference to cancel transitions in preparePopup().
+ mDecorView = null;
+ mIsTransitioningToDismiss = false;
}
/**
@@ -1909,47 +1905,9 @@
mAnchoredGravity = gravity;
}
- /**
- * Layout listener used to run a transition immediately after a view is
- * laid out. Forces the view to transition from invisible to visible.
- */
- private static class PostLayoutTransitionListener implements
- ViewTreeObserver.OnGlobalLayoutListener {
- private final ViewGroup mSceneRoot;
- private final Transition mTransition;
-
- public PostLayoutTransitionListener(ViewGroup sceneRoot, Transition transition) {
- mSceneRoot = sceneRoot;
- mTransition = transition;
- }
-
- @Override
- public void onGlobalLayout() {
- final ViewTreeObserver observer = mSceneRoot.getViewTreeObserver();
- if (observer == null) {
- // View has been detached.
- return;
- }
-
- observer.removeOnGlobalLayoutListener(this);
-
- // Set all targets to be initially invisible.
- final List<View> targets = mTransition.getTargets();
- final int N = targets.size();
- for (int i = 0; i < N; i++) {
- targets.get(i).setVisibility(View.INVISIBLE);
- }
-
- TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);
-
- // Transition targets to visible.
- for (int i = 0; i < N; i++) {
- targets.get(i).setVisibility(View.VISIBLE);
- }
- }
- }
-
private class PopupDecorView extends FrameLayout {
+ private TransitionListenerAdapter mPendingExitListener;
+
public PopupDecorView(Context context) {
super(context);
}
@@ -2004,6 +1962,100 @@
return super.onTouchEvent(event);
}
}
+
+ /**
+ * Requests that an enter transition run after the next layout pass.
+ */
+ public void requestEnterTransition(Transition transition) {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null && transition != null) {
+ final Transition enterTransition = transition.clone();
+
+ // Postpone the enter transition after the first layout pass.
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null) {
+ observer.removeOnGlobalLayoutListener(this);
+ }
+
+ startEnterTransition(enterTransition);
+ }
+ });
+ }
+ }
+
+ /**
+ * Starts the pending enter transition, if one is set.
+ */
+ private void startEnterTransition(Transition enterTransition) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ enterTransition.addTarget(child);
+ child.setVisibility(View.INVISIBLE);
+ }
+
+ TransitionManager.beginDelayedTransition(this, enterTransition);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Starts an exit transition immediately.
+ * <p>
+ * <strong>Note:</strong> The transition listener is guaranteed to have
+ * its {@code onTransitionEnd} method called even if the transition
+ * never starts; however, it may be called with a {@code null} argument.
+ */
+ public void startExitTransition(Transition transition, final TransitionListener listener) {
+ if (transition == null) {
+ return;
+ }
+
+ // The exit listener MUST be called for cleanup, even if the
+ // transition never starts or ends. Stash it for later.
+ mPendingExitListener = new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ listener.onTransitionEnd(transition);
+
+ // The listener was called. Our job here is done.
+ mPendingExitListener = null;
+ }
+ };
+
+ final Transition exitTransition = transition.clone();
+ exitTransition.addListener(mPendingExitListener);
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ exitTransition.addTarget(child);
+ }
+
+ TransitionManager.beginDelayedTransition(this, exitTransition);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ /**
+ * Cancels all pending or current transitions.
+ */
+ public void cancelTransitions() {
+ TransitionManager.endTransitions(this);
+
+ if (mPendingExitListener != null) {
+ mPendingExitListener.onTransitionEnd(null);
+ }
+ }
}
private class PopupBackgroundView extends FrameLayout {
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 3592687..e7031fe 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -727,14 +727,10 @@
}
}
- if (scheduleOtherSpellCheck && wordStart <= end) {
+ if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
// Update range span: start new spell check from last wordStart
setRangeSpan(editable, wordStart, end);
} else {
- if (DBG && scheduleOtherSpellCheck) {
- Log.w(TAG, "Trying to schedule spellcheck for invalid region, from "
- + wordStart + " to " + end);
- }
removeRangeSpan(editable);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index faf1c40..628833d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8818,6 +8818,7 @@
static final int ID_COPY = android.R.id.copy;
static final int ID_PASTE = android.R.id.paste;
static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
+ static final int ID_REPLACE = android.R.id.replaceText;
/**
* Called when a context menu option for the text view is selected. Currently
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 2b20b38..7d45071 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -43,8 +43,6 @@
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
View.OnAttachStateChangeListener, MenuPresenter {
- private static final String TAG = "MenuPopupHelper";
-
static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
private final Context mContext;
@@ -132,7 +130,18 @@
return mPopup;
}
+ /**
+ * Attempts to show the popup anchored to the view specified by
+ * {@link #setAnchorView(View)}.
+ *
+ * @return {@code true} if the popup was shown or was already showing prior
+ * to calling this method, {@code false} otherwise
+ */
public boolean tryShow() {
+ if (isShowing()) {
+ return true;
+ }
+
mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
mPopup.setOnDismissListener(this);
mPopup.setOnItemClickListener(this);
@@ -169,6 +178,7 @@
}
}
+ @Override
public void onDismiss() {
mPopup = null;
mMenu.close();
@@ -190,6 +200,7 @@
adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
}
+ @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
dismiss();
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index a10cfca..7a934bd 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -556,24 +556,33 @@
return NULL;
}
- SkBitmap* bitmap = new SkBitmap;
+ std::unique_ptr<SkBitmap> bitmap(new SkBitmap);
- bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes);
+ if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes)) {
+ return NULL;
+ }
SkColorTable* ctable = NULL;
if (colorType == kIndex_8_SkColorType) {
int count = p->readInt32();
+ if (count < 0 || count > 256) {
+ // The data is corrupt, since SkColorTable enforces a value between 0 and 256,
+ // inclusive.
+ return NULL;
+ }
if (count > 0) {
size_t size = count * sizeof(SkPMColor);
const SkPMColor* src = (const SkPMColor*)p->readInplace(size);
+ if (src == NULL) {
+ return NULL;
+ }
ctable = new SkColorTable(src, count);
}
}
- jbyteArray buffer = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable);
+ jbyteArray buffer = GraphicsJNI::allocateJavaPixelRef(env, bitmap.get(), ctable);
if (NULL == buffer) {
SkSafeUnref(ctable);
- delete bitmap;
return NULL;
}
@@ -585,7 +594,6 @@
android::status_t status = p->readBlob(size, &blob);
if (status) {
doThrowRE(env, "Could not read bitmap from parcel blob.");
- delete bitmap;
return NULL;
}
@@ -595,8 +603,8 @@
blob.release();
- return GraphicsJNI::createBitmap(env, bitmap, buffer, getPremulBitmapCreateFlags(isMutable),
- NULL, NULL, density);
+ return GraphicsJNI::createBitmap(env, bitmap.release(), buffer,
+ getPremulBitmapCreateFlags(isMutable), NULL, NULL, density);
}
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index 5d7877b..6e5e67c 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -29,22 +29,31 @@
static struct {
jfieldID mPtr; // native object attached to the DVM MessageQueue
+ jmethodID dispatchEvents;
} gMessageQueueClassInfo;
+// Must be kept in sync with the constants in Looper.FileDescriptorCallback
+static const int CALLBACK_EVENT_INPUT = 1 << 0;
+static const int CALLBACK_EVENT_OUTPUT = 1 << 1;
+static const int CALLBACK_EVENT_ERROR = 1 << 2;
-class NativeMessageQueue : public MessageQueue {
+
+class NativeMessageQueue : public MessageQueue, public LooperCallback {
public:
NativeMessageQueue();
virtual ~NativeMessageQueue();
virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj);
- void pollOnce(JNIEnv* env, int timeoutMillis);
-
+ void pollOnce(JNIEnv* env, jobject obj, int timeoutMillis);
void wake();
+ void setFileDescriptorEvents(int fd, int events);
+
+ virtual int handleEvent(int fd, int events, void* data);
private:
- bool mInCallback;
+ JNIEnv* mPollEnv;
+ jobject mPollObj;
jthrowable mExceptionObj;
};
@@ -66,10 +75,11 @@
return false;
}
-NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
+NativeMessageQueue::NativeMessageQueue() :
+ mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
- mLooper = new Looper(false);
+ mLooper = new Looper(true);
Looper::setForThread(mLooper);
}
}
@@ -79,7 +89,7 @@
void NativeMessageQueue::raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj) {
if (exceptionObj) {
- if (mInCallback) {
+ if (mPollEnv == env) {
if (mExceptionObj) {
env->DeleteLocalRef(mExceptionObj);
}
@@ -94,10 +104,13 @@
}
}
-void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {
- mInCallback = true;
+void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
+ mPollEnv = env;
+ mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
- mInCallback = false;
+ mPollObj = NULL;
+ mPollEnv = NULL;
+
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
@@ -109,6 +122,46 @@
mLooper->wake();
}
+void NativeMessageQueue::setFileDescriptorEvents(int fd, int events) {
+ if (events) {
+ int looperEvents = 0;
+ if (events & CALLBACK_EVENT_INPUT) {
+ looperEvents |= Looper::EVENT_INPUT;
+ }
+ if (events & CALLBACK_EVENT_OUTPUT) {
+ looperEvents |= Looper::EVENT_OUTPUT;
+ }
+ mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this,
+ reinterpret_cast<void*>(events));
+ } else {
+ mLooper->removeFd(fd);
+ }
+}
+
+int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) {
+ int events = 0;
+ if (looperEvents & Looper::EVENT_INPUT) {
+ events |= CALLBACK_EVENT_INPUT;
+ }
+ if (looperEvents & Looper::EVENT_OUTPUT) {
+ events |= CALLBACK_EVENT_OUTPUT;
+ }
+ if (looperEvents & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP | Looper::EVENT_INVALID)) {
+ events |= CALLBACK_EVENT_ERROR;
+ }
+ int oldWatchedEvents = reinterpret_cast<intptr_t>(data);
+ int newWatchedEvents = mPollEnv->CallIntMethod(mPollObj,
+ gMessageQueueClassInfo.dispatchEvents, fd, events);
+ if (!newWatchedEvents) {
+ return 0; // unregister the fd
+ }
+ if (newWatchedEvents != oldWatchedEvents) {
+ setFileDescriptorEvents(fd, newWatchedEvents);
+ }
+ return 1;
+}
+
+
// ----------------------------------------------------------------------------
sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {
@@ -132,20 +185,26 @@
nativeMessageQueue->decStrong(env);
}
-static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
+static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
- nativeMessageQueue->pollOnce(env, timeoutMillis);
+ nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
- return nativeMessageQueue->wake();
+ nativeMessageQueue->wake();
}
-static jboolean android_os_MessageQueue_nativeIsIdling(JNIEnv* env, jclass clazz, jlong ptr) {
+static jboolean android_os_MessageQueue_nativeIsPolling(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
- return nativeMessageQueue->getLooper()->isIdling();
+ return nativeMessageQueue->getLooper()->isPolling();
+}
+
+static void android_os_MessageQueue_nativeSetFileDescriptorEvents(JNIEnv* env, jclass clazz,
+ jlong ptr, jint fd, jint events) {
+ NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
+ nativeMessageQueue->setFileDescriptorEvents(fd, events);
}
// ----------------------------------------------------------------------------
@@ -156,7 +215,9 @@
{ "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
{ "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
- { "nativeIsIdling", "(J)Z", (void*)android_os_MessageQueue_nativeIsIdling }
+ { "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
+ { "nativeSetFileDescriptorEvents", "(JII)V",
+ (void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};
int register_android_os_MessageQueue(JNIEnv* env) {
@@ -164,8 +225,9 @@
NELEM(gMessageQueueMethods));
jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");
-
gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");
+ gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,
+ "dispatchEvents", "(II)I");
return res;
}
diff --git a/core/jni/android_os_MessageQueue.h b/core/jni/android_os_MessageQueue.h
index 49d2aa0..1e49b5f 100644
--- a/core/jni/android_os_MessageQueue.h
+++ b/core/jni/android_os_MessageQueue.h
@@ -22,7 +22,7 @@
namespace android {
-class MessageQueue : public RefBase {
+class MessageQueue : public virtual RefBase {
public:
/* Gets the message queue's looper. */
inline sp<Looper> getLooper() const {
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 39064ed..fff8604 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -489,7 +489,7 @@
proxy->initialize(surface);
// Shadows can't be used via this interface, so just set the light source
// to all 0s. (and width & height are unused, TODO remove them)
- proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0, 1.0f);
+ proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0);
return (jlong) proxy;
}
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index ad93301..3d9a9ed 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -239,18 +239,20 @@
delete proxy;
}
-static void android_view_ThreadedRenderer_setFrameInterval(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jlong frameIntervalNanos) {
- RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setFrameInterval(frameIntervalNanos);
-}
-
static jboolean android_view_ThreadedRenderer_loadSystemProperties(JNIEnv* env, jobject clazz,
jlong proxyPtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
return proxy->loadSystemProperties();
}
+static void android_view_ThreadedRenderer_setName(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jstring jname) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ const char* name = env->GetStringUTFChars(jname, NULL);
+ proxy->setName(name);
+ env->ReleaseStringUTFChars(jname, name);
+}
+
static jboolean android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -284,7 +286,7 @@
jint ambientShadowAlpha, jint spotShadowAlpha, jfloat density) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
proxy->setup(width, height, (Vector3){lightX, lightY, lightZ}, lightRadius,
- ambientShadowAlpha, spotShadowAlpha, density);
+ ambientShadowAlpha, spotShadowAlpha);
}
static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
@@ -424,12 +426,12 @@
{ "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
{ "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
{ "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
- { "nSetFrameInterval", "(JJ)V", (void*) android_view_ThreadedRenderer_setFrameInterval },
{ "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties },
+ { "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName },
{ "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
{ "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
{ "nPauseSurface", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_pauseSurface },
- { "nSetup", "(JIIFFFFIIF)V", (void*) android_view_ThreadedRenderer_setup },
+ { "nSetup", "(JIIFFFFII)V", (void*) android_view_ThreadedRenderer_setup },
{ "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
{ "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
{ "nDestroy", "(J)V", (void*) android_view_ThreadedRenderer_destroy },
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 30ce271..15797dd 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -31,8 +31,10 @@
<attr name="colorForeground" format="color" />
<!-- Default color of foreground imagery on an inverted background. -->
<attr name="colorForegroundInverse" format="color" />
- <!-- Color that matches (as closely as possible) the window background. -->
+ <!-- Default color of background imagery, ex. full-screen windows. -->
<attr name="colorBackground" format="color" />
+ <!-- Default color of background imagery for floating components, ex. dialogs, popups, and cards. -->
+ <attr name="colorBackgroundFloating" format="color" />
<!-- This is a hint for a solid color that can be used for caching
rendered views. This should be the color of the background when
there is a solid background color; it should be null when the
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index d657bad..6108b27 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -92,4 +92,5 @@
<item type="id" name="pasteAsPlainText" />
<item type="id" name="undo" />
<item type="id" name="redo" />
+ <item type="id" name="replaceText" />
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 16f0676..e507b3d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2642,15 +2642,13 @@
<public type="style" name="Theme.Material.Light.LightStatusBar" />
<public type="style" name="ThemeOverlay.Material.Dialog" />
- <!-- Context menu ID for the "Paste as plain text" menu item to to copy the current contents
- of the clipboard into the text view without formatting. -->
<public type="id" name="pasteAsPlainText" />
- <!-- Context menu ID for the "Undo" menu item to undo the last text edit operation. -->
<public type="id" name="undo" />
- <!-- Context menu ID for the "Redo" menu item to redo the last text edit operation. -->
<public type="id" name="redo" />
+ <public type="id" name="replaceText" />
- <!-- TextView attribute to control undo behavior. -->
<public type="attr" name="allowUndo" />
+ <public type="attr" name="colorBackgroundFloating" />
+
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 4ba6c0b..9e87b4d 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -45,6 +45,7 @@
<item name="colorForeground">@color/bright_foreground_dark</item>
<item name="colorForegroundInverse">@color/bright_foreground_dark_inverse</item>
<item name="colorBackground">@color/background_dark</item>
+ <item name="colorBackgroundFloating">?attr/colorBackground</item>
<item name="colorBackgroundCacheHint">?attr/colorBackground</item>
<item name="colorPressedHighlight">@color/legacy_pressed_highlight</item>
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index c30b3d5..701d0ef 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -65,6 +65,7 @@
<item name="colorForeground">@color/bright_foreground_holo_dark</item>
<item name="colorForegroundInverse">@color/bright_foreground_inverse_holo_dark</item>
<item name="colorBackground">@color/background_holo_dark</item>
+ <item name="colorBackgroundFloating">@color/background_holo_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_holo_dark</item>
<item name="disabledAlpha">0.5</item>
<item name="backgroundDimAmount">0.6</item>
@@ -404,6 +405,7 @@
<item name="colorForeground">@color/bright_foreground_holo_light</item>
<item name="colorForegroundInverse">@color/bright_foreground_inverse_holo_light</item>
<item name="colorBackground">@color/background_holo_light</item>
+ <item name="colorBackgroundFloating">@color/background_holo_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_holo_light</item>
<item name="disabledAlpha">0.5</item>
<item name="backgroundDimAmount">0.6</item>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index a610d07..38cfecd 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -45,6 +45,7 @@
<item name="colorForeground">@color/foreground_material_dark</item>
<item name="colorForegroundInverse">@color/foreground_material_light</item>
<item name="colorBackground">@color/background_material_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
<item name="disabledAlpha">@dimen/disabled_alpha_material_dark</item>
<item name="backgroundDimAmount">0.6</item>
@@ -398,6 +399,7 @@
<item name="colorForeground">@color/foreground_material_light</item>
<item name="colorForegroundInverse">@color/foreground_material_dark</item>
<item name="colorBackground">@color/background_material_light</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
<item name="disabledAlpha">@dimen/disabled_alpha_material_light</item>
<item name="backgroundDimAmount">0.6</item>
@@ -770,6 +772,7 @@
<item name="colorForeground">@color/foreground_material_light</item>
<item name="colorForegroundInverse">@color/foreground_material_dark</item>
<item name="colorBackground">@color/background_material_light</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
<item name="textColorPrimary">@color/primary_text_material_light</item>
@@ -806,6 +809,7 @@
<item name="colorForeground">@color/foreground_material_dark</item>
<item name="colorForegroundInverse">@color/foreground_material_light</item>
<item name="colorBackground">@color/background_material_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
<item name="textColorPrimary">@color/primary_text_material_dark</item>
@@ -852,7 +856,6 @@
<!-- Theme overlay that overrides window properties to display as a dialog. -->
<style name="ThemeOverlay.Material.Dialog">
- <item name="colorBackground">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@null</item>
<item name="windowFrame">@null</item>
@@ -1044,7 +1047,7 @@
<eat-comment />
<style name="Theme.Material.BaseDialog">
- <item name="colorBackground">@color/background_floating_material_dark</item>
+ <item name="colorBackground">?attr/colorBackgroundFloating</item>
<item name="windowFrame">@null</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
@@ -1155,7 +1158,7 @@
<!-- Light material dialog themes -->
<style name="Theme.Material.Light.BaseDialog">
- <item name="colorBackground">@color/background_floating_material_light</item>
+ <item name="colorBackground">?attr/colorBackgroundFloating</item>
<item name="windowFrame">@null</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 46b0945..7df61f27 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -97,8 +97,8 @@
int64_t totalDuration =
frame[FrameInfoIndex::kFrameCompleted] - frame[FrameInfoIndex::kIntendedVsync];
uint32_t framebucket = std::min(
- static_cast<typeof sizeof(mFrameCounts)>(ns2ms(totalDuration)),
- sizeof(mFrameCounts) / sizeof(mFrameCounts[0]));
+ static_cast<typeof mFrameCounts.size()>(ns2ms(totalDuration)),
+ mFrameCounts.size());
// Keep the fast path as fast as possible.
if (CC_LIKELY(totalDuration < mFrameInterval)) {
mFrameCounts[framebucket]++;
@@ -137,8 +137,8 @@
}
void JankTracker::reset() {
- memset(mBuckets, 0, sizeof(mBuckets));
- memset(mFrameCounts, 0, sizeof(mFrameCounts));
+ mBuckets.fill({0});
+ mFrameCounts.fill(0);
mTotalFrameCount = 0;
mJankFrameCount = 0;
}
@@ -146,7 +146,7 @@
uint32_t JankTracker::findPercentile(int percentile) {
int pos = percentile * mTotalFrameCount / 100;
int remaining = mTotalFrameCount - pos;
- for (int i = sizeof(mFrameCounts) / sizeof(mFrameCounts[0]) - 1; i >= 0; i--) {
+ for (int i = mFrameCounts.size() - 1; i >= 0; i--) {
remaining -= mFrameCounts[i];
if (remaining <= 0) {
return i;
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 3d4929b..ae339ec 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -20,6 +20,7 @@
#include "renderthread/TimeLord.h"
#include "utils/RingBuffer.h"
+#include <array>
#include <memory>
namespace android {
@@ -56,9 +57,9 @@
private:
uint32_t findPercentile(int p);
- JankBucket mBuckets[NUM_BUCKETS];
- int64_t mThresholds[NUM_BUCKETS];
- uint32_t mFrameCounts[128];
+ std::array<JankBucket, NUM_BUCKETS> mBuckets;
+ std::array<int64_t, NUM_BUCKETS> mThresholds;
+ std::array<uint32_t, 128> mFrameCounts;
int64_t mFrameInterval;
uint32_t mTotalFrameCount;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index fcf6eb2..9456073 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -51,6 +51,7 @@
, mRootRenderNode(rootRenderNode)
, mCurrentFrameInfo(nullptr) {
mRenderThread.renderState().registerCanvasContext(this);
+ mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
}
CanvasContext::~CanvasContext() {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 9a60dc7..c3904c2 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -33,6 +33,7 @@
#include <utils/Vector.h>
#include <set>
+#include <string>
namespace android {
namespace uirenderer {
@@ -106,6 +107,9 @@
void dumpFrames(int fd);
void resetFrameStats();
+ void setName(const std::string&& name) { mName = name; }
+ const std::string& name() { return mName; }
+
private:
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
@@ -139,6 +143,7 @@
FrameInfo* mCurrentFrameInfo;
// Ring buffer large enough for 1 second worth of frames
RingBuffer<FrameInfo, 60> mFrames;
+ std::string mName;
std::set<RenderNode*> mPrefetechedLayers;
};
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index f48ee41..35391b2 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -34,7 +34,6 @@
DrawFrameTask::DrawFrameTask()
: mRenderThread(nullptr)
, mContext(nullptr)
- , mDensity(1.0f) // safe enough default
, mSyncResult(kSync_OK) {
}
@@ -84,7 +83,6 @@
void DrawFrameTask::run() {
ATRACE_NAME("DrawFrame");
- mContext->profiler().setDensity(mDensity);
mContext->profiler().startFrame();
bool canUnblockUiThread;
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 0e56bea..8039643 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -62,7 +62,6 @@
void pushLayerUpdate(DeferredLayerUpdater* layer);
void removeLayerUpdate(DeferredLayerUpdater* layer);
- void setDensity(float density) { mDensity = density; }
int drawFrame();
int64_t* frameInfo() { return mFrameInfo; }
@@ -83,7 +82,6 @@
/*********************************************
* Single frame data
*********************************************/
- float mDensity;
std::vector< sp<DeferredLayerUpdater> > mLayers;
int mSyncResult;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 0fa2f23..ea4216c 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -97,18 +97,6 @@
}
}
-CREATE_BRIDGE2(setFrameInterval, RenderThread* thread, nsecs_t frameIntervalNanos) {
- args->thread->setFrameInterval(args->frameIntervalNanos);
- return nullptr;
-}
-
-void RenderProxy::setFrameInterval(nsecs_t frameIntervalNanos) {
- SETUP_TASK(setFrameInterval);
- args->thread = &mRenderThread;
- args->frameIntervalNanos = frameIntervalNanos;
- post(task);
-}
-
CREATE_BRIDGE2(setSwapBehavior, CanvasContext* context, SwapBehavior swapBehavior) {
args->context->setSwapBehavior(args->swapBehavior);
return nullptr;
@@ -138,6 +126,18 @@
return (bool) postAndWait(task);
}
+CREATE_BRIDGE2(setName, CanvasContext* context, const char* name) {
+ args->context->setName(std::string(args->name));
+ return nullptr;
+}
+
+void RenderProxy::setName(const char* name) {
+ SETUP_TASK(setName);
+ args->context = mContext;
+ args->name = name;
+ post(task);
+}
+
CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
return (void*) args->context->initialize(args->window);
}
@@ -181,8 +181,7 @@
}
void RenderProxy::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
- uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density) {
- mDrawFrameTask.setDensity(density);
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
SETUP_TASK(setup);
args->context = mContext;
args->width = width;
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 19e73e5..43cbe07 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -62,16 +62,16 @@
ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode, IContextFactory* contextFactory);
ANDROID_API virtual ~RenderProxy();
- ANDROID_API void setFrameInterval(nsecs_t frameIntervalNanos);
// Won't take effect until next EGLSurface creation
ANDROID_API void setSwapBehavior(SwapBehavior swapBehavior);
ANDROID_API bool loadSystemProperties();
+ ANDROID_API void setName(const char* name);
ANDROID_API bool initialize(const sp<ANativeWindow>& window);
ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
- uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density);
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
ANDROID_API void setOpaque(bool opaque);
ANDROID_API int64_t* frameInfo();
ANDROID_API int syncAndDrawFrame();
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 2a8baa7..3ac2976 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -22,6 +22,8 @@
#include "RenderProxy.h"
#include <gui/DisplayEventReceiver.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
#include <sys/resource.h>
#include <utils/Log.h>
@@ -151,11 +153,6 @@
LOG_ALWAYS_FATAL("Can't destroy the render thread");
}
-void RenderThread::setFrameInterval(nsecs_t frameInterval) {
- mTimeLord.setFrameInterval(frameInterval);
- mJankTracker->setFrameInterval(frameInterval);
-}
-
void RenderThread::initializeDisplayEventReceiver() {
LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
mDisplayEventReceiver = new DisplayEventReceiver();
@@ -169,10 +166,16 @@
}
void RenderThread::initThreadLocals() {
+ sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
+ ISurfaceComposer::eDisplayIdMain));
+ status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &mDisplayInfo);
+ LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n");
+ nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / mDisplayInfo.fps);
+ mTimeLord.setFrameInterval(frameIntervalNanos);
initializeDisplayEventReceiver();
mEglManager = new EglManager(*this);
mRenderState = new RenderState(*this);
- mJankTracker = new JankTracker(mTimeLord.frameIntervalNanos());
+ mJankTracker = new JankTracker(frameIntervalNanos);
}
int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index f169424..8096099 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -23,6 +23,7 @@
#include "TimeLord.h"
#include <cutils/compiler.h>
+#include <ui/DisplayInfo.h>
#include <utils/Looper.h>
#include <utils/Mutex.h>
#include <utils/Singleton.h>
@@ -86,13 +87,13 @@
// the next vsync. If it is not currently registered this does nothing.
void pushBackFrameCallback(IFrameCallback* callback);
- void setFrameInterval(nsecs_t frameInterval);
-
TimeLord& timeLord() { return mTimeLord; }
RenderState& renderState() { return *mRenderState; }
EglManager& eglManager() { return *mEglManager; }
JankTracker& jankTracker() { return *mJankTracker; }
+ const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; }
+
protected:
virtual bool threadLoop() override;
@@ -122,6 +123,8 @@
nsecs_t mNextWakeup;
TaskQueue mQueue;
+ DisplayInfo mDisplayInfo;
+
DisplayEventReceiver* mDisplayEventReceiver;
bool mVsyncRequested;
std::set<IFrameCallback*> mFrameCallbacks;
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index 0d1e63e..805989b 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -85,7 +85,7 @@
proxy->initialize(surface);
float lightX = width / 2.0;
proxy->setup(width, height, (Vector3){lightX, dp(-200.0f), dp(800.0f)},
- dp(800.0f), 255 * 0.075, 255 * 0.15, gDisplay.density);
+ dp(800.0f), 255 * 0.075, 255 * 0.15);
android::uirenderer::Rect DUMMY;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 43c394a..db67fbf 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1498,7 +1498,7 @@
public void setMode(int mode) {
IAudioService service = getService();
try {
- service.setMode(mode, mICallBack);
+ service.setMode(mode, mICallBack, mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setMode", e);
}
@@ -3037,7 +3037,8 @@
public void setWiredDeviceConnectionState(int type, int state, String address, String name) {
IAudioService service = getService();
try {
- service.setWiredDeviceConnectionState(type, state, address, name);
+ service.setWiredDeviceConnectionState(type, state, address, name,
+ mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setWiredDeviceConnectionState "+e);
}
@@ -3181,7 +3182,7 @@
*/
public void disableSafeMediaVolume() {
try {
- getService().disableSafeMediaVolume();
+ getService().disableSafeMediaVolume(mContext.getOpPackageName());
} catch (RemoteException e) {
Log.w(TAG, "Error disabling safe media volume", e);
}
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index 059d940..d456b7e 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -27,8 +27,7 @@
public abstract class AudioManagerInternal {
public abstract void adjustSuggestedStreamVolumeForUid(int streamType, int direction,
- int flags,
- String callingPackage, int uid);
+ int flags, String callingPackage, int uid);
public abstract void adjustStreamVolumeForUid(int streamType, int direction, int flags,
String callingPackage, int uid);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 20f7d29..2e8e017 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -40,7 +40,7 @@
interface IAudioService {
void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage);
+ String callingPackage, String caller);
void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage);
@@ -80,7 +80,7 @@
boolean shouldVibrate(int vibrateType);
- void setMode(int mode, IBinder cb);
+ void setMode(int mode, IBinder cb, String callingPackage);
int getMode();
@@ -181,7 +181,9 @@
IRingtonePlayer getRingtonePlayer();
int getUiSoundsStreamType();
- void setWiredDeviceConnectionState(int type, int state, String address, String name);
+ void setWiredDeviceConnectionState(int type, int state, String address, String name,
+ String caller);
+
int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
@@ -196,7 +198,7 @@
boolean isStreamAffectedByMute(int streamType);
- void disableSafeMediaVolume();
+ void disableSafeMediaVolume(String callingPackage);
int setHdmiSystemAudioSupported(boolean on);
diff --git a/media/java/android/media/midi/IMidiDeviceListener.aidl b/media/java/android/media/midi/IMidiDeviceListener.aidl
index 17d9bfd..31c66e3 100644
--- a/media/java/android/media/midi/IMidiDeviceListener.aidl
+++ b/media/java/android/media/midi/IMidiDeviceListener.aidl
@@ -17,10 +17,12 @@
package android.media.midi;
import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
/** @hide */
oneway interface IMidiDeviceListener
{
void onDeviceAdded(in MidiDeviceInfo device);
void onDeviceRemoved(in MidiDeviceInfo device);
+ void onDeviceStatusChanged(in MidiDeviceStatus status);
}
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index 3331aae..642078a 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -24,4 +24,7 @@
ParcelFileDescriptor openInputPort(IBinder token, int portNumber);
ParcelFileDescriptor openOutputPort(IBinder token, int portNumber);
void closePort(IBinder token);
+
+ // connects the input port pfd to the specified output port
+ void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
}
diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl
index 617b03e..a3b40d6 100644
--- a/media/java/android/media/midi/IMidiManager.aidl
+++ b/media/java/android/media/midi/IMidiManager.aidl
@@ -19,6 +19,7 @@
import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceServer;
import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
import android.os.Bundle;
import android.os.IBinder;
@@ -44,4 +45,11 @@
// used by MidiDeviceService to access the MidiDeviceInfo that was created based on its
// manifest's meta-data
MidiDeviceInfo getServiceDeviceInfo(String packageName, String className);
+
+ // used for client's to retrieve a device's MidiDeviceStatus
+ MidiDeviceStatus getDeviceStatus(in MidiDeviceInfo deviceInfo);
+
+ // used by MIDI devices to report their status
+ // the token is used by MidiService for death notification
+ void setDeviceStatus(IBinder token, in MidiDeviceStatus status);
}
diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index af0737d..569f7c6 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -26,6 +26,8 @@
import dalvik.system.CloseGuard;
+import libcore.io.IoUtils;
+
import java.io.Closeable;
import java.io.IOException;
@@ -44,8 +46,29 @@
private Context mContext;
private ServiceConnection mServiceConnection;
+
private final CloseGuard mGuard = CloseGuard.get();
+ public class MidiConnection implements Closeable {
+ private final IBinder mToken;
+ private final MidiInputPort mInputPort;
+
+ MidiConnection(IBinder token, MidiInputPort inputPort) {
+ mToken = token;
+ mInputPort = inputPort;
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ mDeviceServer.closePort(mToken);
+ IoUtils.closeQuietly(mInputPort);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in MidiConnection.close");
+ }
+ }
+ }
+
/* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
this(deviceInfo, server, null, null);
}
@@ -108,6 +131,36 @@
}
}
+ /**
+ * Connects the supplied {@link MidiInputPort} to the output port of this device
+ * with the specified port number. Once the connection is made, the MidiInput port instance
+ * can no longer receive data via its {@link MidiReciever.receive} method.
+ * This method returns a {@link #MidiConnection} object, which can be used to close the connection
+ * @param inputPort the inputPort to connect
+ * @param outputPortNumber the port number of the output port to connect inputPort to.
+ * @return {@link #MidiConnection} object if the connection is successful, or null in case of failure
+ */
+ public MidiConnection connectPorts(MidiInputPort inputPort, int outputPortNumber) {
+ if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) {
+ throw new IllegalArgumentException("outputPortNumber out of range");
+ }
+
+ ParcelFileDescriptor pfd = inputPort.claimFileDescriptor();
+ if (pfd == null) {
+ return null;
+ }
+ try {
+ IBinder token = new Binder();
+ mDeviceServer.connectPorts(token, pfd, outputPortNumber);
+ // close our copy of the file descriptor
+ IoUtils.closeQuietly(pfd);
+ return new MidiConnection(token, inputPort);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in connectPorts");
+ return null;
+ }
+ }
+
@Override
public void close() throws IOException {
synchronized (mGuard) {
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 3b4b6f0..b3c0e3a 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -16,8 +16,8 @@
package android.media.midi;
-import android.os.IBinder;
import android.os.Binder;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
@@ -61,7 +61,25 @@
private final CopyOnWriteArrayList<MidiInputPort> mInputPorts
= new CopyOnWriteArrayList<MidiInputPort>();
+
+ // for reporting device status
+ private final IBinder mDeviceStatusToken = new Binder();
+ private final boolean[] mInputPortBusy;
+ private final int[] mOutputPortOpenCount;
+
private final CloseGuard mGuard = CloseGuard.get();
+ private boolean mIsClosed;
+
+ private final Callback mCallback;
+
+ public interface Callback {
+ /**
+ * Called to notify when an our device status has changed
+ * @param server the {@link MidiDeviceServer} that changed
+ * @param status the {@link MidiDeviceStatus} for the device
+ */
+ public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status);
+ }
abstract private class PortClient implements IBinder.DeathRecipient {
final IBinder mToken;
@@ -96,7 +114,10 @@
void close() {
mToken.unlinkToDeath(this, 0);
synchronized (mInputPortOutputPorts) {
- mInputPortOutputPorts[mOutputPort.getPortNumber()] = null;
+ int portNumber = mOutputPort.getPortNumber();
+ mInputPortOutputPorts[portNumber] = null;
+ mInputPortBusy[portNumber] = false;
+ updateDeviceStatus();
}
IoUtils.closeQuietly(mOutputPort);
}
@@ -113,7 +134,15 @@
@Override
void close() {
mToken.unlinkToDeath(this, 0);
- mOutputPortDispatchers[mInputPort.getPortNumber()].getSender().disconnect(mInputPort);
+ int portNumber = mInputPort.getPortNumber();
+ MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
+ synchronized (dispatcher) {
+ dispatcher.getSender().disconnect(mInputPort);
+ int openCount = dispatcher.getReceiverCount();
+ mOutputPortOpenCount[portNumber] = openCount;
+ updateDeviceStatus();
+ }
+
mInputPorts.remove(mInputPort);
IoUtils.closeQuietly(mInputPort);
}
@@ -153,6 +182,8 @@
synchronized (mPortClients) {
mPortClients.put(token, client);
}
+ mInputPortBusy[portNumber] = true;
+ updateDeviceStatus();
return pair[1];
} catch (IOException e) {
Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
@@ -178,7 +209,14 @@
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
- mOutputPortDispatchers[portNumber].getSender().connect(inputPort);
+ MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
+ synchronized (dispatcher) {
+ dispatcher.getSender().connect(inputPort);
+ int openCount = dispatcher.getReceiverCount();
+ mOutputPortOpenCount[portNumber] = openCount;
+ updateDeviceStatus();
+ }
+
mInputPorts.add(inputPort);
OutputPortClient client = new OutputPortClient(token, inputPort);
synchronized (mPortClients) {
@@ -200,14 +238,27 @@
}
}
}
+
+ @Override
+ public void connectPorts(IBinder token, ParcelFileDescriptor pfd,
+ int outputPortNumber) {
+ MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber);
+ mOutputPortDispatchers[outputPortNumber].getSender().connect(inputPort);
+ mInputPorts.add(inputPort);
+ OutputPortClient client = new OutputPortClient(token, inputPort);
+ synchronized (mPortClients) {
+ mPortClients.put(token, client);
+ }
+ }
};
/* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
- int numOutputPorts) {
+ int numOutputPorts, Callback callback) {
mMidiManager = midiManager;
mInputPortReceivers = inputPortReceivers;
mInputPortCount = inputPortReceivers.length;
mOutputPortCount = numOutputPorts;
+ mCallback = callback;
mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
@@ -216,6 +267,9 @@
mOutputPortDispatchers[i] = new MidiDispatcher();
}
+ mInputPortBusy = new boolean[mInputPortCount];
+ mOutputPortOpenCount = new int[numOutputPorts];
+
mGuard.open("close");
}
@@ -230,9 +284,28 @@
mDeviceInfo = deviceInfo;
}
+ private void updateDeviceStatus() {
+ // clear calling identity, since we may be in a Binder call from one of our clients
+ long identityToken = Binder.clearCallingIdentity();
+
+ MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortBusy,
+ mOutputPortOpenCount);
+ if (mCallback != null) {
+ mCallback.onDeviceStatusChanged(this, status);
+ }
+ try {
+ mMidiManager.setDeviceStatus(mDeviceStatusToken, status);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in updateDeviceStatus");
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
@Override
public void close() throws IOException {
synchronized (mGuard) {
+ if (mIsClosed) return;
mGuard.close();
for (int i = 0; i < mInputPortCount; i++) {
@@ -251,6 +324,7 @@
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in unregisterDeviceServer");
}
+ mIsClosed = true;
}
}
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
index 64f69cd..5f55ae2 100644
--- a/media/java/android/media/midi/MidiDeviceService.java
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -57,6 +57,13 @@
private MidiDeviceServer mServer;
private MidiDeviceInfo mDeviceInfo;
+ private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
+ @Override
+ public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
+ MidiDeviceService.this.onDeviceStatusChanged(status);
+ }
+ };
+
@Override
public void onCreate() {
mMidiManager = IMidiManager.Stub.asInterface(
@@ -75,7 +82,7 @@
inputPortReceivers = new MidiReceiver[0];
}
server = new MidiDeviceServer(mMidiManager, inputPortReceivers,
- deviceInfo.getOutputPortCount());
+ deviceInfo.getOutputPortCount(), mCallback);
server.setDeviceInfo(deviceInfo);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo");
@@ -114,6 +121,13 @@
return mDeviceInfo;
}
+ /**
+ * Called to notify when an our {@link MidiDeviceStatus} has changed
+ * @param status the number of the port that was opened
+ */
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ }
+
@Override
public IBinder onBind(Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
diff --git a/media/java/android/media/midi/MidiDeviceStatus.aidl b/media/java/android/media/midi/MidiDeviceStatus.aidl
new file mode 100644
index 0000000..1a848c0
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015, 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.media.midi;
+
+parcelable MidiDeviceStatus;
diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java
new file mode 100644
index 0000000..cc04889
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceStatus.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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.media.midi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This is an immutable class that describes the current status of a MIDI device's ports.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public final class MidiDeviceStatus implements Parcelable {
+
+ private static final String TAG = "MidiDeviceStatus";
+
+ private final MidiDeviceInfo mDeviceInfo;
+ // true if input ports are busy
+ private final boolean mInputPortBusy[];
+ // open counts for output ports
+ private final int mOutputPortOpenCount[];
+
+ /**
+ * @hide
+ */
+ public MidiDeviceStatus(MidiDeviceInfo deviceInfo, boolean inputPortBusy[],
+ int outputPortOpenCount[]) {
+ // MidiDeviceInfo is immutable so we can share references
+ mDeviceInfo = deviceInfo;
+
+ // make copies of the arrays
+ mInputPortBusy = new boolean[inputPortBusy.length];
+ System.arraycopy(inputPortBusy, 0, mInputPortBusy, 0, inputPortBusy.length);
+ mOutputPortOpenCount = new int[outputPortOpenCount.length];
+ System.arraycopy(outputPortOpenCount, 0, mOutputPortOpenCount, 0,
+ outputPortOpenCount.length);
+ }
+
+ /**
+ * Creates a MidiDeviceStatus with false for all input port busy values
+ * and zero for all output port open counts
+ * @hide
+ */
+ public MidiDeviceStatus(MidiDeviceInfo deviceInfo) {
+ mDeviceInfo = deviceInfo;
+ mInputPortBusy = new boolean[deviceInfo.getInputPortCount()];
+ mOutputPortOpenCount = new int[deviceInfo.getOutputPortCount()];
+ }
+
+ /**
+ * Returns the {@link MidiDeviceInfo} of the device.
+ *
+ * @return the device info
+ */
+ public MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
+
+ /**
+ * Returns true if an input port is busy.
+ *
+ * @param input port's port number
+ * @return input port busy status
+ */
+ public boolean isInputPortBusy(int portNumber) {
+ return mInputPortBusy[portNumber];
+ }
+
+ /**
+ * Returns the open count for an output port.
+ *
+ * @param output port's port number
+ * @return output port open count
+ */
+ public int getOutputPortOpenCount(int portNumber) {
+ return mOutputPortOpenCount[portNumber];
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(mDeviceInfo.toString());
+ int inputPortCount = mDeviceInfo.getInputPortCount();
+ int outputPortCount = mDeviceInfo.getOutputPortCount();
+ builder.append(" mInputPortBusy=[");
+ for (int i = 0; i < inputPortCount; i++) {
+ builder.append(mInputPortBusy[i]);
+ if (i < inputPortCount -1) {
+ builder.append(",");
+ }
+ }
+ builder.append("] mOutputPortOpenCount=[");
+ for (int i = 0; i < outputPortCount; i++) {
+ builder.append(mOutputPortOpenCount[i]);
+ if (i < outputPortCount -1) {
+ builder.append(",");
+ }
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ public static final Parcelable.Creator<MidiDeviceStatus> CREATOR =
+ new Parcelable.Creator<MidiDeviceStatus>() {
+ public MidiDeviceStatus createFromParcel(Parcel in) {
+ ClassLoader classLoader = MidiDeviceInfo.class.getClassLoader();
+ MidiDeviceInfo deviceInfo = in.readParcelable(classLoader);
+ boolean[] inputPortBusy = in.createBooleanArray();
+ int[] outputPortOpenCount = in.createIntArray();
+ return new MidiDeviceStatus(deviceInfo, inputPortBusy, outputPortOpenCount);
+ }
+
+ public MidiDeviceStatus[] newArray(int size) {
+ return new MidiDeviceStatus[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mDeviceInfo, flags);
+ parcel.writeBooleanArray(mInputPortBusy);
+ parcel.writeIntArray(mOutputPortOpenCount);
+ }
+}
diff --git a/media/java/android/media/midi/MidiDispatcher.java b/media/java/android/media/midi/MidiDispatcher.java
index 90789e5..d13ca74 100644
--- a/media/java/android/media/midi/MidiDispatcher.java
+++ b/media/java/android/media/midi/MidiDispatcher.java
@@ -55,11 +55,11 @@
};
/**
- * Returns whether this dispatcher contains any receivers.
- * @return true if the receiver list is not empty
+ * Returns the number of {@link MidiReceiver}s this dispatcher contains.
+ * @return the number of receivers
*/
- public boolean hasReceivers() {
- return mReceivers.size() > 0;
+ public int getReceiverCount() {
+ return mReceivers.size();
}
/**
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 74e1fa4..752075e1 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -41,7 +41,8 @@
private IMidiDeviceServer mDeviceServer;
private final IBinder mToken;
private final int mPortNumber;
- private final FileOutputStream mOutputStream;
+ private ParcelFileDescriptor mParcelFileDescriptor;
+ private FileOutputStream mOutputStream;
private final CloseGuard mGuard = CloseGuard.get();
private boolean mIsClosed;
@@ -53,8 +54,9 @@
ParcelFileDescriptor pfd, int portNumber) {
mDeviceServer = server;
mToken = token;
+ mParcelFileDescriptor = pfd;
mPortNumber = portNumber;
- mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
+ mOutputStream = new FileOutputStream(pfd.getFileDescriptor());
mGuard.open("close");
}
@@ -89,11 +91,27 @@
}
synchronized (mBuffer) {
+ if (mOutputStream == null) {
+ throw new IOException("MidiInputPort is closed");
+ }
int length = MidiPortImpl.packMessage(msg, offset, count, timestamp, mBuffer);
mOutputStream.write(mBuffer, 0, length);
}
}
+ // used by MidiDevice.connectInputPort() to connect our socket directly to another device
+ /* package */ ParcelFileDescriptor claimFileDescriptor() {
+ synchronized (mBuffer) {
+ ParcelFileDescriptor pfd = mParcelFileDescriptor;
+ if (pfd != null) {
+ IoUtils.closeQuietly(mOutputStream);
+ mParcelFileDescriptor = null;
+ mOutputStream = null;
+ }
+ return pfd;
+ }
+ }
+
@Override
public int getMaxMessageSize() {
return MidiPortImpl.MAX_PACKET_DATA_SIZE;
@@ -104,7 +122,16 @@
synchronized (mGuard) {
if (mIsClosed) return;
mGuard.close();
- mOutputStream.close();
+ synchronized (mBuffer) {
+ if (mParcelFileDescriptor != null) {
+ mParcelFileDescriptor.close();
+ mParcelFileDescriptor = null;
+ }
+ if (mOutputStream != null) {
+ mOutputStream.close();
+ mOutputStream = null;
+ }
+ }
if (mDeviceServer != null) {
try {
mDeviceServer.closePort(mToken);
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index d7b8c57..bab9064 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -62,6 +62,7 @@
mHandler = handler;
}
+ @Override
public void onDeviceAdded(MidiDeviceInfo device) {
if (mHandler != null) {
final MidiDeviceInfo deviceF = device;
@@ -75,6 +76,7 @@
}
}
+ @Override
public void onDeviceRemoved(MidiDeviceInfo device) {
if (mHandler != null) {
final MidiDeviceInfo deviceF = device;
@@ -87,25 +89,49 @@
mCallback.onDeviceRemoved(device);
}
}
+
+ @Override
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ if (mHandler != null) {
+ final MidiDeviceStatus statusF = status;
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ mCallback.onDeviceStatusChanged(statusF);
+ }
+ });
+ } else {
+ mCallback.onDeviceStatusChanged(status);
+ }
+ }
}
/**
* Callback class used for clients to receive MIDI device added and removed notifications
*/
- abstract public static class DeviceCallback {
+ public static class DeviceCallback {
/**
* Called to notify when a new MIDI device has been added
*
* @param device a {@link MidiDeviceInfo} for the newly added device
*/
- abstract public void onDeviceAdded(MidiDeviceInfo device);
+ public void onDeviceAdded(MidiDeviceInfo device) {
+ }
/**
* Called to notify when a MIDI device has been removed
*
* @param device a {@link MidiDeviceInfo} for the removed device
*/
- abstract public void onDeviceRemoved(MidiDeviceInfo device);
+ public void onDeviceRemoved(MidiDeviceInfo device) {
+ }
+
+ /**
+ * Called to notify when the status of a MIDI device has changed
+ *
+ * @param device a {@link MidiDeviceStatus} for the changed device
+ */
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ }
}
/**
@@ -251,10 +277,10 @@
/** @hide */
public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
- int numOutputPorts, Bundle properties, int type) {
+ int numOutputPorts, Bundle properties, int type, MidiDeviceServer.Callback callback) {
try {
MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
- numOutputPorts);
+ numOutputPorts, callback);
MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
inputPortReceivers.length, numOutputPorts, properties, type);
if (deviceInfo == null) {
diff --git a/native/graphics/jni/Android.mk b/native/graphics/jni/Android.mk
index 14575ee..b7f0fbd 100644
--- a/native/graphics/jni/Android.mk
+++ b/native/graphics/jni/Android.mk
@@ -31,7 +31,7 @@
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
# TODO: This is to work around b/19059885. Remove after root cause is fixed
-LOCAL_LDFLAGS := -Wl,--hash-style=both
+LOCAL_LDFLAGS_arm := -Wl,--hash-style=both
include $(BUILD_SHARED_LIBRARY)
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 870afeb..b5e49ce 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -76,6 +76,17 @@
<string name="bluetooth_connecting">Connecting\u2026</string>
<!-- Bluetooth settings. Message when connected to a device. [CHAR LIMIT=40] -->
<string name="bluetooth_connected">Connected</string>
+ <!--Bluetooth settings screen, summary text under individual Bluetooth devices when pairing -->
+ <string name="bluetooth_pairing">Pairing\u2026</string>
+
+ <!-- Bluetooth settings. Message when connected to a device, except for phone audio. [CHAR LIMIT=40] -->
+ <string name="bluetooth_connected_no_headset">Connected (no phone)</string>
+ <!-- Bluetooth settings. Message when connected to a device, except for media audio. [CHAR LIMIT=40] -->
+ <string name="bluetooth_connected_no_a2dp">Connected (no media)</string>
+ <!-- Bluetooth settings. Message when connected to a device, except for map. [CHAR LIMIT=40] -->
+ <string name="bluetooth_connected_no_map">Connected (no message access)</string>
+ <!-- Bluetooth settings. Message when connected to a device, except for phone/media audio. [CHAR LIMIT=40] -->
+ <string name="bluetooth_connected_no_headset_no_a2dp">Connected (no phone or media)</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. -->
<string name="bluetooth_profile_a2dp">Media audio</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index b802f58..4c41b49 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -27,4 +27,5 @@
void onDeviceAdded(CachedBluetoothDevice cachedDevice);
void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
+ void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 7c92368..5d6b2f1 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -81,6 +81,9 @@
// Bluetooth on/off broadcasts
addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
+ // Generic connected/not broadcast
+ addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,
+ new ConnectionStateChangedHandler());
// Discovery broadcasts
addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
@@ -183,8 +186,6 @@
cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
+ cachedDevice);
- // callback to UI to create Preference for new device
- dispatchDeviceAdded(cachedDevice);
}
cachedDevice.setRssi(rssi);
cachedDevice.setBtClass(btClass);
@@ -193,7 +194,25 @@
}
}
- private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
+ private class ConnectionStateChangedHandler implements Handler {
+ @Override
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+ BluetoothAdapter.ERROR);
+ dispatchConnectionStateChanged(cachedDevice, state);
+ }
+ }
+
+ private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onConnectionStateChanged(cachedDevice, state);
+ }
+ }
+ }
+
+ void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
synchronized (mCallbacks) {
for (BluetoothCallback callback : mCallbacks) {
callback.onDeviceAdded(cachedDevice);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index ddcc49f7..e1cb878 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -28,6 +28,8 @@
import android.util.Log;
import android.bluetooth.BluetoothAdapter;
+import com.android.settingslib.R;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -784,4 +786,62 @@
setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
}
}
+
+ public int getMaxConnectionState() {
+ int maxState = BluetoothProfile.STATE_DISCONNECTED;
+ for (LocalBluetoothProfile profile : getProfiles()) {
+ int connectionStatus = getProfileConnectionState(profile);
+ if (connectionStatus > maxState) {
+ maxState = connectionStatus;
+ }
+ }
+ return maxState;
+ }
+
+ /**
+ * @return resource for string that discribes the connection state of this device.
+ */
+ public int getConnectionSummary() {
+ boolean profileConnected = false; // at least one profile is connected
+ boolean a2dpNotConnected = false; // A2DP is preferred but not connected
+ boolean headsetNotConnected = false; // Headset is preferred but not connected
+
+ for (LocalBluetoothProfile profile : getProfiles()) {
+ int connectionStatus = getProfileConnectionState(profile);
+
+ switch (connectionStatus) {
+ case BluetoothProfile.STATE_CONNECTING:
+ case BluetoothProfile.STATE_DISCONNECTING:
+ return Utils.getConnectionStateSummary(connectionStatus);
+
+ case BluetoothProfile.STATE_CONNECTED:
+ profileConnected = true;
+ break;
+
+ case BluetoothProfile.STATE_DISCONNECTED:
+ if (profile.isProfileReady()) {
+ if (profile instanceof A2dpProfile) {
+ a2dpNotConnected = true;
+ } else if (profile instanceof HeadsetProfile) {
+ headsetNotConnected = true;
+ }
+ }
+ break;
+ }
+ }
+
+ if (profileConnected) {
+ if (a2dpNotConnected && headsetNotConnected) {
+ return R.string.bluetooth_connected_no_headset_no_a2dp;
+ } else if (a2dpNotConnected) {
+ return R.string.bluetooth_connected_no_a2dp;
+ } else if (headsetNotConnected) {
+ return R.string.bluetooth_connected_no_headset;
+ } else {
+ return R.string.bluetooth_connected;
+ }
+ }
+
+ return getBondState() == BluetoothDevice.BOND_BONDING ? R.string.bluetooth_pairing : 0;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 65db95f..a9f4bd3 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -35,9 +35,11 @@
private Context mContext;
private final List<CachedBluetoothDevice> mCachedDevices =
new ArrayList<CachedBluetoothDevice>();
+ private final LocalBluetoothManager mBtManager;
- CachedBluetoothDeviceManager(Context context) {
+ CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
mContext = context;
+ mBtManager = localBtManager;
}
public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
@@ -88,6 +90,7 @@
profileManager, device);
synchronized (mCachedDevices) {
mCachedDevices.add(newDevice);
+ mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
}
return newDevice;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 0c1adec..e3d2a99 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -122,6 +122,10 @@
return mAdapter.isEnabled();
}
+ public int getConnectionState() {
+ return mAdapter.getConnectionState();
+ }
+
public void setDiscoverableTimeout(int timeout) {
mAdapter.setDiscoverableTimeout(timeout);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
index 4adc62e..623ccc3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -68,7 +68,7 @@
mContext = context;
mLocalAdapter = adapter;
- mCachedDeviceManager = new CachedBluetoothDeviceManager(context);
+ mCachedDeviceManager = new CachedBluetoothDeviceManager(context, this);
mEventManager = new BluetoothEventManager(mLocalAdapter,
mCachedDeviceManager, context);
mProfileManager = new LocalBluetoothProfileManager(context,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 3a8216d..3bf6828 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -470,7 +470,7 @@
private void skipEmptyTextTags(XmlPullParser parser)
throws IOException, XmlPullParserException {
while (accept(parser, XmlPullParser.TEXT, null)
- && "\n".equals(parser.getText())) {
+ && parser.isWhitespace()) {
parser.next();
}
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e8260bb..158e133 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -47,6 +47,7 @@
<!-- Networking and telephony -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index c15566f..b42b5f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
@@ -23,13 +25,14 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.BluetoothController.PairedDevice;
+import java.util.Collection;
import java.util.Set;
/** Quick settings tile: Bluetooth **/
@@ -143,7 +146,7 @@
refreshState();
}
@Override
- public void onBluetoothPairedDevicesChanged() {
+ public void onBluetoothDevicesChanged() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
@@ -199,19 +202,21 @@
private void updateItems() {
if (mItems == null) return;
Item[] items = null;
- final Set<PairedDevice> devices = mController.getPairedDevices();
+ final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
- items = new Item[devices.size()];
+ items = new Item[getBondedCount(devices)];
int i = 0;
- for (PairedDevice device : devices) {
+ for (CachedBluetoothDevice device : devices) {
+ if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
item.icon = R.drawable.ic_qs_bluetooth_on;
- item.line1 = device.name;
- if (device.state == PairedDevice.STATE_CONNECTED) {
+ item.line1 = device.getName();
+ int state = device.getMaxConnectionState();
+ if (state == BluetoothProfile.STATE_CONNECTED) {
item.icon = R.drawable.ic_qs_bluetooth_connected;
item.line2 = mContext.getString(R.string.quick_settings_connected);
item.canDisconnect = true;
- } else if (device.state == PairedDevice.STATE_CONNECTING) {
+ } else if (state == BluetoothProfile.STATE_CONNECTING) {
item.icon = R.drawable.ic_qs_bluetooth_connecting;
item.line2 = mContext.getString(R.string.quick_settings_connecting);
}
@@ -222,11 +227,22 @@
mItems.setItems(items);
}
+ private int getBondedCount(Collection<CachedBluetoothDevice> devices) {
+ int ct = 0;
+ for (CachedBluetoothDevice device : devices) {
+ if (device.getBondState() != BluetoothDevice.BOND_NONE) {
+ ct++;
+ }
+ }
+ return ct;
+ }
+
@Override
public void onDetailItemClick(Item item) {
if (item == null || item.tag == null) return;
- final PairedDevice device = (PairedDevice) item.tag;
- if (device != null && device.state == PairedDevice.STATE_DISCONNECTED) {
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ if (device != null && device.getMaxConnectionState()
+ == BluetoothProfile.STATE_DISCONNECTED) {
mController.connect(device);
}
}
@@ -234,7 +250,7 @@
@Override
public void onDetailItemDisconnect(Item item) {
if (item == null || item.tag == null) return;
- final PairedDevice device = (PairedDevice) item.tag;
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
if (device != null) {
mController.disconnect(device);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index cbdd138..cbe4c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.policy;
-import java.util.Set;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import java.util.Collection;
public interface BluetoothController {
void addStateChangedCallback(Callback callback);
@@ -28,32 +30,12 @@
boolean isBluetoothConnecting();
String getLastDeviceName();
void setBluetoothEnabled(boolean enabled);
- Set<PairedDevice> getPairedDevices();
- void connect(PairedDevice device);
- void disconnect(PairedDevice device);
+ Collection<CachedBluetoothDevice> getDevices();
+ void connect(CachedBluetoothDevice device);
+ void disconnect(CachedBluetoothDevice device);
public interface Callback {
void onBluetoothStateChange(boolean enabled, boolean connecting);
- void onBluetoothPairedDevicesChanged();
- }
-
- public static final class PairedDevice {
- public static int STATE_DISCONNECTED = 0;
- public static int STATE_CONNECTING = 1;
- public static int STATE_CONNECTED = 2;
- public static int STATE_DISCONNECTING = 3;
-
- public String id;
- public String name;
- public int state = STATE_DISCONNECTED;
- public Object tag;
-
- public static String stateToString(int state) {
- if (state == STATE_DISCONNECTED) return "STATE_DISCONNECTED";
- if (state == STATE_CONNECTING) return "STATE_CONNECTING";
- if (state == STATE_CONNECTED) return "STATE_CONNECTED";
- if (state == STATE_DISCONNECTING) return "STATE_DISCONNECTING";
- return "UNKNOWN";
- }
+ void onBluetoothDevicesChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 81e1e45..8d4f302 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -16,138 +16,57 @@
package com.android.systemui.statusbar.policy;
-import static android.bluetooth.BluetoothAdapter.ERROR;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothMap;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
-import android.util.SparseArray;
-import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Set;
+import java.util.Collection;
-public class BluetoothControllerImpl implements BluetoothController {
+public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
+ CachedBluetoothDevice.Callback {
private static final String TAG = "BluetoothController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // This controls the order in which we check the states. Since a device can only have
- // one state on screen, but can have multiple profiles, the later states override the
- // value of earlier states. So if a device has a profile in CONNECTING and one in
- // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often,
- // but seemed worth noting.
- private static final int[] CONNECTION_STATES = {
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_DISCONNECTING,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_CONNECTED,
- };
- // Update all the BT device states.
- private static final int MSG_UPDATE_CONNECTION_STATES = 1;
- // Update just one BT device.
- private static final int MSG_UPDATE_SINGLE_CONNECTION_STATE = 2;
- // Update whether devices are bonded or not.
- private static final int MSG_UPDATE_BONDED_DEVICES = 3;
- private static final int MSG_ADD_PROFILE = 4;
- private static final int MSG_REM_PROFILE = 5;
-
- private final Context mContext;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
- private final BluetoothAdapter mAdapter;
- private final Receiver mReceiver = new Receiver();
- private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
- private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>();
-
- private final H mHandler;
+ private final LocalBluetoothManager mLocalBluetoothManager;
private boolean mEnabled;
private boolean mConnecting;
- private BluetoothDevice mLastDevice;
+ private CachedBluetoothDevice mLastDevice;
public BluetoothControllerImpl(Context context, Looper bgLooper) {
- mContext = context;
- mHandler = new H(bgLooper);
-
- final BluetoothManager bluetoothManager =
- (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
- mAdapter = bluetoothManager.getAdapter();
- if (mAdapter == null) {
- Log.w(TAG, "Default BT adapter not found");
- return;
+ mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null);
+ if (mLocalBluetoothManager != null) {
+ mLocalBluetoothManager.getEventManager().registerCallback(this);
+ onBluetoothStateChanged(
+ mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
}
-
- mReceiver.register();
- setAdapterState(mAdapter.getState());
- updateBondedDevices();
- bindAllProfiles();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("BluetoothController state:");
- pw.print(" mAdapter="); pw.println(mAdapter);
+ pw.print(" mLocalBluetoothManager="); pw.println(mLocalBluetoothManager);
pw.print(" mEnabled="); pw.println(mEnabled);
pw.print(" mConnecting="); pw.println(mConnecting);
pw.print(" mLastDevice="); pw.println(mLastDevice);
pw.print(" mCallbacks.size="); pw.println(mCallbacks.size());
- pw.print(" mProfiles="); pw.println(profilesToString(mProfiles));
- pw.print(" mDeviceInfo.size="); pw.println(mDeviceInfo.size());
- for (int i = 0; i < mDeviceInfo.size(); i++) {
- final BluetoothDevice device = mDeviceInfo.keyAt(i);
- final DeviceInfo info = mDeviceInfo.valueAt(i);
- pw.print(" "); pw.print(deviceToString(device));
- pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
- pw.print(" "); pw.println(infoToString(info));
+ pw.println(" Bluetooth Devices:");
+ for (CachedBluetoothDevice device :
+ mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) {
+ pw.println(" " + getDeviceString(device));
}
}
- private static String infoToString(DeviceInfo info) {
- return info == null ? null : ("connectionState=" +
- connectionStateToString(CONNECTION_STATES[info.connectionStateIndex])
- + ",bonded=" + info.bonded + ",profiles="
- + profilesToString(info.connectedProfiles));
- }
-
- private static String profilesToString(SparseArray<?> profiles) {
- final int N = profiles.size();
- final StringBuffer buffer = new StringBuffer();
- buffer.append('[');
- for (int i = 0; i < N; i++) {
- if (i != 0) {
- buffer.append(',');
- }
- buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i)));
- }
- buffer.append(']');
- return buffer.toString();
+ private String getDeviceString(CachedBluetoothDevice device) {
+ return device.getName() + " " + device.getBondState() + " " + device.isConnected();
}
public void addStateChangedCallback(Callback cb) {
@@ -162,266 +81,63 @@
@Override
public boolean isBluetoothEnabled() {
- return mAdapter != null && mAdapter.isEnabled();
+ return mEnabled;
}
@Override
public boolean isBluetoothConnected() {
- return mAdapter != null
- && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED;
+ return mLocalBluetoothManager != null
+ && mLocalBluetoothManager.getBluetoothAdapter().getConnectionState()
+ == BluetoothAdapter.STATE_CONNECTED;
}
@Override
public boolean isBluetoothConnecting() {
- return mAdapter != null
- && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
+ return mConnecting;
}
@Override
public void setBluetoothEnabled(boolean enabled) {
- if (mAdapter != null) {
- if (enabled) {
- mAdapter.enable();
- } else {
- mAdapter.disable();
- }
+ if (mLocalBluetoothManager != null) {
+ mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);
}
}
@Override
public boolean isBluetoothSupported() {
- return mAdapter != null;
+ return mLocalBluetoothManager != null;
}
@Override
- public ArraySet<PairedDevice> getPairedDevices() {
- final ArraySet<PairedDevice> rt = new ArraySet<>();
- for (int i = 0; i < mDeviceInfo.size(); i++) {
- final BluetoothDevice device = mDeviceInfo.keyAt(i);
- final DeviceInfo info = mDeviceInfo.valueAt(i);
- if (!info.bonded) continue;
- final PairedDevice paired = new PairedDevice();
- paired.id = device.getAddress();
- paired.tag = device;
- paired.name = device.getAliasName();
- paired.state = connectionStateToPairedDeviceState(info.connectionStateIndex);
- rt.add(paired);
- }
- return rt;
- }
-
- private static int connectionStateToPairedDeviceState(int index) {
- int state = CONNECTION_STATES[index];
- if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
- if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
- if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
- return PairedDevice.STATE_DISCONNECTED;
+ public void connect(final CachedBluetoothDevice device) {
+ if (mLocalBluetoothManager == null || device == null) return;
+ device.connect(true);
}
@Override
- public void connect(final PairedDevice pd) {
- connect(pd, true);
- }
-
- @Override
- public void disconnect(PairedDevice pd) {
- connect(pd, false);
- }
-
- private void connect(PairedDevice pd, final boolean connect) {
- if (mAdapter == null || pd == null || pd.tag == null) return;
- final BluetoothDevice device = (BluetoothDevice) pd.tag;
- final DeviceInfo info = mDeviceInfo.get(device);
- final String action = connect ? "connect" : "disconnect";
- if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
- final ParcelUuid[] uuids = device.getUuids();
- if (uuids == null) {
- Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
- return;
- }
- SparseArray<Boolean> profiles = new SparseArray<>();
- if (connect) {
- // When connecting add every profile we can recognize by uuid.
- for (ParcelUuid uuid : uuids) {
- final int profile = uuidToProfile(uuid);
- if (profile == 0) {
- Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
- + uuidToString(uuid));
- continue;
- }
- final boolean connected = info.connectedProfiles.get(profile, false);
- if (!connected) {
- profiles.put(profile, true);
- }
- }
- } else {
- // When disconnecting, just add every profile we know they are connected to.
- profiles = info.connectedProfiles;
- }
- for (int i = 0; i < profiles.size(); i++) {
- final int profile = profiles.keyAt(i);
- if (mProfiles.indexOfKey(profile) >= 0) {
- final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile));
- final boolean ok = connect ? p.connect(device) : p.disconnect(device);
- if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
- + (ok ? "succeeded" : "failed"));
- } else {
- Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
- }
- }
+ public void disconnect(CachedBluetoothDevice device) {
+ if (mLocalBluetoothManager == null || device == null) return;
+ device.disconnect();
}
@Override
public String getLastDeviceName() {
- return mLastDevice != null ? mLastDevice.getAliasName() : null;
+ return mLastDevice != null ? mLastDevice.getName() : null;
}
- private void updateBondedDevices() {
- mHandler.removeMessages(MSG_UPDATE_BONDED_DEVICES);
- mHandler.sendEmptyMessage(MSG_UPDATE_BONDED_DEVICES);
- }
-
- private void updateConnectionStates() {
- mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
- mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
- mHandler.sendEmptyMessage(MSG_UPDATE_CONNECTION_STATES);
- }
-
- private void updateConnectionState(BluetoothDevice device, int profile, int state) {
- if (mHandler.hasMessages(MSG_UPDATE_CONNECTION_STATES)) {
- // If we are about to update all the devices, then we don't need to update this one.
- return;
- }
- mHandler.obtainMessage(MSG_UPDATE_SINGLE_CONNECTION_STATE, profile, state, device)
- .sendToTarget();
- }
-
- private void handleUpdateBondedDevices() {
- if (mAdapter == null) return;
- final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
- for (DeviceInfo info : mDeviceInfo.values()) {
- info.bonded = false;
- }
- int bondedCount = 0;
- BluetoothDevice lastBonded = null;
- if (bondedDevices != null) {
- for (BluetoothDevice bondedDevice : bondedDevices) {
- final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
- updateInfo(bondedDevice).bonded = bonded;
- if (bonded) {
- bondedCount++;
- lastBonded = bondedDevice;
- }
- }
- }
- if (mLastDevice == null && bondedCount == 1) {
- mLastDevice = lastBonded;
- }
- updateConnectionStates();
- firePairedDevicesChanged();
- }
-
- private void handleUpdateConnectionStates() {
- final int N = mDeviceInfo.size();
- for (int i = 0; i < N; i++) {
- BluetoothDevice device = mDeviceInfo.keyAt(i);
- DeviceInfo info = updateInfo(device);
- info.connectionStateIndex = 0;
- info.connectedProfiles.clear();
- for (int j = 0; j < mProfiles.size(); j++) {
- int state = mProfiles.valueAt(j).getConnectionState(device);
- handleUpdateConnectionState(device, mProfiles.keyAt(j), state);
- }
- }
- handleConnectionChange();
- firePairedDevicesChanged();
- }
-
- private void handleUpdateConnectionState(BluetoothDevice device, int profile, int state) {
- if (DEBUG) Log.d(TAG, "updateConnectionState " + BluetoothUtil.deviceToString(device)
- + " " + BluetoothUtil.profileToString(profile)
- + " " + BluetoothUtil.connectionStateToString(state));
- DeviceInfo info = updateInfo(device);
- int stateIndex = 0;
- for (int i = 0; i < CONNECTION_STATES.length; i++) {
- if (CONNECTION_STATES[i] == state) {
- stateIndex = i;
- break;
- }
- }
- info.profileStates.put(profile, stateIndex);
-
- info.connectionStateIndex = 0;
- final int N = info.profileStates.size();
- for (int i = 0; i < N; i++) {
- if (info.profileStates.valueAt(i) > info.connectionStateIndex) {
- info.connectionStateIndex = info.profileStates.valueAt(i);
- }
- }
- if (state == BluetoothProfile.STATE_CONNECTED) {
- info.connectedProfiles.put(profile, true);
- } else {
- info.connectedProfiles.remove(profile);
- }
- }
-
- private void handleConnectionChange() {
- // If we are no longer connected to the current device, see if we are connected to
- // something else, so we don't display a name we aren't connected to.
- if (mLastDevice != null &&
- CONNECTION_STATES[mDeviceInfo.get(mLastDevice).connectionStateIndex]
- != BluetoothProfile.STATE_CONNECTED) {
- // Make sure we don't keep this device while it isn't connected.
- mLastDevice = null;
- // Look for anything else connected.
- final int size = mDeviceInfo.size();
- for (int i = 0; i < size; i++) {
- BluetoothDevice device = mDeviceInfo.keyAt(i);
- DeviceInfo info = mDeviceInfo.valueAt(i);
- if (CONNECTION_STATES[info.connectionStateIndex]
- == BluetoothProfile.STATE_CONNECTED) {
- mLastDevice = device;
- break;
- }
- }
- }
- }
-
- private void bindAllProfiles() {
- // Note: This needs to contain all of the types that can be returned by BluetoothUtil
- // otherwise we can't find the profiles we need when we connect/disconnect.
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP_SINK);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.AVRCP_CONTROLLER);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET_CLIENT);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.INPUT_DEVICE);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.MAP);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.PAN);
- // Note Health is not in this list because health devices aren't 'connected'.
- // If profiles are expanded to use more than just connection state and connect/disconnect
- // then it should be added.
+ @Override
+ public Collection<CachedBluetoothDevice> getDevices() {
+ return mLocalBluetoothManager != null
+ ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
+ : null;
}
private void firePairedDevicesChanged() {
for (Callback cb : mCallbacks) {
- cb.onBluetoothPairedDevicesChanged();
+ cb.onBluetoothDevicesChanged();
}
}
- private void setAdapterState(int adapterState) {
- final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
- if (mEnabled == enabled) return;
- mEnabled = enabled;
- fireStateChange();
- }
-
- private void setConnecting(boolean connecting) {
- if (mConnecting == connecting) return;
- mConnecting = connecting;
- fireStateChange();
- }
-
private void fireStateChange() {
for (Callback cb : mCallbacks) {
fireStateChange(cb);
@@ -432,141 +148,59 @@
cb.onBluetoothStateChange(mEnabled, mConnecting);
}
- private static int getProfileFromAction(String action) {
- if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.A2DP;
- } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.HEADSET;
- } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.A2DP_SINK;
- } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.HEADSET_CLIENT;
- } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.INPUT_DEVICE;
- } else if (BluetoothMap.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.MAP;
- } else if (BluetoothPan.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.PAN;
+ private void updateConnected() {
+ if (mLastDevice != null && mLastDevice.isConnected()) {
+ // Our current device is still valid.
+ return;
}
- if (DEBUG) Log.d(TAG, "Unknown action " + action);
- return -1;
- }
-
- private final ServiceListener mProfileListener = new ServiceListener() {
- @Override
- public void onServiceDisconnected(int profile) {
- if (DEBUG) Log.d(TAG, "Disconnected from " + BluetoothUtil.profileToString(profile));
- // We lost a profile, don't do any updates until it gets removed.
- mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
- mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
- mHandler.obtainMessage(MSG_REM_PROFILE, profile, 0).sendToTarget();
- }
-
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (DEBUG) Log.d(TAG, "Connected to " + BluetoothUtil.profileToString(profile));
- mHandler.obtainMessage(MSG_ADD_PROFILE, profile, 0, proxy).sendToTarget();
- }
- };
-
- private final class Receiver extends BroadcastReceiver {
- public void register() {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
- filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
- mContext.registerReceiver(this, filter);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
- if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
- setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
- updateBondedDevices();
- if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
- } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
- updateInfo(device);
- final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
- ERROR);
+ for (CachedBluetoothDevice device : getDevices()) {
+ if (device.isConnected()) {
mLastDevice = device;
- if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
- + connectionStateToString(state) + " " + deviceToString(device));
- setConnecting(state == BluetoothAdapter.STATE_CONNECTING);
- } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
- updateInfo(device);
- mLastDevice = device;
- } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
- if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
- updateBondedDevices();
- } else {
- int profile = getProfileFromAction(intent.getAction());
- int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGE "
- + BluetoothUtil.profileToString(profile)
- + " " + BluetoothUtil.connectionStateToString(state));
- if ((profile != -1) && (state != -1)) {
- updateConnectionState(device, profile, state);
- }
}
}
}
- private DeviceInfo updateInfo(BluetoothDevice device) {
- DeviceInfo info = mDeviceInfo.get(device);
- info = info != null ? info : new DeviceInfo();
- mDeviceInfo.put(device, info);
- return info;
+ @Override
+ public void onBluetoothStateChanged(int bluetoothState) {
+ mEnabled = bluetoothState == BluetoothAdapter.STATE_ON;
+ fireStateChange();
}
- private class H extends Handler {
- public H(Looper l) {
- super(l);
- }
+ @Override
+ public void onScanningStateChanged(boolean started) {
+ // Don't care.
+ }
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_CONNECTION_STATES:
- handleUpdateConnectionStates();
- firePairedDevicesChanged();
- break;
- case MSG_UPDATE_SINGLE_CONNECTION_STATE:
- handleUpdateConnectionState((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
- handleConnectionChange();
- firePairedDevicesChanged();
- break;
- case MSG_UPDATE_BONDED_DEVICES:
- handleUpdateBondedDevices();
- firePairedDevicesChanged();
- break;
- case MSG_ADD_PROFILE:
- mProfiles.put(msg.arg1, (BluetoothProfile) msg.obj);
- handleUpdateConnectionStates();
- firePairedDevicesChanged();
- break;
- case MSG_REM_PROFILE:
- mProfiles.remove(msg.arg1);
- handleUpdateConnectionStates();
- firePairedDevicesChanged();
- break;
- }
- };
- };
+ @Override
+ public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+ cachedDevice.registerCallback(this);
+ updateConnected();
+ firePairedDevicesChanged();
+ }
- private static class DeviceInfo {
- int connectionStateIndex = 0;
- boolean bonded; // per getBondedDevices
- SparseArray<Boolean> connectedProfiles = new SparseArray<>();
- SparseArray<Integer> profileStates = new SparseArray<>();
+ @Override
+ public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+ updateConnected();
+ firePairedDevicesChanged();
+ }
+
+ @Override
+ public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+ updateConnected();
+ firePairedDevicesChanged();
+ }
+
+ @Override
+ public void onDeviceAttributesChanged() {
+ updateConnected();
+ firePairedDevicesChanged();
+ }
+
+ @Override
+ public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ mConnecting = state == BluetoothAdapter.STATE_CONNECTING;
+ mLastDevice = cachedDevice;
+ updateConnected();
+ fireStateChange();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
deleted file mode 100644
index ed8ac2c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2014 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.systemui.statusbar.policy;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothMap;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.os.ParcelUuid;
-import android.text.TextUtils;
-
-public class BluetoothUtil {
-
- public static String profileToString(int profile) {
- if (profile == BluetoothProfile.HEADSET) return "HEADSET";
- if (profile == BluetoothProfile.A2DP) return "A2DP";
- if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER";
- if (profile == BluetoothProfile.PAN) return "PAN";
- if (profile == BluetoothProfile.INPUT_DEVICE) return "INPUT_DEVICE";
- if (profile == BluetoothProfile.MAP) return "MAP";
- return "UNKNOWN(" + profile + ")";
- }
-
- public static String profileStateToString(int state) {
- if (state == BluetoothProfile.STATE_CONNECTED) return "STATE_CONNECTED";
- if (state == BluetoothProfile.STATE_CONNECTING) return "STATE_CONNECTING";
- if (state == BluetoothProfile.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
- if (state == BluetoothProfile.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
- return "STATE_UNKNOWN";
- }
-
- public static String uuidToString(ParcelUuid uuid) {
- if (BluetoothUuid.AudioSink.equals(uuid)) return "AudioSink";
- if (BluetoothUuid.AudioSource.equals(uuid)) return "AudioSource";
- if (BluetoothUuid.AdvAudioDist.equals(uuid)) return "AdvAudioDist";
- if (BluetoothUuid.HSP.equals(uuid)) return "HSP";
- if (BluetoothUuid.HSP_AG.equals(uuid)) return "HSP_AG";
- if (BluetoothUuid.Handsfree.equals(uuid)) return "Handsfree";
- if (BluetoothUuid.Handsfree_AG.equals(uuid)) return "Handsfree_AG";
- if (BluetoothUuid.AvrcpController.equals(uuid)) return "AvrcpController";
- if (BluetoothUuid.AvrcpTarget.equals(uuid)) return "AvrcpTarget";
- if (BluetoothUuid.ObexObjectPush.equals(uuid)) return "ObexObjectPush";
- if (BluetoothUuid.Hid.equals(uuid)) return "Hid";
- if (BluetoothUuid.Hogp.equals(uuid)) return "Hogp";
- if (BluetoothUuid.PANU.equals(uuid)) return "PANU";
- if (BluetoothUuid.NAP.equals(uuid)) return "NAP";
- if (BluetoothUuid.BNEP.equals(uuid)) return "BNEP";
- if (BluetoothUuid.PBAP_PSE.equals(uuid)) return "PBAP_PSE";
- if (BluetoothUuid.MAP.equals(uuid)) return "MAP";
- if (BluetoothUuid.MNS.equals(uuid)) return "MNS";
- if (BluetoothUuid.MAS.equals(uuid)) return "MAS";
- return uuid != null ? uuid.toString() : null;
- }
-
- public static String connectionStateToString(int connectionState) {
- if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
- if (connectionState == BluetoothAdapter.STATE_CONNECTED) return "STATE_CONNECTED";
- if (connectionState == BluetoothAdapter.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
- if (connectionState == BluetoothAdapter.STATE_CONNECTING) return "STATE_CONNECTING";
- return "ERROR";
- }
-
- public static String deviceToString(BluetoothDevice device) {
- return device == null ? null : (device.getAddress() + '[' + device.getAliasName() + ']');
- }
-
- public static String uuidsToString(BluetoothDevice device) {
- if (device == null) return null;
- final ParcelUuid[] ids = device.getUuids();
- if (ids == null) return null;
- final String[] tokens = new String[ids.length];
- for (int i = 0; i < tokens.length; i++) {
- tokens[i] = uuidToString(ids[i]);
- }
- return TextUtils.join(",", tokens);
- }
-
- public static int uuidToProfile(ParcelUuid uuid) {
- if (BluetoothUuid.AudioSink.equals(uuid)) return BluetoothProfile.A2DP;
- if (BluetoothUuid.AdvAudioDist.equals(uuid)) return BluetoothProfile.A2DP;
-
- if (BluetoothUuid.HSP.equals(uuid)) return BluetoothProfile.HEADSET;
- if (BluetoothUuid.Handsfree.equals(uuid)) return BluetoothProfile.HEADSET;
-
- if (BluetoothUuid.MAP.equals(uuid)) return BluetoothProfile.MAP;
- if (BluetoothUuid.MNS.equals(uuid)) return BluetoothProfile.MAP;
- if (BluetoothUuid.MAS.equals(uuid)) return BluetoothProfile.MAP;
-
- if (BluetoothUuid.AvrcpController.equals(uuid)) return BluetoothProfile.AVRCP_CONTROLLER;
-
- if (BluetoothUuid.Hid.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;
- if (BluetoothUuid.Hogp.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;
-
- if (BluetoothUuid.NAP.equals(uuid)) return BluetoothProfile.PAN;
-
- return 0;
- }
-
- public static Profile getProfile(BluetoothProfile p) {
- if (p instanceof BluetoothA2dp) return newProfile((BluetoothA2dp) p);
- if (p instanceof BluetoothHeadset) return newProfile((BluetoothHeadset) p);
- if (p instanceof BluetoothA2dpSink) return newProfile((BluetoothA2dpSink) p);
- if (p instanceof BluetoothHeadsetClient) return newProfile((BluetoothHeadsetClient) p);
- if (p instanceof BluetoothInputDevice) return newProfile((BluetoothInputDevice) p);
- if (p instanceof BluetoothMap) return newProfile((BluetoothMap) p);
- if (p instanceof BluetoothPan) return newProfile((BluetoothPan) p);
- return null;
- }
-
- private static Profile newProfile(final BluetoothA2dp a2dp) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return a2dp.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return a2dp.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothHeadset headset) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return headset.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return headset.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothA2dpSink sink) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return sink.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return sink.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothHeadsetClient client) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return client.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return client.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothInputDevice input) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return input.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return input.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothMap map) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return map.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return map.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothPan pan) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return pan.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return pan.disconnect(device);
- }
- };
- }
-
- // common abstraction for supported profiles
- public interface Profile {
- boolean connect(BluetoothDevice device);
- boolean disconnect(BluetoothDevice device);
- }
-}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 26510328..d35609e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3862,6 +3862,14 @@
Slog.d(TAG, "Ignoring non-agent system package " + pkg);
}
continue;
+ } else if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+ // Cull any packages in the 'stopped' state: they've either just been
+ // installed or have explicitly been force-stopped by the user. In both
+ // cases we do not want to launch them for backup.
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Ignoring stopped package " + pkg);
+ }
+ continue;
}
mPackages.add(info);
} catch (NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index f04487e..983d83a 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -216,3 +216,8 @@
# ---------------------------
2755 fstrim_start (time|2|3)
2756 fstrim_finish (time|2|3)
+
+# ---------------------------
+# AudioService.java
+# ---------------------------
+40000 volume_changed (stream|1), (prev_level|1), (level|1), (max_level|1), (caller|3)
diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java
index 7f98b30..d534548 100644
--- a/services/core/java/com/android/server/MidiService.java
+++ b/services/core/java/com/android/server/MidiService.java
@@ -29,6 +29,7 @@
import android.media.midi.IMidiManager;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceService;
+import android.media.midi.MidiDeviceStatus;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -147,6 +148,19 @@
}
}
+ public void deviceStatusChanged(Device device, MidiDeviceStatus status) {
+ // ignore private devices that our client cannot access
+ if (!device.isUidAllowed(mUid)) return;
+
+ try {
+ for (IMidiDeviceListener listener : mListeners) {
+ listener.onDeviceStatusChanged(status);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+
public void binderDied() {
removeClient(mToken);
}
@@ -187,6 +201,8 @@
private final class Device implements IBinder.DeathRecipient {
private final IMidiDeviceServer mServer;
private final MidiDeviceInfo mDeviceInfo;
+ private MidiDeviceStatus mDeviceStatus;
+ private IBinder mDeviceStatusToken;
// ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only)
private final ServiceInfo mServiceInfo;
// UID of device implementation
@@ -204,6 +220,33 @@
return mDeviceInfo;
}
+ public MidiDeviceStatus getDeviceStatus() {
+ return mDeviceStatus;
+ }
+
+ public void setDeviceStatus(IBinder token, MidiDeviceStatus status) {
+ mDeviceStatus = status;
+
+ if (mDeviceStatusToken == null && token != null) {
+ // register a death recipient so we can clear the status when the device dies
+ try {
+ token.linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ // reset to default status and clear the token
+ mDeviceStatus = new MidiDeviceStatus(mDeviceInfo);
+ mDeviceStatusToken = null;
+ notifyDeviceStatusChanged(Device.this, mDeviceStatus);
+ }
+ }, 0);
+ mDeviceStatusToken = token;
+ } catch (RemoteException e) {
+ // reset to default status
+ mDeviceStatus = new MidiDeviceStatus(mDeviceInfo);
+ }
+ }
+ }
+
public IMidiDeviceServer getDeviceServer() {
return mServer;
}
@@ -216,6 +259,10 @@
return (mServiceInfo == null ? null : mServiceInfo.packageName);
}
+ public int getUid() {
+ return mUid;
+ }
+
public boolean isUidAllowed(int uid) {
return (!mDeviceInfo.isPrivate() || mUid == uid);
}
@@ -302,13 +349,14 @@
@Override
public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
int numOutputPorts, Bundle properties, int type) {
- if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) {
+ int uid = Binder.getCallingUid();
+ if (type != MidiDeviceInfo.TYPE_VIRTUAL && uid != Process.SYSTEM_UID) {
throw new SecurityException("only system can create non-virtual devices");
}
synchronized (mDevicesByInfo) {
return addDeviceLocked(type, numInputPorts, numOutputPorts, properties,
- server, null, false, -1);
+ server, null, false, uid);
}
}
@@ -337,6 +385,39 @@
}
}
+ @Override
+ public MidiDeviceStatus getDeviceStatus(MidiDeviceInfo deviceInfo) {
+ Device device = mDevicesByInfo.get(deviceInfo);
+ if (device == null) {
+ throw new IllegalArgumentException("no such device for " + deviceInfo);
+ }
+ return device.getDeviceStatus();
+ }
+
+ @Override
+ public void setDeviceStatus(IBinder token, MidiDeviceStatus status) {
+ MidiDeviceInfo deviceInfo = status.getDeviceInfo();
+ Device device = mDevicesByInfo.get(deviceInfo);
+ if (device == null) {
+ // Just return quietly here if device no longer exists
+ return;
+ }
+ if (Binder.getCallingUid() != device.getUid()) {
+ throw new SecurityException("setDeviceStatus() caller UID " + Binder.getCallingUid()
+ + " does not match device's UID " + device.getUid());
+ }
+ device.setDeviceStatus(token, status);
+ notifyDeviceStatusChanged(device, status);
+ }
+
+ private void notifyDeviceStatusChanged(Device device, MidiDeviceStatus status) {
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceStatusChanged(device, status);
+ }
+ }
+ }
+
// synchronize on mDevicesByInfo
private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo,
@@ -469,17 +550,15 @@
continue;
}
- int uid = -1;
- if (isPrivate) {
- try {
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
- serviceInfo.packageName, 0);
- uid = appInfo.uid;
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "could not fetch ApplicationInfo for "
- + serviceInfo.packageName);
- continue;
- }
+ int uid;
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+ serviceInfo.packageName, 0);
+ uid = appInfo.uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "could not fetch ApplicationInfo for "
+ + serviceInfo.packageName);
+ continue;
}
synchronized (mDevicesByInfo) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 8e46c4d..794e1b0 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -106,8 +106,8 @@
}
public void scheduleCheckLocked() {
- if (mMonitors.size() == 0 && mHandler.getLooper().isIdling()) {
- // If the target looper is or just recently was idling, then
+ if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) {
+ // If the target looper has recently been polling, then
// there is no reason to enqueue our checker on it since that
// is as good as it not being deadlocked. This avoid having
// to do a context switch to check the thread. Note that we
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 8b7a6af..7d7520a 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2508,9 +2508,11 @@
}
int taskNdx = mTaskHistory.indexOf(task);
- do {
- taskTop = mTaskHistory.get(taskNdx--).getTopActivity();
- } while (taskTop == null && taskNdx >= 0);
+ if (taskNdx >= 0) {
+ do {
+ taskTop = mTaskHistory.get(taskNdx--).getTopActivity();
+ } while (taskTop == null && taskNdx >= 0);
+ }
if (topOptions != null) {
// If we got some ActivityOptions from an activity on top that
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fc949e0..1208c04 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -96,6 +96,7 @@
import android.view.accessibility.AccessibilityManager;
import com.android.internal.util.XmlUtils;
+import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import org.xmlpull.v1.XmlPullParserException;
@@ -618,7 +619,7 @@
// must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
// array initialized by updateStreamVolumeAlias()
- updateStreamVolumeAlias(false /*updateVolumes*/);
+ updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
readPersistedSettings();
mSettingsObserver = new SettingsObserver();
createStreamStates();
@@ -712,7 +713,7 @@
SENDMSG_REPLACE,
0,
0,
- null,
+ TAG,
SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
StreamOverride.init(mContext);
@@ -745,7 +746,8 @@
for (int streamType = 0; streamType < numStreamTypes; streamType++) {
if (streamType != mStreamVolumeAlias[streamType]) {
mStreamStates[streamType].
- setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]]);
+ setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]],
+ TAG);
}
// apply stream volume
if (!mStreamStates[streamType].mIsMuted) {
@@ -791,7 +793,7 @@
pw.println(Integer.toHexString(mMuteAffectedStreams));
}
- private void updateStreamVolumeAlias(boolean updateVolumes) {
+ private void updateStreamVolumeAlias(boolean updateVolumes, String caller) {
int dtmfStreamAlias;
switch (mPlatformType) {
@@ -821,7 +823,8 @@
mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
if (updateVolumes) {
- mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]);
+ mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias],
+ caller);
// apply stream mute states according to new value of mRingerModeAffectedStreams
setRingerModeInt(getRingerModeInternal(), false);
sendMsg(mAudioHandler,
@@ -950,15 +953,15 @@
///////////////////////////////////////////////////////////////////////////
/** @see AudioManager#adjustVolume(int, int) */
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage) {
+ String callingPackage, String caller) {
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
- Binder.getCallingUid());
+ caller, Binder.getCallingUid());
}
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage, int uid) {
- if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType
- + ", flags=" + flags);
+ String callingPackage, String caller, int uid) {
+ if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
+ + ", flags=" + flags + ", caller=" + caller);
int streamType;
boolean isMute = isMuteAdjust(direction);
if (mVolumeControlStream != -1) {
@@ -983,22 +986,23 @@
if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
}
- adjustStreamVolume(streamType, direction, flags, callingPackage, uid);
+ adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
}
/** @see AudioManager#adjustStreamVolume(int, int, int) */
public void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage) {
- adjustStreamVolume(streamType, direction, flags, callingPackage, Binder.getCallingUid());
+ adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
+ Binder.getCallingUid());
}
private void adjustStreamVolume(int streamType, int direction, int flags,
- String callingPackage, int uid) {
+ String callingPackage, String caller, int uid) {
if (mUseFixedVolume) {
return;
}
- if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream="+streamType+", dir="+direction
- + ", flags="+flags);
+ if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction
+ + ", flags=" + flags + ", caller=" + caller);
ensureValidDirection(direction);
ensureValidStreamType(streamType);
@@ -1119,7 +1123,8 @@
!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
mVolumeController.postDisplaySafeVolumeWarning(flags);
- } else if (streamState.adjustIndex(direction * step, device) || streamState.mIsMuted) {
+ } else if (streamState.adjustIndex(direction * step, device, caller)
+ || streamState.mIsMuted) {
// Post message to set system volume (it in turn will post a
// message to persist).
if (streamState.mIsMuted) {
@@ -1220,8 +1225,9 @@
}
};
- private void onSetStreamVolume(int streamType, int index, int flags, int device) {
- setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false);
+ private void onSetStreamVolume(int streamType, int index, int flags, int device,
+ String caller) {
+ setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false, caller);
// setting volume on ui sounds stream type also controls silent mode
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(mStreamVolumeAlias[streamType] == getUiSoundsStreamType())) {
@@ -1239,11 +1245,12 @@
/** @see AudioManager#setStreamVolume(int, int, int) */
public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
- setStreamVolume(streamType, index, flags, callingPackage, Binder.getCallingUid());
+ setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
+ Binder.getCallingUid());
}
private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
- int uid) {
+ String caller, int uid) {
if (mUseFixedVolume) {
return;
}
@@ -1310,7 +1317,7 @@
mPendingVolumeCommand = new StreamVolumeCommand(
streamType, index, flags, device);
} else {
- onSetStreamVolume(streamType, index, flags, device);
+ onSetStreamVolume(streamType, index, flags, device, caller);
index = mStreamStates[streamType].getIndex(device);
}
}
@@ -1442,10 +1449,11 @@
private void setStreamVolumeInt(int streamType,
int index,
int device,
- boolean force) {
+ boolean force,
+ String caller) {
VolumeStreamState streamState = mStreamStates[streamType];
- if (streamState.setIndex(index, device) || force) {
+ if (streamState.setIndex(index, device, caller) || force) {
// Post message to set system volume (it in turn will post a message
// to persist).
sendMsg(mAudioHandler,
@@ -1874,7 +1882,7 @@
if (index < 0) {
Log.w(TAG, "unregistered setMode() client died");
} else {
- newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid);
+ newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG);
}
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
@@ -1904,8 +1912,8 @@
}
/** @see AudioManager#setMode(int) */
- public void setMode(int mode, IBinder cb) {
- if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ")"); }
+ public void setMode(int mode, IBinder cb, String callingPackage) {
+ if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); }
if (!checkAudioSettingsPermission("setMode()")) {
return;
}
@@ -1928,7 +1936,7 @@
if (mode == AudioSystem.MODE_CURRENT) {
mode = mMode;
}
- newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid());
+ newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode
@@ -1940,8 +1948,9 @@
// must be called synchronized on mSetModeDeathHandlers
// setModeInt() returns a valid PID if the audio mode was successfully set to
// any mode other than NORMAL.
- private int setModeInt(int mode, IBinder cb, int pid) {
- if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ")"); }
+ private int setModeInt(int mode, IBinder cb, int pid, String caller) {
+ if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller="
+ + caller + ")"); }
int newModeOwnerPid = 0;
if (cb == null) {
Log.e(TAG, "setModeInt() called with null binder");
@@ -2021,9 +2030,9 @@
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
int device = getDeviceForStream(streamType);
int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
- setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true);
+ setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller);
- updateStreamVolumeAlias(true /*updateVolumes*/);
+ updateStreamVolumeAlias(true /*updateVolumes*/, caller);
}
return newModeOwnerPid;
}
@@ -2278,7 +2287,7 @@
Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0, UserHandle.USER_CURRENT),
0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX);
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) {
- enforceSafeMediaVolume();
+ enforceSafeMediaVolume(TAG);
}
}
}
@@ -2838,7 +2847,7 @@
}
};
- private void onCheckMusicActive() {
+ private void onCheckMusicActive(String caller) {
synchronized (mSafeMediaVolumeState) {
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
@@ -2849,7 +2858,7 @@
SENDMSG_REPLACE,
0,
0,
- null,
+ caller,
MUSIC_ACTIVE_POLL_PERIOD_MS);
int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
@@ -2857,7 +2866,7 @@
// Approximate cumulative active music time
mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
- setSafeMediaVolumeEnabled(true);
+ setSafeMediaVolumeEnabled(true, caller);
mMusicActiveMs = 0;
}
saveMusicActiveMs();
@@ -2871,7 +2880,7 @@
mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
}
- private void onConfigureSafeVolume(boolean force) {
+ private void onConfigureSafeVolume(boolean force, String caller) {
synchronized (mSafeMediaVolumeState) {
int mcc = mContext.getResources().getConfiguration().mcc;
if ((mMcc != mcc) || ((mMcc == 0) && force)) {
@@ -2893,7 +2902,7 @@
if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
if (mMusicActiveMs == 0) {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
- enforceSafeMediaVolume();
+ enforceSafeMediaVolume(caller);
} else {
// We have existing playback time recorded, already confirmed.
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
@@ -3293,21 +3302,24 @@
* A class just for packaging up a set of connection parameters.
*/
private class WiredDeviceConnectionState {
- public int mType;
- public int mState;
- public String mAddress;
- public String mName;
+ public final int mType;
+ public final int mState;
+ public final String mAddress;
+ public final String mName;
+ public final String mCaller;
- public WiredDeviceConnectionState(int type, int state, String address, String name) {
+ public WiredDeviceConnectionState(int type, int state, String address, String name,
+ String caller) {
mType = type;
mState = state;
mAddress = address;
mName = name;
+ mCaller = caller;
}
}
- public void setWiredDeviceConnectionState(int type, int state, String address,
- String name) {
+ public void setWiredDeviceConnectionState(int type, int state, String address, String name,
+ String caller) {
synchronized (mConnectedDevices) {
if (DEBUG_DEVICES) {
Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
@@ -3318,7 +3330,7 @@
MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
0,
0,
- new WiredDeviceConnectionState(type, state, address, name),
+ new WiredDeviceConnectionState(type, state, address, name, caller),
delay);
}
}
@@ -3482,12 +3494,11 @@
}
}
- public boolean adjustIndex(int deltaIndex, int device) {
- return setIndex(getIndex(device) + deltaIndex,
- device);
+ public boolean adjustIndex(int deltaIndex, int device, String caller) {
+ return setIndex(getIndex(device) + deltaIndex, device, caller);
}
- public boolean setIndex(int index, int device) {
+ public boolean setIndex(int index, int device, String caller) {
boolean changed = false;
int oldIndex;
synchronized (VolumeStreamState.class) {
@@ -3511,11 +3522,10 @@
if (streamType != mStreamType &&
mStreamVolumeAlias[streamType] == mStreamType) {
int scaledIndex = rescaleIndex(index, mStreamType, streamType);
- mStreamStates[streamType].setIndex(scaledIndex,
- device);
+ mStreamStates[streamType].setIndex(scaledIndex, device, caller);
if (currentDevice) {
mStreamStates[streamType].setIndex(scaledIndex,
- getDeviceForStream(streamType));
+ getDeviceForStream(streamType), caller);
}
}
}
@@ -3524,6 +3534,15 @@
if (changed) {
oldIndex = (oldIndex + 5) / 10;
index = (index + 5) / 10;
+ // log base stream changes to the event log
+ if (mStreamVolumeAlias[mStreamType] == mStreamType) {
+ if (caller == null) {
+ Log.w(TAG, "No caller for volume_changed event", new Throwable());
+ }
+ EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10,
+ caller);
+ }
+ // fire changed intents for all streams
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
sendBroadcastToAll(mVolumeChanged);
@@ -3546,7 +3565,7 @@
return mIndexMax;
}
- public void setAllIndexes(VolumeStreamState srcStream) {
+ public void setAllIndexes(VolumeStreamState srcStream, String caller) {
synchronized (VolumeStreamState.class) {
int srcStreamType = srcStream.getStreamType();
// apply default device volume from source stream to all devices first in case
@@ -3563,7 +3582,7 @@
index = srcMap.valueAt(i);
index = rescaleIndex(index, srcStreamType, mStreamType);
- setIndex(index, device);
+ setIndex(index, device, caller);
}
}
}
@@ -4160,7 +4179,7 @@
{ WiredDeviceConnectionState connectState =
(WiredDeviceConnectionState)msg.obj;
onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
- connectState.mAddress, connectState.mName);
+ connectState.mAddress, connectState.mName, connectState.mCaller);
mAudioEventWakeLock.release();
}
break;
@@ -4196,7 +4215,7 @@
}
case MSG_CHECK_MUSIC_ACTIVE:
- onCheckMusicActive();
+ onCheckMusicActive((String) msg.obj);
break;
case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
@@ -4205,7 +4224,8 @@
case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
- onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED));
+ onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED),
+ (String) msg.obj);
break;
case MSG_PERSIST_SAFE_VOLUME_STATE:
onPersistSafeVolumeState(msg.arg1);
@@ -4592,11 +4612,13 @@
}
private void onSetWiredDeviceConnectionState(int device, int state, String address,
- String deviceName) {
+ String deviceName, String caller) {
if (DEBUG_DEVICES) {
- Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device) +
- " state:" + Integer.toHexString(state) + " address:" + address +
- " deviceName:" + deviceName + ");");
+ Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
+ + " state:" + Integer.toHexString(state)
+ + " address:" + address
+ + " deviceName:" + deviceName
+ + " caller: " + caller + ");");
}
synchronized (mConnectedDevices) {
@@ -4621,7 +4643,7 @@
SENDMSG_REPLACE,
0,
0,
- null,
+ caller,
MUSIC_ACTIVE_POLL_PERIOD_MS);
}
// Television devices without CEC service apply software volume on HDMI output
@@ -4974,7 +4996,7 @@
SENDMSG_REPLACE,
0,
0,
- null,
+ TAG,
0);
boolean cameraSoundForced = readCameraSoundForced();
@@ -4994,7 +5016,7 @@
mRingerModeAffectedStreams &=
~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
} else {
- s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]);
+ s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], TAG);
mRingerModeAffectedStreams |=
(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
}
@@ -5159,13 +5181,13 @@
private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed
- private void setSafeMediaVolumeEnabled(boolean on) {
+ private void setSafeMediaVolumeEnabled(boolean on, String caller) {
synchronized (mSafeMediaVolumeState) {
if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) &&
(mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) {
if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
- enforceSafeMediaVolume();
+ enforceSafeMediaVolume(caller);
} else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
mMusicActiveMs = 1; // nonzero = confirmed
@@ -5175,14 +5197,14 @@
SENDMSG_REPLACE,
0,
0,
- null,
+ caller,
MUSIC_ACTIVE_POLL_PERIOD_MS);
}
}
}
}
- private void enforceSafeMediaVolume() {
+ private void enforceSafeMediaVolume(String caller) {
VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
int devices = mSafeMediaVolumeDevices;
int i = 0;
@@ -5194,7 +5216,7 @@
}
int index = streamState.getIndex(device);
if (index > mSafeMediaVolumeIndex) {
- streamState.setIndex(mSafeMediaVolumeIndex, device);
+ streamState.setIndex(mSafeMediaVolumeIndex, device, caller);
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
@@ -5220,15 +5242,16 @@
}
@Override
- public void disableSafeMediaVolume() {
+ public void disableSafeMediaVolume(String callingPackage) {
enforceVolumeController("disable the safe media volume");
synchronized (mSafeMediaVolumeState) {
- setSafeMediaVolumeEnabled(false);
+ setSafeMediaVolumeEnabled(false, callingPackage);
if (mPendingVolumeCommand != null) {
onSetStreamVolume(mPendingVolumeCommand.mStreamType,
mPendingVolumeCommand.mIndex,
mPendingVolumeCommand.mFlags,
- mPendingVolumeCommand.mDevice);
+ mPendingVolumeCommand.mDevice,
+ callingPackage);
mPendingVolumeCommand = null;
}
}
@@ -5604,19 +5627,21 @@
String callingPackage, int uid) {
// direction and stream type swap here because the public
// adjustSuggested has a different order than the other methods.
- adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage, uid);
+ adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage,
+ callingPackage, uid);
}
@Override
public void adjustStreamVolumeForUid(int streamType, int direction, int flags,
String callingPackage, int uid) {
- adjustStreamVolume(streamType, direction, flags, callingPackage, uid);
+ adjustStreamVolume(streamType, direction, flags, callingPackage,
+ callingPackage, uid);
}
@Override
public void setStreamVolumeForUid(int streamType, int direction, int flags,
String callingPackage, int uid) {
- setStreamVolume(streamType, direction, flags, callingPackage, uid);
+ setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid);
}
@Override
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 2ac6ccc..b398f41 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -320,6 +320,12 @@
checkPermission(USE_FINGERPRINT);
return mHalDeviceId != 0;
}
+
+ @Override
+ public void rename(int fpId, String name) {
+ checkPermission(MANAGE_FINGERPRINT);
+ // TODO
+ }
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 72205d6..a530dfa 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -880,7 +880,7 @@
try {
String packageName = getContext().getOpPackageName();
mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
- flags, packageName);
+ flags, packageName, TAG);
} catch (RemoteException e) {
Log.e(TAG, "Error adjusting default volume.", e);
}
diff --git a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
index b8a3155..b99c436 100644
--- a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
+++ b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
@@ -25,7 +25,11 @@
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
+import android.os.Build;
+import android.os.Handler;
import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
import android.view.Display;
import com.android.server.LocalServices;
@@ -37,7 +41,7 @@
private static final String TAG = "BurnInProtection";
// Default value when max burnin radius is not set.
- public static final int BURN_IN_RADIUS_MAX_DEFAULT = -1;
+ public static final int BURN_IN_MAX_RADIUS_DEFAULT = -1;
private static final long BURNIN_PROTECTION_WAKEUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1);
private static final long BURNIN_PROTECTION_MINIMAL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(10);
@@ -74,23 +78,19 @@
updateBurnInProtection();
}
};
-
- public BurnInProtectionHelper(Context context) {
+
+ public BurnInProtectionHelper(Context context, int minHorizontalOffset,
+ int maxHorizontalOffset, int minVerticalOffset, int maxVerticalOffset,
+ int maxOffsetRadius) {
final Resources resources = context.getResources();
- mMinHorizontalBurnInOffset = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset);
- mMaxHorizontalBurnInOffset = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset);
- mMinVerticalBurnInOffset = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset);
- mMaxVerticalBurnInOffset = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset);
- int burnInRadiusMax = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMaxRadius);
- if (burnInRadiusMax != BURN_IN_RADIUS_MAX_DEFAULT) {
- mBurnInRadiusMaxSquared = burnInRadiusMax * burnInRadiusMax;
+ mMinHorizontalBurnInOffset = minHorizontalOffset;
+ mMaxHorizontalBurnInOffset = maxHorizontalOffset;
+ mMinVerticalBurnInOffset = minVerticalOffset;
+ mMaxVerticalBurnInOffset = maxHorizontalOffset;
+ if (maxOffsetRadius != BURN_IN_MAX_RADIUS_DEFAULT) {
+ mBurnInRadiusMaxSquared = maxOffsetRadius * maxOffsetRadius;
} else {
- mBurnInRadiusMaxSquared = BURN_IN_RADIUS_MAX_DEFAULT;
+ mBurnInRadiusMaxSquared = BURN_IN_MAX_RADIUS_DEFAULT;
}
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -175,7 +175,7 @@
}
}
// If we are outside of the radius, let's try again.
- } while (mBurnInRadiusMaxSquared != BURN_IN_RADIUS_MAX_DEFAULT
+ } while (mBurnInRadiusMaxSquared != BURN_IN_MAX_RADIUS_DEFAULT
&& mLastBurnInXOffset * mLastBurnInXOffset + mLastBurnInYOffset * mLastBurnInYOffset
> mBurnInRadiusMaxSquared);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 62e7af4..cdd6c7f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -49,6 +49,7 @@
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.media.session.MediaSessionLegacyHelper;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.FactoryTest;
@@ -94,6 +95,7 @@
import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -1185,6 +1187,12 @@
}
};
+ private boolean isRoundWindow() {
+ return mContext.getResources().getBoolean(com.android.internal.R.bool.config_windowIsRound)
+ || (Build.HARDWARE.contains("goldfish")
+ && SystemProperties.getBoolean(ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false));
+ }
+
/** {@inheritDoc} */
@Override
public void init(Context context, IWindowManager windowManager,
@@ -1194,9 +1202,40 @@
mWindowManagerFuncs = windowManagerFuncs;
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
- if (context.getResources().getBoolean(
- com.android.internal.R.bool.config_enableBurnInProtection)){
- mBurnInProtectionHelper = new BurnInProtectionHelper(context);
+
+ // Init display burn-in protection
+ boolean burnInProtectionEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableBurnInProtection);
+ // Allow a system property to override this. Used by developer settings.
+ boolean burnInProtectionDevMode =
+ SystemProperties.getBoolean("persist.debug.force_burn_in", false);
+ if (burnInProtectionEnabled || burnInProtectionDevMode) {
+ final int minHorizontal;
+ final int maxHorizontal;
+ final int minVertical;
+ final int maxVertical;
+ final int maxRadius;
+ if (burnInProtectionDevMode) {
+ minHorizontal = -8;
+ maxHorizontal = 8;
+ minVertical = -8;
+ maxVertical = -4;
+ maxRadius = (isRoundWindow()) ? 6 : -1;
+ } else {
+ Resources resources = context.getResources();
+ minHorizontal = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset);
+ maxHorizontal = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset);
+ minVertical = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset);
+ maxVertical = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset);
+ maxRadius = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMaxRadius);
+ }
+ mBurnInProtectionHelper = new BurnInProtectionHelper(
+ context, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
}
mHandler = new PolicyHandler();
@@ -4872,7 +4911,7 @@
case KeyEvent.KEYCODE_VOLUME_UP:
try {
getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
- AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
} catch (RemoteException e) {
Log.e(TAG, "Error dispatching volume up in dispatchTvAudioEvent.", e);
}
@@ -4880,7 +4919,7 @@
case KeyEvent.KEYCODE_VOLUME_DOWN:
try {
getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
- AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
} catch (RemoteException e) {
Log.e(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e);
}
@@ -4890,7 +4929,7 @@
if (event.getRepeatCount() == 0) {
getAudioService().adjustSuggestedStreamVolume(
AudioManager.ADJUST_TOGGLE_MUTE,
- AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
}
} catch (RemoteException e) {
Log.e(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index ef70895..4c06cbe 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -977,7 +977,9 @@
+ " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE"
+ " transit=" + transit + " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
- a = createClipRevealAnimationLocked(transit, enter, appWidth, appHeight);
+ a = createClipRevealAnimationLocked(transit, enter,
+ containingFrame.right - containingFrame.left,
+ containingFrame.bottom - containingFrame.top);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 89ed5b7..ac1b0f1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1139,6 +1139,8 @@
mShownAlpha *= appTransformation.getAlpha();
if (appTransformation.hasClipRect()) {
mClipRect.set(appTransformation.getClipRect());
+ // Account for non-fullscreen windows
+ mClipRect.offset(frame.left, frame.top);
if (mWin.mHScale > 0) {
mClipRect.left /= mWin.mHScale;
mClipRect.right /= mWin.mHScale;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index b0e30b5..78fbe16 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -189,7 +189,7 @@
" addr:" + address + " name:" + audioDevice.mDeviceName);
}
mAudioService.setWiredDeviceConnectionState(
- device, state, address, audioDevice.mDeviceName);
+ device, state, address, audioDevice.mDeviceName, TAG);
}
// Capture Device
@@ -198,7 +198,7 @@
AudioSystem.DEVICE_IN_USB_ACCESSORY :
AudioSystem.DEVICE_IN_USB_DEVICE);
mAudioService.setWiredDeviceConnectionState(
- device, state, address, audioDevice.mDeviceName);
+ device, state, address, audioDevice.mDeviceName, TAG);
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index f927965..725f393 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -121,7 +121,7 @@
int outputCount = mOutputStreams.length;
mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount,
- properties, MidiDeviceInfo.TYPE_USB);
+ properties, MidiDeviceInfo.TYPE_USB, null);
if (mServer == null) {
return false;
}