Merge "Removed a loop for buffer lookup"
diff --git a/api/current.txt b/api/current.txt
index 2a7e2f7..9e94a0d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2402,6 +2402,16 @@
}
+package android.annotation {
+
+ public abstract class SuppressLint implements java.lang.annotation.Annotation {
+ }
+
+ public abstract class TargetApi implements java.lang.annotation.Annotation {
+ }
+
+}
+
package android.app {
public abstract class ActionBar {
@@ -4660,10 +4670,9 @@
public abstract class AsyncTaskLoader extends android.content.Loader {
ctor public AsyncTaskLoader(android.content.Context);
- method public boolean cancelLoad();
- method protected boolean isLoadInBackgroundCanceled();
+ method public void cancelLoadInBackground();
+ method public boolean isLoadInBackgroundCanceled();
method public abstract D loadInBackground();
- method protected void onCancelLoadInBackground();
method public void onCanceled(D);
method protected D onLoadInBackground();
method public void setUpdateThrottle(long);
@@ -5630,6 +5639,7 @@
field public static final int FLAG_GRANT_READ_URI_PERMISSION = 1; // 0x1
field public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 2; // 0x2
field public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32; // 0x20
+ field public static final int FLAG_RECEIVER_FOREGROUND = 268435456; // 0x10000000
field public static final int FLAG_RECEIVER_REGISTERED_ONLY = 1073741824; // 0x40000000
field public static final int FLAG_RECEIVER_REPLACE_PENDING = 536870912; // 0x20000000
field public static final java.lang.String METADATA_DOCK_HOME = "android.dock_home";
@@ -5754,7 +5764,9 @@
public class Loader {
ctor public Loader(android.content.Context);
method public void abandon();
+ method public boolean cancelLoad();
method public java.lang.String dataToString(D);
+ method public void deliverCancellation();
method public void deliverResult(D);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void forceLoad();
@@ -5764,23 +5776,30 @@
method public boolean isReset();
method public boolean isStarted();
method protected void onAbandon();
+ method protected boolean onCancelLoad();
method public void onContentChanged();
method protected void onForceLoad();
method protected void onReset();
method protected void onStartLoading();
method protected void onStopLoading();
method public void registerListener(int, android.content.Loader.OnLoadCompleteListener<D>);
+ method public void registerOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
method public void reset();
method public final void startLoading();
method public void stopLoading();
method public boolean takeContentChanged();
method public void unregisterListener(android.content.Loader.OnLoadCompleteListener<D>);
+ method public void unregisterOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
}
public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
ctor public Loader.ForceLoadContentObserver();
}
+ public static abstract interface Loader.OnLoadCanceledListener {
+ method public abstract void onLoadCanceled(android.content.Loader<D>);
+ }
+
public static abstract interface Loader.OnLoadCompleteListener {
method public abstract void onLoadComplete(android.content.Loader<D>, D);
}
@@ -9083,14 +9102,17 @@
ctor public GradientDrawable(android.graphics.drawable.GradientDrawable.Orientation, int[]);
method public void draw(android.graphics.Canvas);
method public int getOpacity();
+ method public android.graphics.drawable.GradientDrawable.Orientation getOrientation();
method public void setAlpha(int);
method public void setColor(int);
method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setColors(int[]);
method public void setCornerRadii(float[]);
method public void setCornerRadius(float);
method public void setGradientCenter(float, float);
method public void setGradientRadius(float);
method public void setGradientType(int);
+ method public void setOrientation(android.graphics.drawable.GradientDrawable.Orientation);
method public void setShape(int);
method public void setSize(int, int);
method public void setStroke(int, int);
@@ -11617,7 +11639,7 @@
method public void setNetworkPreference(int);
method public int startUsingNetworkFeature(int, java.lang.String);
method public int stopUsingNetworkFeature(int, java.lang.String);
- field public static final java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
+ field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
field public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
field public static final java.lang.String EXTRA_EXTRA_INFO = "extraInfo";
@@ -17561,6 +17583,7 @@
field public static final java.lang.String ALARM_ALERT = "alarm_alert";
field public static final java.lang.String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities";
field public static final deprecated java.lang.String ANDROID_ID = "android_id";
+ field public static final java.lang.String ANIMATOR_DURATION_SCALE = "animator_duration_scale";
field public static final java.lang.String APPEND_FOR_LAST_AUDIBLE = "_last_audible";
field public static final java.lang.String AUTO_TIME = "auto_time";
field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone";
@@ -23828,6 +23851,9 @@
method public android.view.ViewPropertyAnimator translationXBy(float);
method public android.view.ViewPropertyAnimator translationY(float);
method public android.view.ViewPropertyAnimator translationYBy(float);
+ method public android.view.ViewPropertyAnimator withEndAction(java.lang.Runnable);
+ method public android.view.ViewPropertyAnimator withLayer();
+ method public android.view.ViewPropertyAnimator withStartAction(java.lang.Runnable);
method public android.view.ViewPropertyAnimator x(float);
method public android.view.ViewPropertyAnimator xBy(float);
method public android.view.ViewPropertyAnimator y(float);
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 83c881a..16dc517 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -122,7 +122,7 @@
dump_file("NETWORK DEV INFO", "/proc/net/dev");
dump_file("QTAGUID NETWORK INTERFACES INFO", "/proc/net/xt_qtaguid/iface_stat_all");
dump_file("QTAGUID CTRL INFO", "/proc/net/xt_qtaguid/ctrl");
- run_command("QTAGUID STATS INFO", 10, "su", "root", "cat", "/proc/net/xt_qtaguid/stats", NULL);
+ dump_file("QTAGUID STATS INFO", "/proc/net/xt_qtaguid/stats");
dump_file("NETWORK ROUTES", "/proc/net/route");
dump_file("NETWORK ROUTES IPV6", "/proc/net/ipv6_route");
@@ -334,7 +334,7 @@
}
/* switch to non-root user and group */
- gid_t groups[] = { AID_LOG, AID_SDCARD_RW, AID_MOUNT, AID_INET };
+ gid_t groups[] = { AID_LOG, AID_SDCARD_RW, AID_MOUNT, AID_INET, AID_NET_BW_STATS };
if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) {
ALOGE("Unable to setgroups, aborting: %s\n", strerror(errno));
return -1;
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 211be52..a463a62 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,8 +17,10 @@
package android.accessibilityservice;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
@@ -218,10 +220,17 @@
private static final String LOG_TAG = "AccessibilityService";
- private AccessibilityServiceInfo mInfo;
+ interface Callbacks {
+ public void onAccessibilityEvent(AccessibilityEvent event);
+ public void onInterrupt();
+ public void onServiceConnected();
+ public void onSetConnectionId(int connectionId);
+ }
private int mConnectionId;
+ private AccessibilityServiceInfo mInfo;
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -282,27 +291,49 @@
*/
@Override
public final IBinder onBind(Intent intent) {
- return new IEventListenerWrapper(this);
+ return new IEventListenerWrapper(this, getMainLooper(), new Callbacks() {
+ @Override
+ public void onServiceConnected() {
+ AccessibilityService.this.onServiceConnected();
+ }
+
+ @Override
+ public void onInterrupt() {
+ AccessibilityService.this.onInterrupt();
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ AccessibilityService.this.onAccessibilityEvent(event);
+ }
+
+ @Override
+ public void onSetConnectionId( int connectionId) {
+ mConnectionId = connectionId;
+ }
+ });
}
/**
* Implements the internal {@link IEventListener} interface to convert
* incoming calls to it back to calls on an {@link AccessibilityService}.
*/
- class IEventListenerWrapper extends IEventListener.Stub
+ static class IEventListenerWrapper extends IEventListener.Stub
implements HandlerCaller.Callback {
+ static final int NO_ID = -1;
+
private static final int DO_SET_SET_CONNECTION = 10;
private static final int DO_ON_INTERRUPT = 20;
private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
private final HandlerCaller mCaller;
- private final AccessibilityService mTarget;
+ private final Callbacks mCallback;
- public IEventListenerWrapper(AccessibilityService context) {
- mTarget = context;
- mCaller = new HandlerCaller(context, this);
+ public IEventListenerWrapper(Context context, Looper looper, Callbacks callback) {
+ mCallback = callback;
+ mCaller = new HandlerCaller(context, looper, this);
}
public void setConnection(IAccessibilityServiceConnection connection, int connectionId) {
@@ -326,12 +357,13 @@
case DO_ON_ACCESSIBILITY_EVENT :
AccessibilityEvent event = (AccessibilityEvent) message.obj;
if (event != null) {
- mTarget.onAccessibilityEvent(event);
+ AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
+ mCallback.onAccessibilityEvent(event);
event.recycle();
}
return;
case DO_ON_INTERRUPT :
- mTarget.onInterrupt();
+ mCallback.onInterrupt();
return;
case DO_SET_SET_CONNECTION :
final int connectionId = message.arg1;
@@ -340,12 +372,11 @@
if (connection != null) {
AccessibilityInteractionClient.getInstance().addConnection(connectionId,
connection);
- mConnectionId = connectionId;
- mTarget.onServiceConnected();
+ mCallback.onSetConnectionId(connectionId);
+ mCallback.onServiceConnected();
} else {
AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
- mConnectionId = AccessibilityInteractionClient.NO_ID;
- // TODO: Do we need a onServiceDisconnected callback?
+ mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
}
return;
default :
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index e53b313..c9468eb 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -32,8 +32,13 @@
/**
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
*
- * @param accessibilityWindowId A unique window id.
- * @param accessibilityNodeId A unique view id or virtual descendant id.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * to start from the root.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
@@ -46,57 +51,58 @@
/**
* Finds {@link AccessibilityNodeInfo}s by View text. The match is case
* insensitive containment. The search is performed in the window whose
- * id is specified and starts from the View whose accessibility id is
+ * id is specified and starts from the node whose accessibility id is
* specified.
*
- * @param text The searched text.
- * @param accessibilityWindowId A unique window id.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
* @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link android.view.View#NO_ID} to start from the root.
- * @param interactionId The id of the interaction for matching with the callback result.
- * @param callback Callback which to receive the result.
- * @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
- */
- float findAccessibilityNodeInfosByText(String text, int accessibilityWindowId,
- long accessibilityNodeId, int interractionId,
- IAccessibilityInteractionConnectionCallback callback, long threadId);
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
- * insensitive containment. The search is performed in the currently
- * active window and start from the root View in the window.
- *
+ * where to start the search. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * to start from the root.
* @param text The searched text.
- * @param accessibilityId The id of the view from which to start searching.
- * Use {@link android.view.View#NO_ID} to start from the root.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
* @return The current window scale, where zero means a failure.
*/
- float findAccessibilityNodeInfosByTextInActiveWindow(String text,
- int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ float findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
+ String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
long threadId);
/**
- * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed
- * in the currently active window and starts from the root View in the window.
+ * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
+ * the window whose id is specified and starts from the node whose accessibility
+ * id is specified.
*
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * to start from the root.
* @param id The id of the node.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
* @return The current window scale, where zero means a failure.
*/
- float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long threadId);
+ float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId,
+ int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ long threadId);
/**
* Performs an accessibility action on an {@link AccessibilityNodeInfo}.
*
- * @param accessibilityWindowId The id of the window.
- * @param accessibilityNodeId A unique view id or virtual descendant id.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * to start from the root.
* @param action The action to perform.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
new file mode 100644
index 0000000..9d48efc
--- /dev/null
+++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2012 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.accessibilityservice;
+
+import android.accessibilityservice.AccessibilityService.Callbacks;
+import android.accessibilityservice.AccessibilityService.IEventListenerWrapper;
+import android.content.Context;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityManager;
+
+import com.android.internal.util.Predicate;
+
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This class represents a bridge that can be used for UI test
+ * automation. It is responsible for connecting to the system,
+ * keeping track of the last accessibility event, and exposing
+ * window content querying APIs. This class is designed to be
+ * used from both an Android application and a Java program
+ * run from the shell.
+ *
+ * @hide
+ */
+public class UiTestAutomationBridge {
+
+ private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName();
+
+ public static final int ACTIVE_WINDOW_ID = -1;
+
+ public static final long ROOT_NODE_ID = -1;
+
+ private static final int TIMEOUT_REGISTER_SERVICE = 5000;
+
+ private final Object mLock = new Object();
+
+ private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
+
+ private IEventListenerWrapper mListener;
+
+ private AccessibilityEvent mLastEvent;
+
+ private volatile boolean mWaitingForEventDelivery;
+
+ private volatile boolean mUnprocessedEventAvailable;
+
+ /**
+ * Gets the last received {@link AccessibilityEvent}.
+ *
+ * @return The event.
+ */
+ public AccessibilityEvent getLastAccessibilityEvent() {
+ return mLastEvent;
+ }
+
+ /**
+ * Callback for receiving an {@link AccessibilityEvent}.
+ *
+ * <strong>Note:</strong> This method is <strong>NOT</strong>
+ * executed on the application main thread. The client are
+ * responsible for proper synchronization.
+ *
+ * @param event The received event.
+ */
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ /* hook - do nothing */
+ }
+
+ /**
+ * Callback for requests to stop feedback.
+ *
+ * <strong>Note:</strong> This method is <strong>NOT</strong>
+ * executed on the application main thread. The client are
+ * responsible for proper synchronization.
+ */
+ public void onInterrupt() {
+ /* hook - do nothing */
+ }
+
+ /**
+ * Connects this service.
+ *
+ * @throws IllegalStateException If already connected.
+ */
+ public void connect() {
+ if (isConnected()) {
+ throw new IllegalStateException("Already connected.");
+ }
+
+ // Serialize binder calls to a handler on a dedicated thread
+ // different from the main since we expose APIs that block
+ // the main thread waiting for a result the deliver of which
+ // on the main thread will prevent that thread from waking up.
+ // The serialization is needed also to ensure that events are
+ // examined in delivery order. Otherwise, a fair locking
+ // is needed for making sure the binder calls are interleaved
+ // with check for the expected event and also to make sure the
+ // binder threads are allowed to proceed in the received order.
+ HandlerThread handlerThread = new HandlerThread("UiTestAutomationBridge");
+ handlerThread.start();
+ Looper looper = handlerThread.getLooper();
+
+ mListener = new IEventListenerWrapper(null, looper, new Callbacks() {
+ @Override
+ public void onServiceConnected() {
+ /* do nothing */
+ }
+
+ @Override
+ public void onInterrupt() {
+ UiTestAutomationBridge.this.onInterrupt();
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ while (true) {
+ if (!mWaitingForEventDelivery) {
+ break;
+ }
+ if (!mUnprocessedEventAvailable) {
+ mUnprocessedEventAvailable = true;
+ mLastEvent = AccessibilityEvent.obtain(event);
+ mLock.notifyAll();
+ break;
+ }
+ try {
+ mLock.wait();
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ UiTestAutomationBridge.this.onAccessibilityEvent(event);
+ }
+
+ @Override
+ public void onSetConnectionId(int connectionId) {
+ synchronized (mLock) {
+ mConnectionId = connectionId;
+ mLock.notifyAll();
+ }
+ }
+ });
+
+ final IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+
+ final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
+
+ try {
+ manager.registerUiTestAutomationService(mListener, info);
+ } catch (RemoteException re) {
+ throw new IllegalStateException("Cound not register UiAutomationService.", re);
+ }
+
+ synchronized (mLock) {
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ if (isConnected()) {
+ return;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = TIMEOUT_REGISTER_SERVICE - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new IllegalStateException("Cound not register UiAutomationService.");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ /**
+ * Disconnects this service.
+ *
+ * @throws IllegalStateException If already disconnected.
+ */
+ public void disconnect() {
+ if (!isConnected()) {
+ throw new IllegalStateException("Already disconnected.");
+ }
+
+ IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+
+ try {
+ manager.unregisterUiTestAutomationService(mListener);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while unregistering UiTestAutomationService", re);
+ }
+ }
+
+ /**
+ * Gets whether this service is connected.
+ *
+ * @return True if connected.
+ */
+ public boolean isConnected() {
+ return (mConnectionId != AccessibilityInteractionClient.NO_ID);
+ }
+
+ /**
+ * Executes a command and waits for a specific accessibility event type up
+ * to a given timeout.
+ *
+ * @param command The command to execute before starting to wait for the event.
+ * @param predicate Predicate for recognizing the awaited event.
+ * @param timeoutMillis The max wait time in milliseconds.
+ */
+ public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
+ Predicate<AccessibilityEvent> predicate, long timeoutMillis)
+ throws TimeoutException, Exception {
+ synchronized (mLock) {
+ // Prepare to wait for an event.
+ mWaitingForEventDelivery = true;
+ mUnprocessedEventAvailable = false;
+ if (mLastEvent != null) {
+ mLastEvent.recycle();
+ mLastEvent = null;
+ }
+ // Execute the command.
+ command.run();
+ // Wait for the event.
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ // If the expected event is received, that's it.
+ if ((mUnprocessedEventAvailable && predicate.apply(mLastEvent))) {
+ mWaitingForEventDelivery = false;
+ mUnprocessedEventAvailable = false;
+ mLock.notifyAll();
+ return mLastEvent;
+ }
+ // Ask for another event.
+ mWaitingForEventDelivery = true;
+ mUnprocessedEventAvailable = false;
+ mLock.notifyAll();
+ // Check if timed out and if not wait.
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ mWaitingForEventDelivery = false;
+ mUnprocessedEventAvailable = false;
+ mLock.notifyAll();
+ throw new TimeoutException("Expacted event not received within: "
+ + timeoutMillis + " ms.");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id in the active
+ * window. The search is performed from the root node.
+ *
+ * @param accessibilityNodeId A unique view id or virtual descendant id for
+ * which to search.
+ * @return The current window scale, where zero means a failure.
+ */
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityIdInActiveWindow(
+ long accessibilityNodeId) {
+ return findAccessibilityNodeInfoByAccessibilityId(ACTIVE_WINDOW_ID, accessibilityNodeId);
+ }
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id.
+ *
+ * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id for
+ * which to search.
+ * @return The current window scale, where zero means a failure.
+ */
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+ int accessibilityWindowId, long accessibilityNodeId) {
+ // Cache the id to avoid locking
+ final int connectionId = mConnectionId;
+ ensureValidConnection(connectionId);
+ return AccessibilityInteractionClient.getInstance()
+ .findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ accessibilityWindowId, accessibilityNodeId);
+ }
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by View id in the active
+ * window. The search is performed from the root node.
+ *
+ * @return The current window scale, where zero means a failure.
+ */
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) {
+ return findAccessibilityNodeInfoByViewId(ACTIVE_WINDOW_ID, ROOT_NODE_ID, viewId);
+ }
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
+ * the window whose id is specified and starts from the node whose accessibility
+ * id is specified.
+ *
+ * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
+ * @return The current window scale, where zero means a failure.
+ */
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, int viewId) {
+ // Cache the id to avoid locking
+ final int connectionId = mConnectionId;
+ ensureValidConnection(connectionId);
+ return AccessibilityInteractionClient.getInstance()
+ .findAccessibilityNodeInfoByViewId(connectionId, accessibilityWindowId,
+ accessibilityNodeId, viewId);
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by View text in the active
+ * window. The search is performed from the root node.
+ *
+ * @param text The searched text.
+ * @return The current window scale, where zero means a failure.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(String text) {
+ return findAccessibilityNodeInfosByText(ACTIVE_WINDOW_ID, ROOT_NODE_ID, text);
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
+ * insensitive containment. The search is performed in the window whose
+ * id is specified and starts from the node whose accessibility id is
+ * specified.
+ *
+ * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
+ * @param text The searched text.
+ * @return The current window scale, where zero means a failure.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int accessibilityWindowId,
+ long accessibilityNodeId, String text) {
+ // Cache the id to avoid locking
+ final int connectionId = mConnectionId;
+ ensureValidConnection(connectionId);
+ return AccessibilityInteractionClient.getInstance()
+ .findAccessibilityNodeInfosByText(connectionId, accessibilityWindowId,
+ accessibilityNodeId, text);
+ }
+
+ /**
+ * Performs an accessibility action on an {@link AccessibilityNodeInfo}
+ * in the active window.
+ *
+ * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
+ * @param action The action to perform.
+ * @return Whether the action was performed.
+ */
+ public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action) {
+ return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action);
+ }
+
+ /**
+ * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
+ *
+ * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
+ * @param action The action to perform.
+ * @return Whether the action was performed.
+ */
+ public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
+ int action) {
+ // Cache the id to avoid locking
+ final int connectionId = mConnectionId;
+ ensureValidConnection(connectionId);
+ return AccessibilityInteractionClient.getInstance().performAccessibilityAction(connectionId,
+ accessibilityWindowId, accessibilityNodeId, action);
+ }
+
+ private void ensureValidConnection(int connectionId) {
+ if (connectionId == AccessibilityInteractionClient.NO_ID) {
+ throw new IllegalStateException("UiAutomationService not connected."
+ + " Did you call #register()?");
+ }
+ }
+}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index c7a129e..cc1efb9 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -19,6 +19,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
import android.view.Choreographer;
import android.view.animation.AccelerateDecelerateInterpolator;
@@ -52,6 +53,7 @@
/**
* Internal constants
*/
+ private static float sDurationScale = 1.0f;
/**
* Messages sent to timing handler: START is sent when an animation first begins.
@@ -158,10 +160,12 @@
//
// How long the animation should last in ms
- private long mDuration = 300;
+ private long mDuration = (long)(300 * sDurationScale);
+ private long mUnscaledDuration = 300;
// The amount of time in ms to delay starting the animation after start() is called
private long mStartDelay = 0;
+ private long mUnscaledStartDelay = 0;
// The number of times the animation will repeat. The default is 0, which means the animation
// will play only once
@@ -217,6 +221,14 @@
*/
public static final int INFINITE = -1;
+
+ /**
+ * @hide
+ */
+ public static void setDurationScale(float durationScale) {
+ sDurationScale = durationScale;
+ }
+
/**
* Creates a new ValueAnimator object. This default constructor is primarily for
* use internally; the factory methods which take parameters are more generally
@@ -453,7 +465,8 @@
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
- mDuration = duration;
+ mUnscaledDuration = duration;
+ mDuration = (long)(duration * sDurationScale);
return this;
}
@@ -463,7 +476,7 @@
* @return The length of the animation, in milliseconds.
*/
public long getDuration() {
- return mDuration;
+ return mUnscaledDuration;
}
/**
@@ -658,7 +671,7 @@
* @return the number of milliseconds to delay running the animation
*/
public long getStartDelay() {
- return mStartDelay;
+ return mUnscaledStartDelay;
}
/**
@@ -668,7 +681,8 @@
* @param startDelay The amount of the delay, in milliseconds
*/
public void setStartDelay(long startDelay) {
- this.mStartDelay = startDelay;
+ this.mStartDelay = (long)(startDelay * sDurationScale);
+ mUnscaledStartDelay = startDelay;
}
/**
diff --git a/core/java/android/annotation/SuppressLint.java b/core/java/android/annotation/SuppressLint.java
new file mode 100644
index 0000000..2d3456b
--- /dev/null
+++ b/core/java/android/annotation/SuppressLint.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+ /**
+ * The set of warnings (identified by the lint issue id) that should be
+ * ignored by lint. It is not an error to specify an unrecognized name.
+ */
+ String[] value();
+}
diff --git a/core/java/android/annotation/TargetApi.java b/core/java/android/annotation/TargetApi.java
new file mode 100644
index 0000000..ea17890
--- /dev/null
+++ b/core/java/android/annotation/TargetApi.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should treat this type as targeting a given API level, no matter what the
+ project target is. */
+@Target({TYPE, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface TargetApi {
+ /**
+ * This sets the target api level for the type..
+ */
+ int value();
+}
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index d83d2e6..ff71ee7 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -17,6 +17,7 @@
package android.app;
import android.content.Loader;
+import android.content.Loader.OnLoadCanceledListener;
import android.os.Bundle;
import android.util.DebugUtils;
import android.util.Log;
@@ -219,7 +220,8 @@
boolean mCreatingLoader;
- final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> {
+ final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,
+ Loader.OnLoadCanceledListener<Object> {
final int mId;
final Bundle mArgs;
LoaderManager.LoaderCallbacks<Object> mCallbacks;
@@ -271,6 +273,7 @@
}
if (!mListenerRegistered) {
mLoader.registerListener(mId, this);
+ mLoader.registerOnLoadCanceledListener(this);
mListenerRegistered = true;
}
mLoader.startLoading();
@@ -329,11 +332,21 @@
// Let the loader know we're done with it
mListenerRegistered = false;
mLoader.unregisterListener(this);
+ mLoader.unregisterOnLoadCanceledListener(this);
mLoader.stopLoading();
}
}
}
-
+
+ void cancel() {
+ if (DEBUG) Log.v(TAG, " Canceling: " + this);
+ if (mStarted && mLoader != null && mListenerRegistered) {
+ if (!mLoader.cancelLoad()) {
+ onLoadCanceled(mLoader);
+ }
+ }
+ }
+
void destroy() {
if (DEBUG) Log.v(TAG, " Destroying: " + this);
mDestroyed = true;
@@ -361,6 +374,7 @@
if (mListenerRegistered) {
mListenerRegistered = false;
mLoader.unregisterListener(this);
+ mLoader.unregisterOnLoadCanceledListener(this);
}
mLoader.reset();
}
@@ -368,8 +382,38 @@
mPendingLoader.destroy();
}
}
-
- @Override public void onLoadComplete(Loader<Object> loader, Object data) {
+
+ @Override
+ public void onLoadCanceled(Loader<Object> loader) {
+ if (DEBUG) Log.v(TAG, "onLoadCanceled: " + this);
+
+ if (mDestroyed) {
+ if (DEBUG) Log.v(TAG, " Ignoring load canceled -- destroyed");
+ return;
+ }
+
+ if (mLoaders.get(mId) != this) {
+ // This cancellation message is not coming from the current active loader.
+ // We don't care about it.
+ if (DEBUG) Log.v(TAG, " Ignoring load canceled -- not active");
+ return;
+ }
+
+ LoaderInfo pending = mPendingLoader;
+ if (pending != null) {
+ // There is a new request pending and we were just
+ // waiting for the old one to cancel or complete before starting
+ // it. So now it is time, switch over to the new loader.
+ if (DEBUG) Log.v(TAG, " Switching to pending loader: " + pending);
+ mPendingLoader = null;
+ mLoaders.put(mId, null);
+ destroy();
+ installLoader(pending);
+ }
+ }
+
+ @Override
+ public void onLoadComplete(Loader<Object> loader, Object data) {
if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
if (mDestroyed) {
@@ -632,7 +676,9 @@
} else {
// Now we have three active loaders... we'll queue
// up this request to be processed once one of the other loaders
- // finishes.
+ // finishes or is canceled.
+ if (DEBUG) Log.v(TAG, " Current loader is running; attempting to cancel");
+ info.cancel();
if (info.mPendingLoader != null) {
if (DEBUG) Log.v(TAG, " Removing pending loader: " + info.mPendingLoader);
info.mPendingLoader.destroy();
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 944ca6b..da51952 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -53,19 +53,33 @@
static final boolean DEBUG = false;
final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
+ private final CountDownLatch mDone = new CountDownLatch(1);
- D result;
+ // Set to true to indicate that the task has been posted to a handler for
+ // execution at a later time. Used to throttle updates.
boolean waiting;
- private CountDownLatch done = new CountDownLatch(1);
-
/* Runs on a worker thread */
@Override
protected D doInBackground(Void... params) {
if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
- result = AsyncTaskLoader.this.onLoadInBackground();
- if (DEBUG) Slog.v(TAG, this + " <<< doInBackground");
- return result;
+ try {
+ D data = AsyncTaskLoader.this.onLoadInBackground();
+ if (DEBUG) Slog.v(TAG, this + " <<< doInBackground");
+ return data;
+ } catch (OperationCanceledException ex) {
+ if (!isCancelled()) {
+ // onLoadInBackground threw a canceled exception spuriously.
+ // This is problematic because it means that the LoaderManager did not
+ // cancel the Loader itself and still expects to receive a result.
+ // Additionally, the Loader's own state will not have been updated to
+ // reflect the fact that the task was being canceled.
+ // So we treat this case as an unhandled exception.
+ throw ex;
+ }
+ if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)");
+ return null;
+ }
}
/* Runs on the UI thread */
@@ -75,25 +89,37 @@
try {
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
} finally {
- done.countDown();
+ mDone.countDown();
}
}
+ /* Runs on the UI thread */
@Override
- protected void onCancelled() {
+ protected void onCancelled(D data) {
if (DEBUG) Slog.v(TAG, this + " onCancelled");
try {
- AsyncTaskLoader.this.dispatchOnCancelled(this, result);
+ AsyncTaskLoader.this.dispatchOnCancelled(this, data);
} finally {
- done.countDown();
+ mDone.countDown();
}
}
+ /* Runs on the UI thread, when the waiting task is posted to a handler.
+ * This method is only executed when task execution was deferred (waiting was true). */
@Override
public void run() {
waiting = false;
AsyncTaskLoader.this.executePendingTask();
}
+
+ /* Used for testing purposes to wait for the task to complete. */
+ public void waitForLoader() {
+ try {
+ mDone.await();
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
}
volatile LoadTask mTask;
@@ -109,7 +135,7 @@
/**
* Set amount to throttle updates by. This is the minimum time from
- * when the last {@link #onLoadInBackground()} call has completed until
+ * when the last {@link #loadInBackground()} call has completed until
* a new load is scheduled.
*
* @param delayMS Amount of delay, in milliseconds.
@@ -130,24 +156,9 @@
executePendingTask();
}
- /**
- * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)}
- * for more info. Must be called on the main thread of the process.
- *
- * <p>Cancelling is not an immediate operation, since the load is performed
- * in a background thread. If there is currently a load in progress, this
- * method requests that the load be cancelled, and notes this is the case;
- * once the background thread has completed its work its remaining state
- * will be cleared. If another load request comes in during this time,
- * it will be held until the cancelled load is complete.
- *
- * @return Returns <tt>false</tt> if the task could not be cancelled,
- * typically because it has already completed normally, or
- * because {@link #startLoading()} hasn't been called; returns
- * <tt>true</tt> otherwise.
- */
- public boolean cancelLoad() {
- if (DEBUG) Slog.v(TAG, "cancelLoad: mTask=" + mTask);
+ @Override
+ protected boolean onCancelLoad() {
+ if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask);
if (mTask != null) {
if (mCancellingTask != null) {
// There was a pending task already waiting for a previous
@@ -173,7 +184,7 @@
if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
if (cancelled) {
mCancellingTask = mTask;
- onCancelLoadInBackground();
+ cancelLoadInBackground();
}
mTask = null;
return cancelled;
@@ -184,7 +195,10 @@
/**
* Called if the task was canceled before it was completed. Gives the class a chance
- * to properly dispose of the result.
+ * to clean up post-cancellation and to properly dispose of the result.
+ *
+ * @param data The value that was returned by {@link #loadInBackground}, or null
+ * if the task threw {@link OperationCanceledException}.
*/
public void onCanceled(D data) {
}
@@ -218,6 +232,8 @@
if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mCancellingTask = null;
+ if (DEBUG) Slog.v(TAG, "Delivering cancellation");
+ deliverCancellation();
executePendingTask();
}
}
@@ -240,38 +256,72 @@
}
/**
+ * Called on a worker thread to perform the actual load and to return
+ * the result of the load operation.
+ *
+ * Implementations should not deliver the result directly, but should return them
+ * from this method, which will eventually end up calling {@link #deliverResult} on
+ * the UI thread. If implementations need to process the results on the UI thread
+ * they may override {@link #deliverResult} and do so there.
+ *
+ * To support cancellation, this method should periodically check the value of
+ * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
+ * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
+ * directly instead of polling {@link #isLoadInBackgroundCanceled}.
+ *
+ * When the load is canceled, this method may either return normally or throw
+ * {@link OperationCanceledException}. In either case, the {@link Loader} will
+ * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
+ * result object, if any.
+ *
+ * @return The result of the load operation.
+ *
+ * @throws OperationCanceledException if the load is canceled during execution.
+ *
+ * @see #isLoadInBackgroundCanceled
+ * @see #cancelLoadInBackground
+ * @see #onCanceled
*/
public abstract D loadInBackground();
/**
- * Called on a worker thread to perform the actual load. Implementations should not deliver the
- * result directly, but should return them from this method, which will eventually end up
- * calling {@link #deliverResult} on the UI thread. If implementations need to process
- * the results on the UI thread they may override {@link #deliverResult} and do so
- * there.
+ * Calls {@link #loadInBackground()}.
*
- * @return Implementations must return the result of their load operation.
+ * This method is reserved for use by the loader framework.
+ * Subclasses should override {@link #loadInBackground} instead of this method.
+ *
+ * @return The result of the load operation.
+ *
+ * @throws OperationCanceledException if the load is canceled during execution.
+ *
+ * @see #loadInBackground
*/
protected D onLoadInBackground() {
return loadInBackground();
}
/**
- * Override this method to try to abort the computation currently taking
- * place on a background thread.
+ * Called on the main thread to abort a load in progress.
*
- * Note that when this method is called, it is possible that {@link #loadInBackground}
- * has not started yet or has already completed.
+ * Override this method to abort the current invocation of {@link #loadInBackground}
+ * that is running in the background on a worker thread.
+ *
+ * This method should do nothing if {@link #loadInBackground} has not started
+ * running or if it has already finished.
+ *
+ * @see #loadInBackground
*/
- protected void onCancelLoadInBackground() {
+ public void cancelLoadInBackground() {
}
/**
- * Returns true if the current execution of {@link #loadInBackground()} is being canceled.
+ * Returns true if the current invocation of {@link #loadInBackground} is being canceled.
*
- * @return True if the current execution of {@link #loadInBackground()} is being canceled.
+ * @return True if the current invocation of {@link #loadInBackground} is being canceled.
+ *
+ * @see #loadInBackground
*/
- protected boolean isLoadInBackgroundCanceled() {
+ public boolean isLoadInBackgroundCanceled() {
return mCancellingTask != null;
}
@@ -288,11 +338,7 @@
public void waitForLoader() {
LoadTask task = mTask;
if (task != null) {
- try {
- task.done.await();
- } catch (InterruptedException e) {
- // Ignore
- }
+ task.waitForLoader();
}
}
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 6e4aca8..e58fad7 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -76,8 +76,8 @@
}
@Override
- protected void onCancelLoadInBackground() {
- super.onCancelLoadInBackground();
+ public void cancelLoadInBackground() {
+ super.cancelLoadInBackground();
synchronized (this) {
if (mCancelationSignal != null) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index fbc1b2b..ab62c44 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2957,6 +2957,13 @@
*/
public static final int FLAG_RECEIVER_REPLACE_PENDING = 0x20000000;
/**
+ * If set, when sending a broadcast the recipient is allowed to run at
+ * foreground priority, with a shorter timeout interval. During normal
+ * broadcasts the receivers are not automatically hoisted out of the
+ * background priority class.
+ */
+ public static final int FLAG_RECEIVER_FOREGROUND = 0x10000000;
+ /**
* If set, when sending a broadcast <i>before boot has completed</i> only
* registered receivers will be called -- no BroadcastReceiver components
* will be launched. Sticky intent state will be recorded properly even
@@ -2969,14 +2976,14 @@
*
* @hide
*/
- public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x10000000;
+ public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x08000000;
/**
* Set when this broadcast is for a boot upgrade, a special mode that
* allows the broadcast to be sent before the system is ready and launches
* the app process with no providers running in it.
* @hide
*/
- public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x08000000;
+ public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x04000000;
/**
* @hide Flags that can't be changed with PendingIntent.
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index ac05682..3052414 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -52,6 +52,7 @@
public class Loader<D> {
int mId;
OnLoadCompleteListener<D> mListener;
+ OnLoadCanceledListener<D> mOnLoadCanceledListener;
Context mContext;
boolean mStarted = false;
boolean mAbandoned = false;
@@ -100,6 +101,23 @@
}
/**
+ * Interface that is implemented to discover when a Loader has been canceled
+ * before it finished loading its data. You do not normally need to implement
+ * this yourself; it is used in the implementation of {@link android.app.LoaderManager}
+ * to find out when a Loader it is managing has been canceled so that it
+ * can schedule the next Loader. This interface should only be used if a
+ * Loader is not being used in conjunction with LoaderManager.
+ */
+ public interface OnLoadCanceledListener<D> {
+ /**
+ * Called on the thread that created the Loader when the load is canceled.
+ *
+ * @param loader the loader that canceled the load
+ */
+ public void onLoadCanceled(Loader<D> loader);
+ }
+
+ /**
* Stores away the application context associated with context.
* Since Loaders can be used across multiple activities it's dangerous to
* store the context directly; always use {@link #getContext()} to retrieve
@@ -127,6 +145,18 @@
}
/**
+ * Informs the registered {@link OnLoadCanceledListener} that the load has been canceled.
+ * Should only be called by subclasses.
+ *
+ * Must be called from the process's main thread.
+ */
+ public void deliverCancellation() {
+ if (mOnLoadCanceledListener != null) {
+ mOnLoadCanceledListener.onLoadCanceled(this);
+ }
+ }
+
+ /**
* @return an application context retrieved from the Context passed to the constructor.
*/
public Context getContext() {
@@ -171,6 +201,40 @@
}
/**
+ * Registers a listener that will receive callbacks when a load is canceled.
+ * The callback will be called on the process's main thread so it's safe to
+ * pass the results to widgets.
+ *
+ * Must be called from the process's main thread.
+ *
+ * @param listener The listener to register.
+ */
+ public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
+ if (mOnLoadCanceledListener != null) {
+ throw new IllegalStateException("There is already a listener registered");
+ }
+ mOnLoadCanceledListener = listener;
+ }
+
+ /**
+ * Unregisters a listener that was previously added with
+ * {@link #registerOnLoadCanceledListener}.
+ *
+ * Must be called from the process's main thread.
+ *
+ * @param listener The listener to unregister.
+ */
+ public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
+ if (mOnLoadCanceledListener == null) {
+ throw new IllegalStateException("No listener register");
+ }
+ if (mOnLoadCanceledListener != listener) {
+ throw new IllegalArgumentException("Attempting to unregister the wrong listener");
+ }
+ mOnLoadCanceledListener = null;
+ }
+
+ /**
* Return whether this load has been started. That is, its {@link #startLoading()}
* has been called and no calls to {@link #stopLoading()} or
* {@link #reset()} have yet been made.
@@ -234,6 +298,43 @@
}
/**
+ * Attempt to cancel the current load task.
+ * Must be called on the main thread of the process.
+ *
+ * <p>Cancellation is not an immediate operation, since the load is performed
+ * in a background thread. If there is currently a load in progress, this
+ * method requests that the load be canceled, and notes this is the case;
+ * once the background thread has completed its work its remaining state
+ * will be cleared. If another load request comes in during this time,
+ * it will be held until the canceled load is complete.
+ *
+ * @return Returns <tt>false</tt> if the task could not be canceled,
+ * typically because it has already completed normally, or
+ * because {@link #startLoading()} hasn't been called; returns
+ * <tt>true</tt> otherwise. When <tt>true</tt> is returned, the task
+ * is still running and the {@link OnLoadCanceledListener} will be called
+ * when the task completes.
+ */
+ public boolean cancelLoad() {
+ return onCancelLoad();
+ }
+
+ /**
+ * Subclasses must implement this to take care of requests to {@link #cancelLoad()}.
+ * This will always be called from the process's main thread.
+ *
+ * @return Returns <tt>false</tt> if the task could not be canceled,
+ * typically because it has already completed normally, or
+ * because {@link #startLoading()} hasn't been called; returns
+ * <tt>true</tt> otherwise. When <tt>true</tt> is returned, the task
+ * is still running and the {@link OnLoadCanceledListener} will be called
+ * when the task completes.
+ */
+ protected boolean onCancelLoad() {
+ return false;
+ }
+
+ /**
* Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
* loaded data set and load a new one. This simply calls through to the
* implementation's {@link #onForceLoad()}. You generally should only call this
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 6015668..e04b2f7 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -347,6 +347,7 @@
sb.append(" (no locale)");
}
switch (textLayoutDirection) {
+ case LocaleUtil.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE: /* ltr not interesting */ break;
case LocaleUtil.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE: sb.append(" rtl"); break;
default: sb.append(" layoutdir="); sb.append(textLayoutDirection); break;
}
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index b69d9bf..0022118 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -21,6 +21,7 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
+import android.content.OperationCanceledException;
import android.database.sqlite.SQLiteAbortException;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
@@ -107,6 +108,9 @@
code = 9;
} else if (e instanceof OperationApplicationException) {
code = 10;
+ } else if (e instanceof OperationCanceledException) {
+ code = 11;
+ logException = false;
} else {
reply.writeException(e);
Log.e(TAG, "Writing exception to parcel", e);
@@ -178,6 +182,8 @@
throw new SQLiteDiskIOException(msg);
case 9:
throw new SQLiteException(msg);
+ case 11:
+ throw new OperationCanceledException(msg);
default:
reply.readException(code, msg);
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a569317..2eef8f4 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -142,8 +142,19 @@
* If an application uses the network in the background, it should listen
* for this broadcast and stop using the background data if the value is
* {@code false}.
+ * <p>
+ *
+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability
+ * of background data depends on several combined factors, and
+ * this broadcast is no longer sent. Instead, when background
+ * data is unavailable, {@link #getActiveNetworkInfo()} will now
+ * appear disconnected. During first boot after a platform
+ * upgrade, this broadcast will be sent once if
+ * {@link #getBackgroundDataSetting()} was {@code false} before
+ * the upgrade.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
"android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f14d27e..207810c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1678,6 +1678,13 @@
public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
/**
+ * Scaling factor for Animator-based animations. This affects both the start delay and
+ * duration of all such animations. Setting to 0 will cause animations to end immediately.
+ * The default value is 1.
+ */
+ public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale";
+
+ /**
* Scaling factor for normal window animations. Setting to 0 will disable window
* animations.
* @hide
diff --git a/core/java/android/view/AccessibilityNodeInfoCache.java b/core/java/android/view/AccessibilityNodeInfoCache.java
new file mode 100644
index 0000000..244a491
--- /dev/null
+++ b/core/java/android/view/AccessibilityNodeInfoCache.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.util.LongSparseArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Simple cache for AccessibilityNodeInfos. The cache is mapping an
+ * accessibility id to an info. The cache allows storing of
+ * <code>null</code> values. It also tracks accessibility events
+ * and invalidates accordingly.
+ *
+ * @hide
+ */
+public class AccessibilityNodeInfoCache {
+
+ private final boolean ENABLED = true;
+
+ /**
+ * @return A new <strong>not synchronized</strong> AccessibilityNodeInfoCache.
+ */
+ public static AccessibilityNodeInfoCache newAccessibilityNodeInfoCache() {
+ return new AccessibilityNodeInfoCache();
+ }
+
+ /**
+ * @return A new <strong>synchronized</strong> AccessibilityNodeInfoCache.
+ */
+ public static AccessibilityNodeInfoCache newSynchronizedAccessibilityNodeInfoCache() {
+ return new AccessibilityNodeInfoCache() {
+ private final Object mLock = new Object();
+
+ @Override
+ public void clear() {
+ synchronized(mLock) {
+ super.clear();
+ }
+ }
+
+ @Override
+ public AccessibilityNodeInfo get(long accessibilityNodeId) {
+ synchronized(mLock) {
+ return super.get(accessibilityNodeId);
+ }
+ }
+
+ @Override
+ public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
+ synchronized(mLock) {
+ super.put(accessibilityNodeId, info);
+ }
+ }
+
+ @Override
+ public void remove(long accessibilityNodeId) {
+ synchronized(mLock) {
+ super.remove(accessibilityNodeId);
+ }
+ }
+ };
+ }
+
+ private final LongSparseArray<AccessibilityNodeInfo> mCacheImpl;
+
+ private AccessibilityNodeInfoCache() {
+ if (ENABLED) {
+ mCacheImpl = new LongSparseArray<AccessibilityNodeInfo>();
+ } else {
+ mCacheImpl = null;
+ }
+ }
+
+ /**
+ * The cache keeps track of {@link AccessibilityEvent}s and invalidates
+ * cached nodes as appropriate.
+ *
+ * @param event An event.
+ */
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ final int eventType = event.getEventType();
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
+ case AccessibilityEvent.TYPE_VIEW_SCROLLED:
+ clear();
+ break;
+ case AccessibilityEvent.TYPE_VIEW_FOCUSED:
+ case AccessibilityEvent.TYPE_VIEW_SELECTED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
+ final long accessibilityNodeId = event.getSourceNodeId();
+ remove(accessibilityNodeId);
+ break;
+ }
+ }
+
+ /**
+ * Gets a cached {@link AccessibilityNodeInfo} given its accessibility node id.
+ *
+ * @param accessibilityNodeId The info accessibility node id.
+ * @return The cached {@link AccessibilityNodeInfo} or null if such not found.
+ */
+ public AccessibilityNodeInfo get(long accessibilityNodeId) {
+ if (ENABLED) {
+ return mCacheImpl.get(accessibilityNodeId);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Caches an {@link AccessibilityNodeInfo} given its accessibility node id.
+ *
+ * @param accessibilityNodeId The info accessibility node id.
+ * @param info The {@link AccessibilityNodeInfo} to cache.
+ */
+ public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
+ if (ENABLED) {
+ mCacheImpl.put(accessibilityNodeId, info);
+ }
+ }
+
+ /**
+ * Returns whether the cache contains an accessibility node id key.
+ *
+ * @param accessibilityNodeId The key for which to check.
+ * @return True if the key is in the cache.
+ */
+ public boolean containsKey(long accessibilityNodeId) {
+ if (ENABLED) {
+ return (mCacheImpl.indexOfKey(accessibilityNodeId) >= 0);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Removes a cached {@link AccessibilityNodeInfo}.
+ *
+ * @param accessibilityNodeId The info accessibility node id.
+ */
+ public void remove(long accessibilityNodeId) {
+ if (ENABLED) {
+ mCacheImpl.remove(accessibilityNodeId);
+ }
+ }
+
+ /**
+ * Clears the cache.
+ */
+ public void clear() {
+ if (ENABLED) {
+ mCacheImpl.clear();
+ }
+ }
+}
diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java
index 34e7d4d..0349a2b 100644
--- a/core/java/android/view/ActionMode.java
+++ b/core/java/android/view/ActionMode.java
@@ -18,9 +18,15 @@
/**
- * Represents a contextual mode of the user interface. Action modes can be used for
- * modal interactions with content and replace parts of the normal UI until finished.
- * Examples of good action modes include selection modes, search, content editing, etc.
+ * Represents a contextual mode of the user interface. Action modes can be used to provide
+ * alternative interaction modes and replace parts of the normal UI until finished.
+ * Examples of good action modes include text selection and contextual actions.
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to provide contextual actions with {@code ActionMode},
+ * read the <a href="{@docRoot}guide/topics/ui/menu.html#context-menu">Menus</a>
+ * developer guide.</p>
+ * </div>
*/
public abstract class ActionMode {
private Object mTag;
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 63de128..c86ea77 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -26,7 +26,7 @@
import android.util.Log;
/**
- * Coodinates animations and drawing for UI on a particular thread.
+ * Coordinates animations and drawing for UI on a particular thread.
* @hide
*/
public final class Choreographer extends Handler {
@@ -94,8 +94,8 @@
}
/**
- * Gets the choreographer for this thread.
- * Must be called on the UI thread.
+ * Gets the choreographer for the calling thread. Must be called from
+ * a thread that already has a {@link android.os.Looper} associated with it.
*
* @return The choreographer for this thread.
* @throws IllegalStateException if the thread does not have a looper.
@@ -163,6 +163,15 @@
}
/**
+ * Return true if {@link #scheduleAnimation()} has been called but
+ * {@link OnAnimateListener#onAnimate() OnAnimateListener.onAnimate()} has
+ * not yet been called.
+ */
+ public boolean isAnimationScheduled() {
+ return mAnimationScheduled;
+ }
+
+ /**
* Schedules drawing to occur on the next frame synchronization boundary.
* Must be called on the UI thread.
*/
@@ -180,6 +189,15 @@
}
}
+ /**
+ * Return true if {@link #scheduleDraw()} has been called but
+ * {@link OnDrawListener#onDraw() OnDrawListener.onDraw()} has
+ * not yet been called.
+ */
+ public boolean isDrawScheduled() {
+ return mDrawScheduled;
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8597017..cf4cff9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3761,8 +3761,14 @@
}
/**
- * Called when this view wants to give up focus. This will cause
- * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.
+ * Called when this view wants to give up focus. If focus is cleared
+ * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called.
+ * <p>
+ * <strong>Note:</strong> When a View clears focus the framework is trying
+ * to give focus to the first focusable View from the top. Hence, if this
+ * View is the first from the top that can take focus, then its focus will
+ * not be cleared nor will the focus change callback be invoked.
+ * </p>
*/
public void clearFocus() {
if (DBG) {
@@ -12679,6 +12685,11 @@
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
}
+ if (getAccessibilityNodeProvider() != null) {
+ throw new IllegalStateException("Views with AccessibilityNodeProvider"
+ + " can't have children.");
+ }
+
mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7559862..d3af618 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3356,6 +3356,11 @@
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
+ if (getAccessibilityNodeProvider() != null) {
+ throw new IllegalStateException("Views with AccessibilityNodeProvider"
+ + " can't have children.");
+ }
+
if (mTransition != null) {
// Don't prevent other add transitions from completing, but cancel remove
// transitions to let them complete the process before we add to the container
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 89a1ef2..0fdcd0f 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -113,6 +113,10 @@
* on that list are added to the list of properties associated with that animator.
*/
ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>();
+ private Runnable mPendingSetupAction;
+ private Runnable mPendingCleanupAction;
+ private Runnable mPendingOnStartAction;
+ private Runnable mPendingOnEndAction;
/**
* Constants used to associate a property being requested and the mechanism used to set
@@ -199,6 +203,10 @@
*/
private HashMap<Animator, PropertyBundle> mAnimatorMap =
new HashMap<Animator, PropertyBundle>();
+ private HashMap<Animator, Runnable> mAnimatorSetupMap;
+ private HashMap<Animator, Runnable> mAnimatorCleanupMap;
+ private HashMap<Animator, Runnable> mAnimatorOnStartMap;
+ private HashMap<Animator, Runnable> mAnimatorOnEndMap;
/**
* This is the information we need to set each property during the animation.
@@ -614,6 +622,93 @@
}
/**
+ * The View associated with this ViewPropertyAnimator will have its
+ * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to
+ * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation. This state
+ * is not persistent, either on the View or on this ViewPropertyAnimator: the layer type
+ * of the View will be restored when the animation ends to what it was when this method was
+ * called, and this setting on ViewPropertyAnimator is only valid for the next animation.
+ * Note that calling this method and then independently setting the layer type of the View
+ * (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will result
+ * in some inconsistency, including having the layer type restored to its pre-withLayer()
+ * value when the animation ends.
+ *
+ * @see View#setLayerType(int, android.graphics.Paint)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator withLayer() {
+ mPendingSetupAction= new Runnable() {
+ @Override
+ public void run() {
+ mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ };
+ final int currentLayerType = mView.getLayerType();
+ mPendingCleanupAction = new Runnable() {
+ @Override
+ public void run() {
+ mView.setLayerType(currentLayerType, null);
+ }
+ };
+ if (mAnimatorSetupMap == null) {
+ mAnimatorSetupMap = new HashMap<Animator, Runnable>();
+ }
+ if (mAnimatorCleanupMap == null) {
+ mAnimatorCleanupMap = new HashMap<Animator, Runnable>();
+ }
+
+ return this;
+ }
+
+ /**
+ * Specifies an action to take place when the next animation runs. If there is a
+ * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the
+ * action will run after that startDelay expires, when the actual animation begins.
+ * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate
+ * choreographing ViewPropertyAnimator animations with other animations or actions
+ * in the application.
+ *
+ * @param runnable The action to run when the next animation starts.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator withStartAction(Runnable runnable) {
+ mPendingOnStartAction = runnable;
+ if (runnable != null && mAnimatorOnStartMap == null) {
+ mAnimatorOnStartMap = new HashMap<Animator, Runnable>();
+ }
+ return this;
+ }
+
+ /**
+ * Specifies an action to take place when the next animation ends. The action is only
+ * run if the animation ends normally; if the ViewPropertyAnimator is canceled during
+ * that animation, the runnable will not run.
+ * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate
+ * choreographing ViewPropertyAnimator animations with other animations or actions
+ * in the application.
+ *
+ * <p>For example, the following code animates a view to x=200 and then back to 0:</p>
+ * <pre>
+ * Runnable endAction = new Runnable() {
+ * public void run() {
+ * view.animate().x(0);
+ * }
+ * };
+ * view.animate().x(200).onEnd(endAction);
+ * </pre>
+ *
+ * @param runnable The action to run when the next animation ends.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator withEndAction(Runnable runnable) {
+ mPendingOnEndAction = runnable;
+ if (runnable != null && mAnimatorOnEndMap == null) {
+ mAnimatorOnEndMap = new HashMap<Animator, Runnable>();
+ }
+ return this;
+ }
+
+ /**
* Starts the underlying Animator for a set of properties. We use a single animator that
* simply runs from 0 to 1, and then use that fractional value to set each property
* value accordingly.
@@ -630,6 +725,22 @@
propertyMask |= nameValuesHolder.mNameConstant;
}
mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
+ if (mPendingSetupAction != null) {
+ mAnimatorSetupMap.put(animator, mPendingSetupAction);
+ mPendingSetupAction = null;
+ }
+ if (mPendingCleanupAction != null) {
+ mAnimatorCleanupMap.put(animator, mPendingCleanupAction);
+ mPendingCleanupAction = null;
+ }
+ if (mPendingOnStartAction != null) {
+ mAnimatorOnStartMap.put(animator, mPendingOnStartAction);
+ mPendingOnStartAction = null;
+ }
+ if (mPendingOnEndAction != null) {
+ mAnimatorOnEndMap.put(animator, mPendingOnEndAction);
+ mPendingOnEndAction = null;
+ }
animator.addUpdateListener(mAnimatorEventListener);
animator.addListener(mAnimatorEventListener);
if (mStartDelaySet) {
@@ -800,6 +911,20 @@
implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
@Override
public void onAnimationStart(Animator animation) {
+ if (mAnimatorSetupMap != null) {
+ Runnable r = mAnimatorSetupMap.get(animation);
+ if (r != null) {
+ r.run();
+ }
+ mAnimatorSetupMap.remove(animation);
+ }
+ if (mAnimatorOnStartMap != null) {
+ Runnable r = mAnimatorOnStartMap.get(animation);
+ if (r != null) {
+ r.run();
+ }
+ mAnimatorOnStartMap.remove(animation);
+ }
if (mListener != null) {
mListener.onAnimationStart(animation);
}
@@ -810,6 +935,9 @@
if (mListener != null) {
mListener.onAnimationCancel(animation);
}
+ if (mAnimatorOnEndMap != null) {
+ mAnimatorOnEndMap.remove(animation);
+ }
}
@Override
@@ -824,6 +952,20 @@
if (mListener != null) {
mListener.onAnimationEnd(animation);
}
+ if (mAnimatorOnEndMap != null) {
+ Runnable r = mAnimatorOnEndMap.get(animation);
+ if (r != null) {
+ r.run();
+ }
+ mAnimatorOnEndMap.remove(animation);
+ }
+ if (mAnimatorCleanupMap != null) {
+ Runnable r = mAnimatorCleanupMap.get(animation);
+ if (r != null) {
+ r.run();
+ }
+ mAnimatorCleanupMap.remove(animation);
+ }
mAnimatorMap.remove(animation);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2ef843b..cbf4b5a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -18,11 +18,13 @@
import android.Manifest;
import android.animation.LayoutTransition;
+import android.animation.ValueAnimator;
import android.app.ActivityManagerNative;
import android.content.ClipDescription;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -52,6 +54,7 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
+import android.util.LongSparseArray;
import android.util.Pool;
import android.util.Poolable;
import android.util.PoolableManager;
@@ -81,7 +84,6 @@
import java.io.IOException;
import java.io.OutputStream;
-import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -139,6 +141,10 @@
static final ArrayList<ComponentCallbacks> sConfigCallbacks
= new ArrayList<ComponentCallbacks>();
+ private static boolean sUseRenderThread = false;
+ private static boolean sRenderThreadQueried = false;
+ private static final Object[] sRenderThreadQueryLock = new Object[0];
+
long mLastTrackballTime = 0;
final TrackballAxis mTrackballAxisX = new TrackballAxis();
final TrackballAxis mTrackballAxisY = new TrackballAxis();
@@ -302,6 +308,8 @@
SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
+ AccessibilityPrefetchStrategy mAccessibilityPrefetchStrategy;
+
private final int mDensity;
/**
@@ -316,8 +324,11 @@
if (!mInitialized) {
try {
InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
- sWindowSession = Display.getWindowManager().openSession(
+ IWindowManager windowManager = Display.getWindowManager();
+ sWindowSession = windowManager.openSession(
imm.getClient(), imm.getInputContext());
+ float animatorScale = windowManager.getAnimationScale(2);
+ ValueAnimator.setDurationScale(animatorScale);
mInitialized = true;
} catch (RemoteException e) {
}
@@ -378,6 +389,31 @@
mChoreographer = Choreographer.getInstance();
}
+ /**
+ * @return True if the application requests the use of a separate render thread,
+ * false otherwise
+ */
+ private static boolean isRenderThreadRequested(Context context) {
+ synchronized (sRenderThreadQueryLock) {
+ if (!sRenderThreadQueried) {
+ final PackageManager packageManager = context.getPackageManager();
+ final String packageName = context.getApplicationInfo().packageName;
+ try {
+ ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName,
+ PackageManager.GET_META_DATA);
+ if (applicationInfo.metaData != null) {
+ sUseRenderThread = applicationInfo.metaData.getBoolean(
+ "android.graphics.renderThread", false);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ } finally {
+ sRenderThreadQueried = true;
+ }
+ }
+ return sUseRenderThread;
+ }
+ }
+
public static void addFirstDrawHandler(Runnable callback) {
synchronized (sFirstDrawHandlers) {
if (!sFirstDrawComplete) {
@@ -448,7 +484,7 @@
// If the application owns the surface, don't enable hardware acceleration
if (mSurfaceHolder == null) {
- enableHardwareAcceleration(attrs);
+ enableHardwareAcceleration(mView.getContext(), attrs);
}
boolean restore = false;
@@ -608,7 +644,7 @@
}
}
- private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
+ private void enableHardwareAcceleration(Context context, WindowManager.LayoutParams attrs) {
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
@@ -641,20 +677,27 @@
if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled
&& forceHwAccelerated)) {
// Don't enable hardware acceleration when we're not on the main thread
- if (!HardwareRenderer.sSystemRendererDisabled
- && Looper.getMainLooper() != Looper.myLooper()) {
- Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware "
+ if (!HardwareRenderer.sSystemRendererDisabled &&
+ Looper.getMainLooper() != Looper.myLooper()) {
+ Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware "
+ "acceleration outside of the main thread, aborting");
return;
}
- final boolean translucent = attrs.format != PixelFormat.OPAQUE;
+ boolean renderThread = isRenderThreadRequested(context);
+ if (renderThread) {
+ Log.i(HardwareRenderer.LOG_TAG, "Render threat initiated");
+ }
+
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroy(true);
- }
+ }
+
+ final boolean translucent = attrs.format != PixelFormat.OPAQUE;
mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested
= mAttachInfo.mHardwareRenderer != null;
+
} else if (fakeHwAccelerated) {
// The window had wanted to use hardware acceleration, but this
// is not allowed in its process. By setting this flag, it can
@@ -3441,11 +3484,11 @@
if (args.localChanges != 0) {
if (mAttachInfo != null) {
mAttachInfo.mSystemUiVisibility =
- (mAttachInfo.mSystemUiVisibility&~args.localChanges)
- | (args.localValue&args.localChanges);
+ (mAttachInfo.mSystemUiVisibility & ~args.localChanges) |
+ (args.localValue & args.localChanges);
+ mAttachInfo.mRecomputeGlobalAttributes = true;
}
mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);
- mAttachInfo.mRecomputeGlobalAttributes = true;
scheduleTraversals();
}
mView.dispatchSystemUiVisibilityChanged(args.globalVisibility);
@@ -3483,6 +3526,17 @@
return mAccessibilityInteractionController;
}
+ public AccessibilityPrefetchStrategy getAccessibilityPrefetchStrategy() {
+ if (mView == null) {
+ throw new IllegalStateException("getAccessibilityPrefetchStrategy"
+ + " called when there is no mView");
+ }
+ if (mAccessibilityPrefetchStrategy == null) {
+ mAccessibilityPrefetchStrategy = new AccessibilityPrefetchStrategy();
+ }
+ return mAccessibilityPrefetchStrategy;
+ }
+
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
@@ -3588,7 +3642,7 @@
mView.debug();
}
- public void dumpGfxInfo(PrintWriter pw, int[] info) {
+ public void dumpGfxInfo(int[] info) {
if (mView != null) {
getGfxInfo(mView, info);
} else {
@@ -3700,7 +3754,7 @@
* Represents a pending input event that is waiting in a queue.
*
* Input events are processed in serial order by the timestamp specified by
- * {@link InputEvent#getEventTime()}. In general, the input dispatcher delivers
+ * {@link InputEvent#getEventTimeNano()}. In general, the input dispatcher delivers
* one input event to the application at a time and waits for the application
* to finish handling it before delivering the next one.
*
@@ -3709,7 +3763,7 @@
* needing a queue on the application's side.
*/
private static final class QueuedInputEvent {
- public static final int FLAG_DELIVER_POST_IME = 1 << 0;
+ public static final int FLAG_DELIVER_POST_IME = 1;
public QueuedInputEvent mNext;
@@ -3983,6 +4037,7 @@
if (mView == null) {
return false;
}
+ getAccessibilityPrefetchStrategy().onAccessibilityEvent(event);
mAccessibilityManager.sendAccessibilityEvent(event);
return true;
}
@@ -4542,6 +4597,13 @@
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
interactionId, callback, interrogatingPid, interrogatingTid);
+ } else {
+ // We cannot make the call and notify the caller so it does not wait.
+ try {
+ callback.setFindAccessibilityNodeInfosResult(null, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
}
}
@@ -4553,28 +4615,49 @@
viewRootImpl.getAccessibilityInteractionController()
.performAccessibilityActionClientThread(accessibilityNodeId, action,
interactionId, callback, interogatingPid, interrogatingTid);
+ } else {
+ // We cannot make the call and notify the caller so it does not
+ try {
+ callback.setPerformAccessibilityActionResult(false, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
}
}
- public void findAccessibilityNodeInfoByViewId(int viewId,
+ public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback,
- interrogatingPid, interrogatingTid);
- }
- }
-
- public void findAccessibilityNodeInfosByText(String text, long accessibilityNodeId,
- int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid) {
- ViewRootImpl viewRootImpl = mViewRootImpl.get();
- if (viewRootImpl != null && viewRootImpl.mView != null) {
- viewRootImpl.getAccessibilityInteractionController()
- .findAccessibilityNodeInfosByTextClientThread(text, accessibilityNodeId,
+ .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
interactionId, callback, interrogatingPid, interrogatingTid);
+ } else {
+ // We cannot make the call and notify the caller so it does not
+ try {
+ callback.setFindAccessibilityNodeInfoResult(null, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
+ }
+ }
+
+ public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int interrogatingPid, long interrogatingTid) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
+ interactionId, callback, interrogatingPid, interrogatingTid);
+ } else {
+ // We cannot make the call and notify the caller so it does not
+ try {
+ callback.setFindAccessibilityNodeInfosResult(null, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
}
}
}
@@ -4652,6 +4735,7 @@
long interrogatingTid) {
Message message = Message.obtain();
message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
+ message.arg1 = interrogatingPid;
SomeArgs args = mPool.acquire();
args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
@@ -4674,40 +4758,47 @@
public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
SomeArgs args = (SomeArgs) message.obj;
+ final int interrogatingPid = message.arg1;
final int accessibilityViewId = args.argi1;
final int virtualDescendantId = args.argi2;
final int interactionId = args.argi3;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
mPool.release(args);
- AccessibilityNodeInfo info = null;
+ List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
try {
View target = findViewByAccessibilityId(accessibilityViewId);
if (target != null && target.getVisibility() == View.VISIBLE) {
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
- info = provider.createAccessibilityNodeInfo(virtualDescendantId);
+ infos.add(provider.createAccessibilityNodeInfo(virtualDescendantId));
} else if (virtualDescendantId == View.NO_ID) {
- info = target.createAccessibilityNodeInfo();
+ getAccessibilityPrefetchStrategy().prefetchAccessibilityNodeInfos(
+ interrogatingPid, target, infos);
}
}
} finally {
try {
- callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+ infos.clear();
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
}
}
- public void findAccessibilityNodeInfoByViewIdClientThread(int viewId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
- long interrogatingTid) {
+ public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
+ int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int interrogatingPid, long interrogatingTid) {
Message message = Message.obtain();
message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
- message.arg1 = viewId;
- message.arg2 = interactionId;
- message.obj = callback;
+ message.arg1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ SomeArgs args = mPool.acquire();
+ args.argi1 = viewId;
+ args.argi2 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
// If the interrogation is performed by the same thread as the main UI
// thread in this process, set the message as a static reference so
// after this call completes the same thread but in the interrogating
@@ -4723,17 +4814,26 @@
}
public void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
- final int viewId = message.arg1;
- final int interactionId = message.arg2;
+ final int accessibilityViewId = message.arg1;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int viewId = args.argi1;
+ final int interactionId = args.argi2;
final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) message.obj;
-
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
AccessibilityNodeInfo info = null;
try {
- View root = ViewRootImpl.this.mView;
- View target = root.findViewById(viewId);
- if (target != null && target.getVisibility() == View.VISIBLE) {
- info = target.createAccessibilityNodeInfo();
+ View root = null;
+ if (accessibilityViewId != View.NO_ID) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = ViewRootImpl.this.mView;
+ }
+ if (root != null) {
+ View target = root.findViewById(viewId);
+ if (target != null && target.getVisibility() == View.VISIBLE) {
+ info = target.createAccessibilityNodeInfo();
+ }
}
} finally {
try {
@@ -4744,8 +4844,8 @@
}
}
- public void findAccessibilityNodeInfosByTextClientThread(String text,
- long accessibilityNodeId, int interactionId,
+ public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
+ String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
long interrogatingTid) {
Message message = Message.obtain();
@@ -4782,7 +4882,7 @@
mPool.release(args);
List<AccessibilityNodeInfo> infos = null;
try {
- View target = null;
+ View target;
if (accessibilityViewId != View.NO_ID) {
target = findViewByAccessibilityId(accessibilityViewId);
} else {
@@ -4937,4 +5037,88 @@
}
}
}
+
+ /**
+ * This class encapsulates a prefetching strategy for the accessibility APIs for
+ * querying window content.It is responsible to prefetch a batch of
+ * AccessibilityNodeInfos in addition to the one for a requested node. It caches
+ * the ids of the prefeteched nodes such that they are fetched only once.
+ */
+ class AccessibilityPrefetchStrategy {
+ private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 100;
+
+ // We need to keep track of what we have sent for each interrogating
+ // process. Usually there will be only one such process but we
+ // should support the general case. Note that the accessibility event
+ // stream will take care of clearing caches of querying processes that
+ // are not longer alive, so we do not waste memory.
+ private final LongSparseArray<AccessibilityNodeInfoCache> mAccessibilityNodeInfoCaches =
+ new LongSparseArray<AccessibilityNodeInfoCache>();
+
+ private AccessibilityNodeInfoCache getCacheForInterrogatingPid(long interrogatingPid) {
+ AccessibilityNodeInfoCache cache = mAccessibilityNodeInfoCaches.get(interrogatingPid);
+ if (cache == null) {
+ cache = AccessibilityNodeInfoCache.newAccessibilityNodeInfoCache();
+ mAccessibilityNodeInfoCaches.put(interrogatingPid, cache);
+ }
+ return cache;
+ }
+
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ final int cacheCount = mAccessibilityNodeInfoCaches.size();
+ for (int i = 0; i < cacheCount; i++) {
+ AccessibilityNodeInfoCache cache = mAccessibilityNodeInfoCaches.valueAt(i);
+ cache.onAccessibilityEvent(event);
+ }
+ }
+
+ public void prefetchAccessibilityNodeInfos(long interrogatingPid, View root,
+ List<AccessibilityNodeInfo> outInfos) {
+ addAndCacheNotCachedNodeInfo(interrogatingPid, root, outInfos);
+ addAndCacheNotCachedPredecessorInfos(interrogatingPid, root, outInfos);
+ addAndCacheNotCachedDescendantInfos(interrogatingPid, root, outInfos);
+ }
+
+ private void addAndCacheNotCachedNodeInfo(long interrogatingPid,
+ View view, List<AccessibilityNodeInfo> outInfos) {
+ final long accessibilityNodeId = AccessibilityNodeInfo.makeNodeId(
+ view.getAccessibilityViewId(), View.NO_ID);
+ AccessibilityNodeInfoCache cache = getCacheForInterrogatingPid(interrogatingPid);
+ if (!cache.containsKey(accessibilityNodeId)) {
+ // Account for the ids of the fetched infos. The infos will be
+ // cached in the window querying process. We just need to know
+ // which infos are cached to avoid fetching a cached one again.
+ cache.put(accessibilityNodeId, null);
+ outInfos.add(view.createAccessibilityNodeInfo());
+ }
+ }
+
+ private void addAndCacheNotCachedPredecessorInfos(long interrogatingPid, View view,
+ List<AccessibilityNodeInfo> outInfos) {
+ ViewParent predecessor = view.getParent();
+ while (predecessor instanceof View
+ && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ View predecessorView = (View) predecessor;
+ addAndCacheNotCachedNodeInfo(interrogatingPid, predecessorView, outInfos);
+ predecessor = predecessor.getParent();
+ }
+ }
+
+ private void addAndCacheNotCachedDescendantInfos(long interrogatingPid, View view,
+ List<AccessibilityNodeInfo> outInfos) {
+ if (outInfos.size() > MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE
+ || view.getAccessibilityNodeProvider() != null) {
+ return;
+ }
+ addAndCacheNotCachedNodeInfo(interrogatingPid, view, outInfos);
+ if (view instanceof ViewGroup) {
+ ViewGroup rootGroup = (ViewGroup) view;
+ final int childCount = rootGroup.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = rootGroup.getChildAt(i);
+ addAndCacheNotCachedDescendantInfos(interrogatingPid, child, outInfos);
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index d7113374..6bdc4e8 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -490,7 +490,7 @@
for (int i = 0; i < count; i++) {
ViewRootImpl root = mRoots[i];
- root.dumpGfxInfo(pw, info);
+ root.dumpGfxInfo(info);
String name = root.getClass().getName() + '@' +
Integer.toHexString(hashCode());
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 95c070c..072fdd8 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -24,7 +24,9 @@
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
+import android.view.AccessibilityNodeInfoCache;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -97,6 +99,11 @@
private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
new SparseArray<IAccessibilityServiceConnection>();
+ // The connection cache is shared between all interrogating threads since
+ // at any given time there is only one window allowing querying.
+ private static final AccessibilityNodeInfoCache sAccessibilityNodeInfoCache =
+ AccessibilityNodeInfoCache.newSynchronizedAccessibilityNodeInfoCache();
+
/**
* @return The client for the current thread.
*/
@@ -145,7 +152,9 @@
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
*
* @param connectionId The id of a connection for interacting with the system.
- * @param accessibilityWindowId A unique window id.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
* @param accessibilityNodeId A unique node accessibility id
* (accessibility view and virtual descendant id).
* @return An {@link AccessibilityNodeInfo} if found, null otherwise.
@@ -155,16 +164,22 @@
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
+ AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get(accessibilityNodeId);
+ if (cachedInfo != null) {
+ return cachedInfo;
+ }
final int interactionId = mInteractionIdCounter.getAndIncrement();
final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
accessibilityWindowId, accessibilityNodeId, interactionId, this,
Thread.currentThread().getId());
// If the scale is zero the call has failed.
if (windowScale > 0) {
- AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
- return info;
+ finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
+ if (infos != null && !infos.isEmpty()) {
+ return infos.get(0);
+ }
}
} else {
if (DEBUG) {
@@ -181,22 +196,30 @@
}
/**
- * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed
- * in the currently active window and starts from the root View in the window.
+ * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
+ * the window whose id is specified and starts from the node whose accessibility
+ * id is specified.
*
* @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id from where to start the search. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * to start from the root.
* @param viewId The id of the view.
* @return An {@link AccessibilityNodeInfo} if found, null otherwise.
*/
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int connectionId,
- int viewId) {
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int connectionId,
+ int accessibilityWindowId, long accessibilityNodeId, int viewId) {
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final float windowScale =
- connection.findAccessibilityNodeInfoByViewIdInActiveWindow(viewId,
- interactionId, this, Thread.currentThread().getId());
+ connection.findAccessibilityNodeInfoByViewId(accessibilityWindowId,
+ accessibilityNodeId, viewId, interactionId, this,
+ Thread.currentThread().getId());
// If the scale is zero the call has failed.
if (windowScale > 0) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
@@ -220,64 +243,27 @@
/**
* Finds {@link AccessibilityNodeInfo}s by View text. The match is case
- * insensitive containment. The search is performed in the currently
- * active window and starts from the root View in the window.
- *
- * @param connectionId The id of a connection for interacting with the system.
- * @param text The searched text.
- * @return A list of found {@link AccessibilityNodeInfo}s.
- */
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(
- int connectionId, String text) {
- try {
- IAccessibilityServiceConnection connection = getConnection(connectionId);
- if (connection != null) {
- final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale =
- connection.findAccessibilityNodeInfosByTextInActiveWindow(text,
- interactionId, this, Thread.currentThread().getId());
- // If the scale is zero the call has failed.
- if (windowScale > 0) {
- List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
- interactionId);
- finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
- return infos;
- }
- } else {
- if (DEBUG) {
- Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
- }
- }
- } catch (RemoteException re) {
- if (DEBUG) {
- Log.w(LOG_TAG, "Error while calling remote"
- + " findAccessibilityNodeInfosByViewTextInActiveWindow", re);
- }
- }
- return null;
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
* insensitive containment. The search is performed in the window whose
- * id is specified and starts from the View whose accessibility id is
+ * id is specified and starts from the node whose accessibility id is
* specified.
*
* @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id from where to start the search. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
* @param text The searched text.
- * @param accessibilityWindowId A unique window id.
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id) from
- * where to start the search. Use {@link android.view.View#NO_ID} to start from the root.
* @return A list of found {@link AccessibilityNodeInfo}s.
*/
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId,
- String text, int accessibilityWindowId, long accessibilityNodeId) {
+ int accessibilityWindowId, long accessibilityNodeId, String text) {
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale = connection.findAccessibilityNodeInfosByText(text,
- accessibilityWindowId, accessibilityNodeId, interactionId, this,
+ final float windowScale = connection.findAccessibilityNodeInfosByText(
+ accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
Thread.currentThread().getId());
// If the scale is zero the call has failed.
if (windowScale > 0) {
@@ -304,7 +290,9 @@
* Performs an accessibility action on an {@link AccessibilityNodeInfo}.
*
* @param connectionId The id of a connection for interacting with the system.
- * @param accessibilityWindowId The id of the window.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
* @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
* @param action The action to perform.
* @return Whether the action was performed.
@@ -319,7 +307,7 @@
accessibilityWindowId, accessibilityNodeId, action, interactionId, this,
Thread.currentThread().getId());
if (success) {
- return getPerformAccessibilityActionResult(interactionId);
+ return getPerformAccessibilityActionResultAndClear(interactionId);
}
} else {
if (DEBUG) {
@@ -334,6 +322,24 @@
return false;
}
+ public void clearCache() {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "clearCache()");
+ }
+ sAccessibilityNodeInfoCache.clear();
+ }
+
+ public void removeCachedNode(long accessibilityNodeId) {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "removeCachedNode(" + accessibilityNodeId +")");
+ }
+ sAccessibilityNodeInfoCache.remove(accessibilityNodeId);
+ }
+
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ sAccessibilityNodeInfoCache.onAccessibilityEvent(event);
+ }
+
/**
* Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}.
*
@@ -358,6 +364,9 @@
if (interactionId > mInteractionId) {
mFindAccessibilityNodeInfoResult = info;
mInteractionId = interactionId;
+ if (info != null) {
+ sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info);
+ }
}
mInstanceLock.notifyAll();
}
@@ -386,8 +395,20 @@
int interactionId) {
synchronized (mInstanceLock) {
if (interactionId > mInteractionId) {
- mFindAccessibilityNodeInfosResult = infos;
+ // If the call is not an IPC, i.e. it is made from the same process, we need to
+ // instantiate new result list to avoid passing internal instances to clients.
+ final boolean isIpcCall = (queryLocalInterface(getInterfaceDescriptor()) == null);
+ if (!isIpcCall) {
+ mFindAccessibilityNodeInfosResult = new ArrayList<AccessibilityNodeInfo>(infos);
+ } else {
+ mFindAccessibilityNodeInfosResult = infos;
+ }
mInteractionId = interactionId;
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i ++) {
+ AccessibilityNodeInfo info = infos.get(i);
+ sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info);
+ }
}
mInstanceLock.notifyAll();
}
@@ -399,7 +420,7 @@
* @param interactionId The interaction id to match the result with the request.
* @return Whether the action was performed.
*/
- private boolean getPerformAccessibilityActionResult(int interactionId) {
+ private boolean getPerformAccessibilityActionResultAndClear(int interactionId) {
synchronized (mInstanceLock) {
final boolean success = waitForResultTimedLocked(interactionId);
final boolean result = success ? mPerformAccessibilityActionResult : false;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 6939c2c..d7d6792 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -380,8 +380,8 @@
return Collections.emptyList();
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
- return client.findAccessibilityNodeInfosByText(mConnectionId, text, mWindowId,
- mSourceNodeId);
+ return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
+ text);
}
/**
@@ -903,6 +903,17 @@
}
/**
+ * Gets the id of the source node.
+ *
+ * @return The id.
+ *
+ * @hide
+ */
+ public long getSourceNodeId() {
+ return mSourceNodeId;
+ }
+
+ /**
* Sets if this instance is sealed.
*
* @param sealed Whether is sealed.
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 07aeb9a..b60f50e 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -564,6 +564,17 @@
}
/**
+ * Gets the id of the source node.
+ *
+ * @return The id.
+ *
+ * @hide
+ */
+ public long getSourceNodeId() {
+ return mSourceNodeId;
+ }
+
+ /**
* Sets the unique id of the IAccessibilityServiceConnection over which
* this instance can send requests to the system.
*
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index a90c427..ae6869c 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -31,13 +31,13 @@
IAccessibilityInteractionConnectionCallback callback,
int interrogatingPid, long interrogatingTid);
- void findAccessibilityNodeInfoByViewId(int id, int interactionId,
+ void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int id, int interactionId,
IAccessibilityInteractionConnectionCallback callback,
int interrogatingPid, long interrogatingTid);
- void findAccessibilityNodeInfosByText(String text, long accessibilityNodeId,
- int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid);
+ void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
+ long interrogatingTid);
void performAccessibilityAction(long accessibilityNodeId, int action, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index c3794be..320c75d 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -49,5 +49,7 @@
void removeAccessibilityInteractionConnection(IWindow windowToken);
- void registerEventListener(IEventListener client);
+ void registerUiTestAutomationService(IEventListener listener, in AccessibilityServiceInfo info);
+
+ void unregisterUiTestAutomationService(IEventListener listener);
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index cc8eef2..4a42e92 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -789,12 +789,18 @@
// know to handle Shift and arrows natively first
private boolean mAccessibilityScriptInjected;
- static final boolean USE_JAVA_TEXT_SELECTION = true;
- static final boolean DEBUG_TEXT_HANDLES = false;
- private Region mTextSelectionRegion = new Region();
- private Paint mTextSelectionPaint;
private Drawable mSelectHandleLeft;
private Drawable mSelectHandleRight;
+ private Rect mSelectCursorBase = new Rect();
+ private int mSelectCursorBaseLayerId;
+ private Rect mSelectCursorExtent = new Rect();
+ private int mSelectCursorExtentLayerId;
+ private Rect mSelectDraggingCursor;
+ private Point mSelectDraggingOffset = new Point();
+ static final int HANDLE_ID_START = 0;
+ static final int HANDLE_ID_END = 1;
+ static final int HANDLE_ID_BASE = 2;
+ static final int HANDLE_ID_EXTENT = 3;
static boolean sDisableNavcache = false;
// the color used to highlight the touch rectangles
@@ -2656,12 +2662,6 @@
return mZoomManager.getScale();
}
- // Called by JNI. Returns the scale to apply to the text selection handles
- /* package */ float getTextHandleScale() {
- float density = mContext.getResources().getDisplayMetrics().density;
- return density / getScale();
- }
-
/**
* Compute the reading level scale of the WebView
* @param scale The current scale.
@@ -3852,6 +3852,16 @@
if (x == mScrollingLayerRect.left && y == mScrollingLayerRect.top) {
return;
}
+ if (mSelectingText) {
+ int dx = mScrollingLayerRect.left - x;
+ int dy = mScrollingLayerRect.top - y;
+ if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) {
+ mSelectCursorBase.offset(dx, dy);
+ }
+ if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) {
+ mSelectCursorExtent.offset(dx, dy);
+ }
+ }
nativeScrollLayer(mCurrentScrollingLayerId, x, y);
mScrollingLayerRect.left = x;
mScrollingLayerRect.top = y;
@@ -4624,12 +4634,7 @@
* Select the word at the indicated content coordinates.
*/
boolean selectText(int x, int y) {
- if (!setUpSelect(true, x, y)) {
- return false;
- }
- nativeSetExtendSelection();
- mDrawSelectionPointer = false;
- mTouchMode = TOUCH_DRAG_MODE;
+ mWebViewCore.sendMessage(EventHub.SELECT_WORD_AT, x, y);
return true;
}
@@ -4830,11 +4835,8 @@
int extras = DRAW_EXTRAS_NONE;
if (mFindIsUp) {
extras = DRAW_EXTRAS_FIND;
- } else if (mSelectingText && (!USE_JAVA_TEXT_SELECTION || DEBUG_TEXT_HANDLES)) {
+ } else if (mSelectingText) {
extras = DRAW_EXTRAS_SELECTION;
- nativeSetSelectionPointer(mNativeClass,
- mDrawSelectionPointer,
- mZoomManager.getInvScale(), mSelectX, mSelectY - getTitleHeight());
} else if (drawCursorRing) {
extras = DRAW_EXTRAS_CURSOR_RING;
}
@@ -4879,7 +4881,7 @@
}
canvas.restoreToCount(saveCount);
- if (mSelectingText && USE_JAVA_TEXT_SELECTION) {
+ if (mSelectingText) {
drawTextSelectionHandles(canvas);
}
@@ -4901,30 +4903,12 @@
}
private void drawTextSelectionHandles(Canvas canvas) {
- if (mTextSelectionPaint == null) {
- mTextSelectionPaint = new Paint();
- mTextSelectionPaint.setColor(HIGHLIGHT_COLOR);
- }
- mTextSelectionRegion.setEmpty();
- nativeGetTextSelectionRegion(mNativeClass, mTextSelectionRegion);
- Rect r = new Rect();
- RegionIterator iter = new RegionIterator(mTextSelectionRegion);
- Rect clip = canvas.getClipBounds();
- while (iter.next(r)) {
- r.set(contentToViewDimension(r.left),
- contentToViewDimension(r.top),
- contentToViewDimension(r.right),
- contentToViewDimension(r.bottom));
- if (r.intersect(clip)) {
- canvas.drawRect(r, mTextSelectionPaint);
- }
- }
if (mSelectHandleLeft == null) {
mSelectHandleLeft = mContext.getResources().getDrawable(
com.android.internal.R.drawable.text_select_handle_left);
}
int[] handles = new int[4];
- nativeGetSelectionHandles(mNativeClass, handles);
+ getSelectionHandles(handles);
int start_x = contentToViewDimension(handles[0]);
int start_y = contentToViewDimension(handles[1]);
int end_x = contentToViewDimension(handles[2]);
@@ -4942,14 +4926,31 @@
mSelectHandleRight.setBounds(end_x, end_y,
end_x + mSelectHandleRight.getIntrinsicWidth(),
end_y + mSelectHandleRight.getIntrinsicHeight());
- if (DEBUG_TEXT_HANDLES) {
- mSelectHandleLeft.setAlpha(125);
- mSelectHandleRight.setAlpha(125);
- }
mSelectHandleLeft.draw(canvas);
mSelectHandleRight.draw(canvas);
}
+ /**
+ * Takes an int[4] array as an output param with the values being
+ * startX, startY, endX, endY
+ */
+ private void getSelectionHandles(int[] handles) {
+ handles[0] = mSelectCursorBase.right;
+ handles[1] = mSelectCursorBase.bottom -
+ (mSelectCursorBase.height() / 4);
+ handles[2] = mSelectCursorExtent.left;
+ handles[3] = mSelectCursorExtent.bottom
+ - (mSelectCursorExtent.height() / 4);
+ if (!nativeIsBaseFirst(mNativeClass)) {
+ int swap = handles[0];
+ handles[0] = handles[2];
+ handles[2] = swap;
+ swap = handles[1];
+ handles[1] = handles[3];
+ handles[3] = swap;
+ }
+ }
+
// draw history
private boolean mDrawHistory = false;
private Picture mHistoryPicture = null;
@@ -5009,7 +5010,7 @@
/* package */ void deleteSelection(int start, int end) {
mTextGeneration++;
WebViewCore.TextSelectionData data
- = new WebViewCore.TextSelectionData(start, end);
+ = new WebViewCore.TextSelectionData(start, end, 0);
mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0,
data);
}
@@ -5462,15 +5463,6 @@
return pinScrollTo(mContentWidth, mScrollY, true, 0);
}
}
- if (mSelectingText) {
- int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
- ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
- int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
- -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0;
- int multiplier = event.getRepeatCount() + 1;
- moveSelection(xRate * multiplier, yRate * multiplier);
- return true;
- }
if (navHandledKey(keyCode, 1, false, event.getEventTime())) {
playSoundEffect(keyCodeToSoundsEffect(keyCode));
return true;
@@ -5623,14 +5615,8 @@
mGotCenterDown = false;
if (mSelectingText) {
- if (mExtendSelection) {
- copySelection();
- selectionDone();
- } else {
- mExtendSelection = true;
- nativeSetExtendSelection();
- invalidate(); // draw the i-beam instead of the arrow
- }
+ copySelection();
+ selectionDone();
return true; // discard press if copy in progress
}
@@ -5676,21 +5662,7 @@
return false;
}
- /*
- * Enter selecting text mode, and see if CAB should be shown.
- * Returns true if the WebView is now in
- * selecting text mode (including if it was already in that mode, and this
- * method did nothing).
- */
- private boolean setUpSelect(boolean selectWord, int x, int y) {
- if (0 == mNativeClass) return false; // client isn't initialized
- if (inFullScreenMode()) return false;
- if (mSelectingText) return true;
- nativeResetSelection();
- if (selectWord && !nativeWordSelection(x, y)) {
- selectionDone();
- return false;
- }
+ private boolean startSelectActionMode() {
mSelectCallback = new SelectActionModeCallback();
mSelectCallback.setWebView(this);
if (startActionMode(mSelectCallback) == null) {
@@ -5699,54 +5671,43 @@
selectionDone();
return false;
}
- mExtendSelection = false;
- mSelectingText = mDrawSelectionPointer = true;
- if (DEBUG_TEXT_HANDLES) {
- // Debugging text handles requires running in software mode
- setLayerType(LAYER_TYPE_SOFTWARE, null);
- }
- // don't let the picture change during text selection
- WebViewCore.pauseUpdatePicture(mWebViewCore);
- if (nativeHasCursorNode()) {
- Rect rect = nativeCursorNodeBounds();
- mSelectX = contentToViewX(rect.left);
- mSelectY = contentToViewY(rect.top);
- } else if (mLastTouchY > getVisibleTitleHeightImpl()) {
- mSelectX = mScrollX + mLastTouchX;
- mSelectY = mScrollY + mLastTouchY;
- } else {
- mSelectX = mScrollX + getViewWidth() / 2;
- mSelectY = mScrollY + getViewHeightWithTitle() / 2;
- }
- nativeHideCursor();
- mMinAutoScrollX = 0;
- mMaxAutoScrollX = getViewWidth();
- mMinAutoScrollY = 0;
- mMaxAutoScrollY = getViewHeightWithTitle();
- mCurrentScrollingLayerId = nativeScrollableLayer(viewToContentX(mSelectX),
- viewToContentY(mSelectY), mScrollingLayerRect,
- mScrollingLayerBounds);
- if (mCurrentScrollingLayerId != 0) {
- if (mScrollingLayerRect.left != mScrollingLayerRect.right) {
- mMinAutoScrollX = Math.max(mMinAutoScrollX,
- contentToViewX(mScrollingLayerBounds.left));
- mMaxAutoScrollX = Math.min(mMaxAutoScrollX,
- contentToViewX(mScrollingLayerBounds.right));
- }
- if (mScrollingLayerRect.top != mScrollingLayerRect.bottom) {
- mMinAutoScrollY = Math.max(mMinAutoScrollY,
- contentToViewY(mScrollingLayerBounds.top));
- mMaxAutoScrollY = Math.min(mMaxAutoScrollY,
- contentToViewY(mScrollingLayerBounds.bottom));
- }
- }
- mMinAutoScrollX += SELECT_SCROLL;
- mMaxAutoScrollX -= SELECT_SCROLL;
- mMinAutoScrollY += SELECT_SCROLL;
- mMaxAutoScrollY -= SELECT_SCROLL;
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}
+ private void syncSelectionCursors() {
+ mSelectCursorBaseLayerId =
+ nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE, mSelectCursorBase);
+ mSelectCursorExtentLayerId =
+ nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT, mSelectCursorExtent);
+ }
+
+ private boolean setupWebkitSelect() {
+ syncSelectionCursors();
+ if (!startSelectActionMode()) {
+ selectionDone();
+ return false;
+ }
+ mSelectingText = true;
+ mTouchMode = TOUCH_DRAG_MODE;
+ return true;
+ }
+
+ private void updateWebkitSelection() {
+ int[] handles = null;
+ if (mSelectingText) {
+ handles = new int[4];
+ handles[0] = mSelectCursorBase.centerX();
+ handles[1] = mSelectCursorBase.centerY();
+ handles[2] = mSelectCursorExtent.centerX();
+ handles[3] = mSelectCursorExtent.centerY();
+ } else {
+ nativeSetTextSelection(mNativeClass, 0);
+ }
+ mWebViewCore.removeMessages(EventHub.SELECT_TEXT);
+ mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, handles);
+ }
+
/**
* Use this method to put the WebView into text selection mode.
* Do not rely on this functionality; it will be deprecated in the future.
@@ -5755,7 +5716,6 @@
@Deprecated
public void emulateShiftHeld() {
checkThread();
- setUpSelect(false, 0, 0);
}
/**
@@ -5764,17 +5724,7 @@
* @hide This is an implementation detail.
*/
public void selectAll() {
- if (0 == mNativeClass) return; // client isn't initialized
- if (inFullScreenMode()) return;
- if (!mSelectingText) {
- // retrieve a point somewhere within the text
- Point select = nativeSelectableText();
- if (!selectText(select.x, select.y)) return;
- }
- nativeSelectAll();
- mDrawSelectionPointer = false;
- mExtendSelection = true;
- invalidate();
+ mWebViewCore.sendMessage(EventHub.SELECT_ALL);
}
/**
@@ -5783,17 +5733,11 @@
void selectionDone() {
if (mSelectingText) {
mSelectingText = false;
- if (DEBUG_TEXT_HANDLES) {
- // Debugging text handles required running in software mode, set
- // back to default now
- setLayerType(LAYER_TYPE_NONE, null);
- }
// finish is idempotent, so this is fine even if selectionDone was
// called by mSelectCallback.onDestroyActionMode
mSelectCallback.finish();
mSelectCallback = null;
- WebViewCore.resumePriority();
- WebViewCore.resumeUpdatePicture(mWebViewCore);
+ updateWebkitSelection();
invalidate(); // redraw without selection
mAutoScrollX = 0;
mAutoScrollY = 0;
@@ -5821,7 +5765,7 @@
.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setText(selection);
int[] handles = new int[4];
- nativeGetSelectionHandles(mNativeClass, handles);
+ getSelectionHandles(handles);
mWebViewCore.sendMessage(EventHub.COPY_TEXT, handles);
}
invalidate(); // remove selection region and pointer
@@ -5836,7 +5780,7 @@
public void cutSelection() {
copySelection();
int[] handles = new int[4];
- nativeGetSelectionHandles(mNativeClass, handles);
+ getSelectionHandles(handles);
mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles);
}
@@ -5854,7 +5798,7 @@
CharSequence pasteText = clipItem.getText();
if (pasteText != null) {
int[] handles = new int[4];
- nativeGetSelectionHandles(mNativeClass, handles);
+ getSelectionHandles(handles);
mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles);
mWebViewCore.sendMessage(EventHub.INSERT_TEXT,
pasteText.toString());
@@ -6406,13 +6350,28 @@
EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
(eventTime - mLastTouchUpTime), eventTime);
}
- if (mSelectingText) {
- mDrawSelectionPointer = false;
- mSelectionStarted = nativeStartSelection(contentX, contentY);
+ mSelectionStarted = false;
+ if (mSelectingText && mSelectHandleLeft != null
+ && mSelectHandleRight != null) {
+ int shiftedY = y - getTitleHeight() + mScrollY;
+ int shiftedX = x + mScrollX;
+ if (mSelectHandleLeft.getBounds()
+ .contains(shiftedX, shiftedY)) {
+ mSelectionStarted = true;
+ mSelectDraggingCursor = mSelectCursorBase;
+ } else if (mSelectHandleRight.getBounds()
+ .contains(shiftedX, shiftedY)) {
+ mSelectionStarted = true;
+ mSelectDraggingCursor = mSelectCursorExtent;
+ }
+ if (mSelectDraggingCursor != null) {
+ mSelectDraggingOffset.set(
+ mSelectDraggingCursor.left - contentX,
+ mSelectDraggingCursor.top - contentY);
+ }
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "select=" + contentX + "," + contentY);
}
- invalidate();
}
}
// Trigger the link
@@ -6478,6 +6437,26 @@
removeTouchHighlight();
}
}
+ if (mSelectingText && mSelectionStarted) {
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
+ }
+ ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ if (deltaX != 0 || deltaY != 0) {
+ mSelectDraggingCursor.offsetTo(
+ contentX + mSelectDraggingOffset.x,
+ contentY + mSelectDraggingOffset.y);
+ updateWebkitSelection();
+ mLastTouchX = x;
+ mLastTouchY = y;
+ invalidate();
+ }
+ break;
+ }
+
// pass the touch events from UI thread to WebCore thread
if (shouldForwardTouchEvent() && mConfirmMove && (firstMove
|| eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
@@ -6520,30 +6499,6 @@
} else {
mVelocityTracker.addMovement(ev);
}
- if (mSelectingText && mSelectionStarted) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
- }
- ViewParent parent = getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
- mAutoScrollX = x <= mMinAutoScrollX ? -SELECT_SCROLL
- : x >= mMaxAutoScrollX ? SELECT_SCROLL : 0;
- mAutoScrollY = y <= mMinAutoScrollY ? -SELECT_SCROLL
- : y >= mMaxAutoScrollY ? SELECT_SCROLL : 0;
- if ((mAutoScrollX != 0 || mAutoScrollY != 0)
- && !mSentAutoScrollMessage) {
- mSentAutoScrollMessage = true;
- mPrivateHandler.sendEmptyMessageDelayed(
- SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
- }
- if (deltaX != 0 || deltaY != 0) {
- nativeExtendSelection(contentX, contentY);
- invalidate();
- }
- break;
- }
if (mTouchMode != TOUCH_DRAG_MODE &&
mTouchMode != TOUCH_DRAG_LAYER_MODE) {
@@ -6758,7 +6713,7 @@
} else {
if (mSelectingText) {
// tapping on selection or controls does nothing
- if (!nativeHitSelection(contentX, contentY)) {
+ if (!mSelectionStarted) {
selectionDone();
}
break;
@@ -7055,6 +7010,12 @@
if (mOverScrollGlow != null) {
mOverScrollGlow.releaseAll();
}
+
+ if (mSelectingText) {
+ mSelectionStarted = false;
+ syncSelectionCursors();
+ invalidate();
+ }
}
private void cancelTouch() {
@@ -7119,8 +7080,6 @@
private int mTrackballYMove = 0;
private boolean mSelectingText = false;
private boolean mSelectionStarted = false;
- private boolean mExtendSelection = false;
- private boolean mDrawSelectionPointer = false;
private static final int TRACKBALL_KEY_TIMEOUT = 1000;
private static final int TRACKBALL_TIMEOUT = 200;
private static final int TRACKBALL_WAIT = 100;
@@ -7189,14 +7148,8 @@
mTrackballDown = false;
mTrackballUpTime = time;
if (mSelectingText) {
- if (mExtendSelection) {
- copySelection();
- selectionDone();
- } else {
- mExtendSelection = true;
- nativeSetExtendSelection();
- invalidate(); // draw the i-beam instead of the arrow
- }
+ copySelection();
+ selectionDone();
return true; // discard press if copy in progress
}
if (DebugFlags.WEB_VIEW) {
@@ -7239,42 +7192,6 @@
return true;
}
- void moveSelection(float xRate, float yRate) {
- if (mNativeClass == 0)
- return;
- int width = getViewWidth();
- int height = getViewHeight();
- mSelectX += xRate;
- mSelectY += yRate;
- int maxX = width + mScrollX;
- int maxY = height + mScrollY;
- mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
- , mSelectX));
- mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET
- , mSelectY));
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "moveSelection"
- + " mSelectX=" + mSelectX
- + " mSelectY=" + mSelectY
- + " mScrollX=" + mScrollX
- + " mScrollY=" + mScrollY
- + " xRate=" + xRate
- + " yRate=" + yRate
- );
- }
- nativeMoveSelection(viewToContentX(mSelectX), viewToContentY(mSelectY));
- int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
- : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
- : 0;
- int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET
- : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
- : 0;
- pinScrollBy(scrollX, scrollY, true, 0);
- Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1);
- requestRectangleOnScreen(select);
- invalidate();
- }
-
private int scaleTrackballX(float xRate, int width) {
int xMove = (int) (xRate / TRACKBALL_SCALE * width);
int nextXMove = xMove;
@@ -7328,21 +7245,6 @@
float yRate = mTrackballRemainsY * 1000 / elapsed;
int viewWidth = getViewWidth();
int viewHeight = getViewHeight();
- if (mSelectingText) {
- if (!mDrawSelectionPointer) {
- // The last selection was made by touch, disabling drawing the
- // selection pointer. Allow the trackball to adjust the
- // position of the touch control.
- mSelectX = contentToViewX(nativeSelectionX());
- mSelectY = contentToViewY(nativeSelectionY());
- mDrawSelectionPointer = mExtendSelection = true;
- nativeSetExtendSelection();
- }
- moveSelection(scaleTrackballX(xRate, viewWidth),
- scaleTrackballY(yRate, viewHeight));
- mTrackballRemainsX = mTrackballRemainsY = 0;
- return;
- }
float ax = Math.abs(xRate);
float ay = Math.abs(yRate);
float maxA = Math.max(ax, ay);
@@ -9204,6 +9106,18 @@
mInputConnection.setSelection(data.mStart, data.mEnd);
}
}
+
+ nativeSetTextSelection(mNativeClass, data.mSelectTextPtr);
+ if (data.mSelectTextPtr != 0) {
+ if (!mSelectingText) {
+ setupWebkitSelect();
+ } else if (!mSelectionStarted) {
+ syncSelectionCursors();
+ }
+ } else {
+ selectionDone();
+ }
+ invalidate();
}
// Class used to use a dropdown for a <select> element
@@ -9952,7 +9866,6 @@
private native boolean nativeMoveCursor(int keyCode, int count,
boolean noScroll);
private native int nativeMoveGeneration();
- private native void nativeMoveSelection(int x, int y);
/**
* @return true if the page should get the shift and arrow keys, rather
* than select text/navigation.
@@ -9962,15 +9875,8 @@
*/
private native boolean nativePageShouldHandleShiftAndArrows();
private native boolean nativePointInNavCache(int x, int y, int slop);
- // Like many other of our native methods, you must make sure that
- // mNativeClass is not null before calling this method.
- private native void nativeResetSelection();
- private native Point nativeSelectableText();
- private native void nativeSelectAll();
private native void nativeSelectBestAt(Rect rect);
private native void nativeSelectAt(int x, int y);
- private native int nativeSelectionX();
- private native int nativeSelectionY();
private native int nativeFindIndex();
private native void nativeSetExtendSelection();
private native void nativeSetFindIsEmpty();
@@ -10026,12 +9932,14 @@
private native int nativeGetBackgroundColor();
native boolean nativeSetProperty(String key, String value);
native String nativeGetProperty(String key);
- private native void nativeGetTextSelectionRegion(int instance, Region region);
- private native void nativeGetSelectionHandles(int instance, int[] handles);
/**
* See {@link ComponentCallbacks2} for the trim levels and descriptions
*/
private static native void nativeOnTrimMemory(int level);
private static native void nativeSetPauseDrawing(int instance, boolean pause);
private static native boolean nativeDisableNavcache();
+ private static native void nativeSetTextSelection(int instance, int selection);
+ private static native int nativeGetHandleLayerId(int instance, int handle,
+ Rect cursorLocation);
+ private static native boolean nativeIsBaseFirst(int instance);
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 9c7bd34..395a638 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -835,12 +835,14 @@
}
static class TextSelectionData {
- public TextSelectionData(int start, int end) {
+ public TextSelectionData(int start, int end, int selectTextPtr) {
mStart = start;
mEnd = end;
+ mSelectTextPtr = selectTextPtr;
}
int mStart;
int mEnd;
+ int mSelectTextPtr;
}
static class TouchUpData {
@@ -1118,6 +1120,9 @@
static final int COPY_TEXT = 210;
static final int DELETE_TEXT = 211;
static final int INSERT_TEXT = 212;
+ static final int SELECT_TEXT = 213;
+ static final int SELECT_WORD_AT = 214;
+ static final int SELECT_ALL = 215;
// Private handler for WebCore messages.
private Handler mHandler;
@@ -1746,6 +1751,25 @@
case INSERT_TEXT:
nativeInsertText(mNativeClass, (String) msg.obj);
break;
+ case SELECT_TEXT: {
+ int[] args = (int[]) msg.obj;
+ if (args == null) {
+ nativeClearTextSelection(mNativeClass);
+ } else {
+ nativeSelectText(mNativeClass, args[0],
+ args[1], args[2], args[3]);
+ }
+ break;
+ }
+ case SELECT_WORD_AT: {
+ int x = msg.arg1;
+ int y = msg.arg2;
+ nativeSelectWordAt(mNativeClass, x, y);
+ break;
+ }
+ case SELECT_ALL:
+ nativeSelectAll(mNativeClass);
+ break;
}
}
};
@@ -2699,11 +2723,11 @@
// called by JNI
private void updateTextSelection(int pointer, int start, int end,
- int textGeneration) {
+ int textGeneration, int selectionPtr) {
if (mWebView != null) {
Message.obtain(mWebView.mPrivateHandler,
WebView.UPDATE_TEXT_SELECTION_MSG_ID, pointer, textGeneration,
- new TextSelectionData(start, end)).sendToTarget();
+ new TextSelectionData(start, end, selectionPtr)).sendToTarget();
}
}
@@ -2770,7 +2794,7 @@
if (mWebView != null) {
Message.obtain(mWebView.mPrivateHandler,
WebView.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID, pointer,
- textGeneration, new TextSelectionData(selStart, selEnd))
+ textGeneration, new TextSelectionData(selStart, selEnd, 0))
.sendToTarget();
}
}
@@ -3025,4 +3049,9 @@
*/
private native String nativeGetText(int nativeClass,
int startX, int startY, int endX, int endY);
+ private native void nativeSelectText(int nativeClass,
+ int startX, int startY, int endX, int endY);
+ private native void nativeClearTextSelection(int nativeClass);
+ private native void nativeSelectWordAt(int nativeClass, int x, int y);
+ private native void nativeSelectAll(int nativeClass);
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index e20d12a..67fd059 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1651,6 +1651,7 @@
// are focusable
if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
+ focusLayoutRestoreView != null &&
focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
if (!focusWasTaken) {
// selected item didn't take focus, fine, but still want
diff --git a/core/java/com/android/internal/util/FastMath.java b/core/java/com/android/internal/util/FastMath.java
index efd0871..88a17e6 100644
--- a/core/java/com/android/internal/util/FastMath.java
+++ b/core/java/com/android/internal/util/FastMath.java
@@ -26,8 +26,8 @@
* thought it may return slightly different results. It does not try to
* handle (in any meaningful way) NaN or infinities.
*/
- public static int round(float x) {
- long lx = (long)(x * (65536 * 256f));
- return (int)((lx + 0x800000) >> 24);
+ public static int round(float value) {
+ long lx = (long) (value * (65536 * 256f));
+ return (int) ((lx + 0x800000) >> 24);
}
}
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index f9e1f5b..ce734fc 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -22,19 +22,9 @@
<!-- Do not translate. These are all of the drawable resources that should be preloaded by
the zygote process before it starts forking application processes. -->
<array name="preloaded_drawables">
- <item>@drawable/spinner_black_16</item>
- <item>@drawable/spinner_black_20</item>
- <item>@drawable/spinner_black_48</item>
- <item>@drawable/spinner_black_76</item>
- <item>@drawable/spinner_white_16</item>
- <item>@drawable/spinner_white_48</item>
- <item>@drawable/spinner_white_76</item>
- <item>@drawable/toast_frame</item>
<item>@drawable/toast_frame_holo</item>
- <item>@drawable/btn_check_on_selected</item>
<item>@drawable/btn_check_on_pressed_holo_light</item>
<item>@drawable/btn_check_on_pressed_holo_dark</item>
- <item>@drawable/btn_check_on_pressed</item>
<item>@drawable/btn_check_on_holo_light</item>
<item>@drawable/btn_check_on_holo_dark</item>
<item>@drawable/btn_check_on_focused_holo_light</item>
@@ -43,13 +33,8 @@
<item>@drawable/btn_check_on_disabled_holo_dark</item>
<item>@drawable/btn_check_on_disabled_focused_holo_light</item>
<item>@drawable/btn_check_on_disabled_focused_holo_dark</item>
- <item>@drawable/btn_check_on_disable_focused</item>
- <item>@drawable/btn_check_on_disable</item>
- <item>@drawable/btn_check_on</item>
- <item>@drawable/btn_check_off_selected</item>
<item>@drawable/btn_check_off_pressed_holo_light</item>
<item>@drawable/btn_check_off_pressed_holo_dark</item>
- <item>@drawable/btn_check_off_pressed</item>
<item>@drawable/btn_check_off_holo_light</item>
<item>@drawable/btn_check_off_holo_dark</item>
<item>@drawable/btn_check_off_focused_holo_light</item>
@@ -58,16 +43,10 @@
<item>@drawable/btn_check_off_disabled_holo_dark</item>
<item>@drawable/btn_check_off_disabled_focused_holo_light</item>
<item>@drawable/btn_check_off_disabled_focused_holo_dark</item>
- <item>@drawable/btn_check_off_disable_focused</item>
- <item>@drawable/btn_check_off_disable</item>
- <item>@drawable/btn_check_label_background</item>
<item>@drawable/btn_check_holo_light</item>
<item>@drawable/btn_check_holo_dark</item>
- <item>@drawable/btn_check</item>
- <item>@drawable/btn_radio_on_selected</item>
<item>@drawable/btn_radio_on_pressed_holo_light</item>
<item>@drawable/btn_radio_on_pressed_holo_dark</item>
- <item>@drawable/btn_radio_on_pressed</item>
<item>@drawable/btn_radio_on_holo_light</item>
<item>@drawable/btn_radio_on_holo_dark</item>
<item>@drawable/btn_radio_on_focused_holo_light</item>
@@ -76,11 +55,8 @@
<item>@drawable/btn_radio_on_disabled_holo_dark</item>
<item>@drawable/btn_radio_on_disabled_focused_holo_light</item>
<item>@drawable/btn_radio_on_disabled_focused_holo_dark</item>
- <item>@drawable/btn_radio_on</item>
- <item>@drawable/btn_radio_off_selected</item>
<item>@drawable/btn_radio_off_pressed_holo_light</item>
<item>@drawable/btn_radio_off_pressed_holo_dark</item>
- <item>@drawable/btn_radio_off_pressed</item>
<item>@drawable/btn_radio_off_holo_light</item>
<item>@drawable/btn_radio_off_holo_dark</item>
<item>@drawable/btn_radio_off_focused_holo_light</item>
@@ -89,23 +65,10 @@
<item>@drawable/btn_radio_off_disabled_holo_dark</item>
<item>@drawable/btn_radio_off_disabled_focused_holo_light</item>
<item>@drawable/btn_radio_off_disabled_focused_holo_dark</item>
- <item>@drawable/btn_radio_label_background</item>
- <item>@drawable/btn_radio</item>
- <item>@drawable/btn_default_transparent_normal</item>
- <item>@drawable/btn_default_small_selected</item>
- <item>@drawable/btn_default_small_pressed</item>
- <item>@drawable/btn_default_small_normal_disable_focused</item>
- <item>@drawable/btn_default_small_normal_disable</item>
- <item>@drawable/btn_default_small_normal</item>
- <item>@drawable/btn_default_selected</item>
<item>@drawable/btn_default_pressed_holo_light</item>
<item>@drawable/btn_default_pressed_holo_dark</item>
- <item>@drawable/btn_default_pressed</item>
<item>@drawable/btn_default_normal_holo_light</item>
<item>@drawable/btn_default_normal_holo_dark</item>
- <item>@drawable/btn_default_normal_disable_focused</item>
- <item>@drawable/btn_default_normal_disable</item>
- <item>@drawable/btn_default_normal</item>
<item>@drawable/btn_default_focused_holo_light</item>
<item>@drawable/btn_default_focused_holo_dark</item>
<item>@drawable/btn_default_disabled_holo_light</item>
@@ -114,24 +77,6 @@
<item>@drawable/btn_default_disabled_focused_holo_dark</item>
<item>@drawable/btn_default_holo_dark</item>
<item>@drawable/btn_default_holo_light</item>
- <item>@drawable/btn_default</item>
- <item>@drawable/btn_default_small</item>
- <item>@drawable/btn_dropdown_disabled</item>
- <item>@drawable/btn_dropdown_disabled_focused</item>
- <item>@drawable/btn_dropdown_normal</item>
- <item>@drawable/btn_dropdown_pressed</item>
- <item>@drawable/btn_dropdown_selected</item>
- <item>@drawable/btn_star_label_background</item>
- <item>@drawable/btn_star_big_off</item>
- <item>@drawable/btn_star_big_on</item>
- <item>@drawable/btn_star_big_on_disable</item>
- <item>@drawable/btn_star_big_off_disable</item>
- <item>@drawable/btn_star_big_on_pressed</item>
- <item>@drawable/btn_star_big_off_pressed</item>
- <item>@drawable/btn_star_big_on_selected</item>
- <item>@drawable/btn_star_big_off_selected</item>
- <item>@drawable/btn_star_big_on_disable_focused</item>
- <item>@drawable/btn_star_big_off_disable_focused</item>
<item>@drawable/btn_star_off_normal_holo_light</item>
<item>@drawable/btn_star_on_normal_holo_light</item>
<item>@drawable/btn_star_on_disabled_holo_light</item>
@@ -154,7 +99,6 @@
<item>@drawable/btn_star_on_disabled_focused_holo_dark</item>
<item>@drawable/btn_star_off_disabled_focused_holo_dark</item>
<item>@drawable/btn_star_holo_dark</item>
- <item>@drawable/btn_star</item>
<item>@drawable/btn_toggle_on_pressed_holo_light</item>
<item>@drawable/btn_toggle_on_pressed_holo_dark</item>
<item>@drawable/btn_toggle_on_normal_holo_light</item>
@@ -165,7 +109,6 @@
<item>@drawable/btn_toggle_on_disabled_holo_dark</item>
<item>@drawable/btn_toggle_on_disabled_focused_holo_light</item>
<item>@drawable/btn_toggle_on_disabled_focused_holo_dark</item>
- <item>@drawable/btn_toggle_on</item>
<item>@drawable/btn_toggle_off_pressed_holo_light</item>
<item>@drawable/btn_toggle_off_pressed_holo_dark</item>
<item>@drawable/btn_toggle_off_normal_holo_light</item>
@@ -176,23 +119,10 @@
<item>@drawable/btn_toggle_off_disabled_holo_dark</item>
<item>@drawable/btn_toggle_off_disabled_focused_holo_light</item>
<item>@drawable/btn_toggle_off_disabled_focused_holo_dark</item>
- <item>@drawable/btn_toggle_off</item>
<item>@drawable/btn_toggle_holo_light</item>
<item>@drawable/btn_toggle_holo_dark</item>
- <item>@drawable/btn_toggle</item>
- <item>@drawable/btn_toggle_bg</item>
- <item>@drawable/btn_dropdown</item>
- <item>@drawable/btn_dropdown</item>
- <item>@drawable/light_header_dither</item>
- <item>@drawable/divider_horizontal_textfield</item>
- <item>@drawable/divider_horizontal_dark_opaque</item>
- <item>@drawable/divider_horizontal_dark</item>
- <item>@drawable/divider_horizontal_bright_opaque</item>
- <item>@drawable/divider_horizontal_bright</item>
- <item>@drawable/divider_vertical_dark</item>
<item>@drawable/edit_text_holo_light</item>
<item>@drawable/edit_text_holo_dark</item>
- <item>@drawable/edit_text</item>
<item>@drawable/text_cursor_holo_light</item>
<item>@drawable/text_cursor_holo_dark</item>
<item>@drawable/text_select_handle_left</item>
@@ -200,70 +130,45 @@
<item>@drawable/text_edit_paste_window</item>
<item>@drawable/expander_close_holo_dark</item>
<item>@drawable/expander_close_holo_light</item>
- <item>@drawable/expander_ic_maximized</item>
- <item>@drawable/expander_ic_minimized</item>
- <item>@drawable/expander_group</item>
<item>@drawable/expander_group_holo_dark</item>
<item>@drawable/expander_group_holo_light</item>
- <item>@drawable/list_selector_background</item>
- <item>@drawable/list_selector_background_light</item>
- <item>@drawable/list_selector_background_longpress</item>
- <item>@drawable/list_selector_background_longpress_light</item>
- <item>@drawable/list_selector_background_pressed</item>
- <item>@drawable/list_selector_background_pressed_light</item>
- <item>@drawable/list_selector_background_selected</item>
<item>@drawable/list_selector_holo_dark</item>
<item>@drawable/list_selector_holo_light</item>
<item>@drawable/list_section_divider_holo_light</item>
<item>@drawable/list_section_divider_holo_dark</item>
- <item>@drawable/menu_background</item>
- <item>@drawable/menu_background_fill_parent_width</item>
<item>@drawable/menu_hardkey_panel_holo_dark</item>
<item>@drawable/menu_hardkey_panel_holo_light</item>
<item>@drawable/menu_submenu_background</item>
- <item>@drawable/menu_selector</item>
<item>@drawable/menu_dropdown_panel_holo_light</item>
<item>@drawable/menu_dropdown_panel_holo_dark</item>
<item>@drawable/overscroll_edge</item>
<item>@drawable/overscroll_glow</item>
- <item>@drawable/panel_background</item>
- <item>@drawable/popup_bottom_bright</item>
- <item>@drawable/popup_bottom_dark</item>
- <item>@drawable/popup_bottom_medium</item>
- <item>@drawable/popup_center_bright</item>
- <item>@drawable/popup_center_dark</item>
- <item>@drawable/popup_center_medium</item>
- <item>@drawable/popup_full_bright</item>
- <item>@drawable/popup_full_dark</item>
- <item>@drawable/popup_top_bright</item>
- <item>@drawable/popup_top_dark</item>
<item>@drawable/popup_inline_error_above_holo_dark</item>
<item>@drawable/popup_inline_error_above_holo_light</item>
<item>@drawable/popup_inline_error_holo_dark</item>
<item>@drawable/popup_inline_error_holo_light</item>
+ <item>@drawable/spinner_16_outer_holo</item>
+ <item>@drawable/spinner_16_inner_holo</item>
+ <item>@drawable/spinner_48_outer_holo</item>
+ <item>@drawable/spinner_48_inner_holo</item>
+ <item>@drawable/spinner_76_outer_holo</item>
+ <item>@drawable/spinner_76_inner_holo</item>
<item>@drawable/progress_bg_holo_dark</item>
<item>@drawable/progress_bg_holo_light</item>
- <item>@drawable/progress_horizontal</item>
<item>@drawable/progress_horizontal_holo_dark</item>
<item>@drawable/progress_horizontal_holo_light</item>
- <item>@drawable/progress_indeterminate_horizontal</item>
<item>@drawable/progress_indeterminate_horizontal_holo</item>
- <item>@drawable/progress_large</item>
<item>@drawable/progress_large_holo</item>
- <item>@drawable/progress_large_white</item>
- <item>@drawable/progress_medium</item>
<item>@drawable/progress_medium_holo</item>
- <item>@drawable/progress_medium_white</item>
<item>@drawable/progress_primary_holo_dark</item>
<item>@drawable/progress_primary_holo_light</item>
<item>@drawable/progress_secondary_holo_dark</item>
<item>@drawable/progress_secondary_holo_light</item>
- <item>@drawable/progress_small</item>
<item>@drawable/progress_small_holo</item>
- <item>@drawable/progress_small_titlebar</item>
- <item>@drawable/progress_small_white</item>
<item>@drawable/scrubber_progress_horizontal_holo_dark</item>
<item>@drawable/scrubber_progress_horizontal_holo_light</item>
+ <item>@drawable/background_holo_light</item>
+ <item>@drawable/background_holo_dark</item>
<item>@drawable/screen_background_dark</item>
<item>@drawable/screen_background_dark_transparent</item>
<item>@drawable/screen_background_light</item>
@@ -272,8 +177,6 @@
<item>@drawable/screen_background_selector_light</item>
<item>@drawable/scrollbar_handle_holo_dark</item>
<item>@drawable/scrollbar_handle_holo_light</item>
- <item>@drawable/scrollbar_handle_horizontal</item>
- <item>@drawable/scrollbar_handle_vertical</item>
<item>@drawable/spinner_background_holo_dark</item>
<item>@drawable/spinner_background_holo_light</item>
<item>@drawable/spinner_ab_default_holo_dark</item>
@@ -290,9 +193,6 @@
<item>@drawable/spinner_default_holo_light</item>
<item>@drawable/spinner_disabled_holo_dark</item>
<item>@drawable/spinner_disabled_holo_light</item>
- <item>@drawable/spinner_dropdown_background</item>
- <item>@drawable/spinner_dropdown_background_down</item>
- <item>@drawable/spinner_dropdown_background_up</item>
<item>@drawable/spinner_focused_holo_dark</item>
<item>@drawable/spinner_focused_holo_light</item>
<item>@drawable/spinner_pressed_holo_dark</item>
@@ -337,7 +237,6 @@
<item>@drawable/dialog_middle_holo_light</item>
<item>@drawable/dialog_top_holo_dark</item>
<item>@drawable/dialog_top_holo_light</item>
- <item>@drawable/ic_dialog_alert</item>
<item>@drawable/ic_dialog_alert_holo_dark</item>
<item>@drawable/ic_dialog_alert_holo_light</item>
<item>@drawable/list_divider_holo_dark</item>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b514bf5..16b7ff3 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2061,7 +2061,7 @@
</attr>
<!-- Direction of the text. A heuristic is used to determine the resolved text direction
of paragraphs. -->
- <attr name="textDirection" format="integer">
+ <attr name="textDirection" format="integer">
<!-- Default -->
<enum name="inherit" value="0" />
<!-- Default for the root view. The first strong directional character determines the
@@ -2072,16 +2072,12 @@
it is LTR if it contains any strong LTR characters. If there are neither, the
paragraph direction is the view’s resolved layout direction. -->
<enum name="anyRtl" value="2" />
- <!-- The paragraph direction is the same as the one held by a 60% majority of the
- characters. If there is no majority then the paragraph direction is the resolved
- layout direction of the View. -->
- <enum name="charCount" value="3" />
<!-- The paragraph direction is left to right. -->
- <enum name="ltr" value="4" />
+ <enum name="ltr" value="3" />
<!-- The paragraph direction is right to left. -->
- <enum name="rtl" value="5" />
+ <enum name="rtl" value="4" />
<!-- The paragraph direction is coming from the system Locale. -->
- <enum name="locale" value="6" />
+ <enum name="locale" value="5" />
</attr>
</declare-styleable>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 3b6d6f1..571c4ad 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -112,7 +112,7 @@
</style>
<!-- Standard animations for a translucent window or activity. This
- style is <em>not<em> used by default for the translucent theme
+ style is <em>not</em> used by default for the translucent theme
(since translucent activities are a special case that have no
clear UI paradigm), but you can make your own specialized theme
with this animation style if you would like to have the standard
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index fe5388b..7046fc5 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -29,15 +29,16 @@
===============================================================
-->
<resources>
- <!-- The default system theme. This is the theme used for activities
- that have not explicitly set their own theme.
-
+ <!-- The default theme for apps on API level 10 and lower. This is the theme used for
+ activities that have not explicitly set their own theme.
<p>You can count on this being a dark
background with light text on top, but should try to make no
other assumptions about its appearance. In particular, the text
inside of widgets using this theme may be completely different,
with the widget container being a light color and the text on top
of it a dark color.
+ <p>If you're developing for API level 11 and higher, you should instead use {@link
+ #Theme_Holo} or {@link #Theme_DeviceDefault}.</p>
-->
<style name="Theme">
@@ -370,13 +371,12 @@
<item name="pointerStyle">@android:style/Pointer</item>
</style>
- <!-- Variant of the default (dark) theme with no title bar -->
+ <!-- Variant of {@link #Theme} with no title bar -->
<style name="Theme.NoTitleBar">
<item name="android:windowNoTitle">true</item>
</style>
- <!-- Variant of the default (dark) theme that has no title bar and
- fills the entire screen -->
+ <!-- Variant of {@link #Theme} that has no title bar and no status bar -->
<style name="Theme.NoTitleBar.Fullscreen">
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
@@ -385,7 +385,8 @@
<!-- Theme for a light background with dark text on top. Set your activity
to this theme if you would like such an appearance. As with the
default theme, you should try to assume little more than that the
- background will be a light color. -->
+ background will be a light color.
+ <p>This is designed for API level 10 and lower.</p>-->
<style name="Theme.Light">
<item name="windowBackground">@android:drawable/screen_background_selector_light</item>
<item name="colorBackground">@android:color/background_light</item>
@@ -457,19 +458,19 @@
<item name="detailsElementBackground">@android:drawable/panel_bg_holo_light</item>
</style>
- <!-- Variant of the light theme with no title bar -->
+ <!-- Variant of {@link #Theme_Light} with no title bar -->
<style name="Theme.Light.NoTitleBar">
<item name="android:windowNoTitle">true</item>
</style>
- <!-- Variant of the light theme that has no title bar and
- fills the entire screen -->
+ <!-- Variant of {@link #Theme_Light} that has no title bar and
+ no status bar -->
<style name="Theme.Light.NoTitleBar.Fullscreen">
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
- <!-- Special variation on the default theme that ensures the background is
+ <!-- Variant on {@link #Theme} that ensures the background is
completely black. This is useful for things like image viewers and
media players. If you want the normal (dark background) theme
do <em>not</em> use this, use {@link #Theme}. -->
@@ -478,40 +479,40 @@
<item name="android:colorBackground">@android:color/black</item>
</style>
- <!-- Variant of the black theme with no title bar -->
+ <!-- Variant of {@link #Theme_Black} with no title bar -->
<style name="Theme.Black.NoTitleBar">
<item name="android:windowNoTitle">true</item>
</style>
- <!-- Variant of the black theme that has no title bar and
- fills the entire screen -->
+ <!-- Variant of {@link #Theme_Black} that has no title bar and
+ no status bar -->
<style name="Theme.Black.NoTitleBar.Fullscreen">
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
- <!-- Default theme for windows that want to have the user's selected
- wallpaper appear behind them. -->
+ <!-- Theme for windows that want to have the user's selected
+ wallpaper appear behind them (for API level 10 and lower). -->
<style name="Theme.Wallpaper">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowShowWallpaper">true</item>
</style>
- <!-- Variant of the translucent theme with no title bar -->
+ <!-- Variant of {@link #Theme_Wallpaper} that has no title bar -->
<style name="Theme.Wallpaper.NoTitleBar">
<item name="android:windowNoTitle">true</item>
</style>
- <!-- Variant of the translucent theme that has no title bar and
- fills the entire screen -->
+ <!-- Variant of {@link #Theme_Wallpaper} that
+ has no title bar or status bar. -->
<style name="Theme.Wallpaper.NoTitleBar.Fullscreen">
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
- <!-- Theme for a wallpaper's setting activity that is designed to be on
- top of a dark background. -->
+ <!-- Theme for a wallpaper's setting activity, which is designed to be a transparent
+ background with a dark shade, so the previous Activity is visible in the background. -->
<style name="Theme.WallpaperSettings">
<item name="android:windowBackground">@android:drawable/screen_background_dark_transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
@@ -519,8 +520,8 @@
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
</style>
- <!-- Theme for a wallpaper's setting activity that is designed to be on
- top of a light background. -->
+ <!-- Theme for a wallpaper's setting activity, which is designed to be a transparent
+ background with a light shade, so the previous Activity is visible in the background. -->
<style name="Theme.Light.WallpaperSettings">
<item name="android:windowBackground">@android:drawable/screen_background_light_transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
@@ -538,8 +539,8 @@
<style name="PreviewWallpaperSettings">
</style>
- <!-- Default theme for translucent activities, that is windows that allow you
- to see through them to the windows behind. This sets up the translucent
+ <!-- Theme for translucent activities (on API level 10 and lower). That is, windows
+ that allow you to see through them to the windows behind. This sets up the translucent
flag and appropriate animations for your windows. -->
<style name="Theme.Translucent">
<item name="android:windowBackground">@android:color/transparent</item>
@@ -551,14 +552,14 @@
<item name="android:windowAnimationStyle">@android:style/Animation</item>
</style>
- <!-- Variant of the translucent theme with no title bar -->
+ <!-- Variant of {@link #Theme_Translucent} with no title bar -->
<style name="Theme.Translucent.NoTitleBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
- <!-- Variant of the translucent theme that has no title bar and
- fills the entire screen -->
+ <!-- Variant of {@link #Theme_Translucent} that has no title bar and
+ no status bar -->
<style name="Theme.Translucent.NoTitleBar.Fullscreen">
<item name="android:windowFullscreen">true</item>
</style>
@@ -574,7 +575,8 @@
<item name="android:windowNoDisplay">true</item>
</style>
- <!-- Default theme for dialog windows and activities, which is used by the
+ <!-- Default theme for dialog windows and activities (on API level 10 and lower),
+ which is used by the
{@link android.app.Dialog} class. This changes the window to be
floating (not fill the entire screen), and puts a frame around its
contents. You can set this theme on an activity if you would like to
@@ -622,7 +624,7 @@
<item name="listPreferredItemPaddingRight">10dip</item>
</style>
- <!-- Variation of Theme.Dialog that does not include a frame (or background).
+ <!-- Variant of {@link Theme_Dialog} that does not include a frame (or background).
The view hierarchy of the dialog is responsible for drawing all of
its pixels. -->
<style name="Theme.Dialog.NoFrame">
@@ -636,7 +638,7 @@
<item name="android:windowCloseOnTouchOutside">false</item>
</style>
- <!-- Default theme for alert dialog windows, which is used by the
+ <!-- Default theme for alert dialog windows (on API level 10 and lower), which is used by the
{@link android.app.AlertDialog} class. This is basically a dialog
but sets the background to empty so it can do two-tone backgrounds. -->
<style name="Theme.Dialog.Alert">
@@ -648,8 +650,8 @@
<item name="textAppearanceListItemSmall">@android:style/TextAppearance.Large.Inverse</item>
</style>
- <!-- Default dark theme for panel windows. This removes all extraneous
- window decorations, so you basically have an empty rectangle in which
+ <!-- Default dark theme for panel windows (on API level 10 and lower). This removes all
+ extraneous window decorations, so you basically have an empty rectangle in which
to place your content. It makes the window floating, with a transparent
background, and turns off dimming behind the window. -->
<style name="Theme.Panel">
@@ -664,8 +666,8 @@
<item name="android:windowNoTitle">true</item>
</style>
- <!-- Default light theme for panel windows. This removes all extraneous
- window decorations, so you basically have an empty rectangle in which
+ <!-- Default light theme for panel windows (on API level 10 and lower). This removes all
+ extraneous window decorations, so you basically have an empty rectangle in which
to place your content. It makes the window floating, with a transparent
background, and turns off dimming behind the window. -->
<style name="Theme.Light.Panel">
@@ -712,7 +714,7 @@
<item name="android:windowNoTitle">true</item>
</style>
- <!-- Default theme for input methods, which is used by the
+ <!-- Default theme for input methods (on API level 10 and lower), which is used by the
{@link android.inputmethodservice.InputMethodService} class.
this inherits from Theme.Panel, but sets up IME appropriate animations
and a few custom attributes. -->
@@ -723,7 +725,7 @@
<item name="android:imeExtractExitAnimation">@android:anim/input_method_extract_exit</item>
</style>
- <!-- Default theme for modern holo style input methods, which is used by the
+ <!-- Default theme for holo style input methods, which is used by the
{@link android.inputmethodservice.InputMethodService} class.
this inherits from Theme.Panel, but sets up IME appropriate animations
and a few custom attributes. -->
@@ -842,14 +844,23 @@
<item name="android:windowActionModeOverlay">true</item>
</style>
- <!-- New Honeycomb holographic theme. Dark version. The widgets in the
- holographic theme are translucent on their brackground, so applications
- must ensure that any background they use with this theme is itself
- dark; otherwise, it will be difficult to see the widgets. The new
- UI style also includes a full action bar by default.
+ <!-- Honeycomb holographic theme (dark version).
+ <p>This is the default system theme for apps that target API level 11 - 13. Starting
+ with API level 14, the default system theme is supplied by {@link #Theme_DeviceDefault},
+ which might apply a different style on different devices. If you want to ensure that your
+ app consistenly uses the Holo theme at all times, you must explicitly declare it in your
+ manifest. For example, {@code <application android:theme="@android:style/Theme.Holo">}.
+ For more information, read <a
+ href="http://android-developers.blogspot.com/2012/01/holo-everywhere.html">Holo
+ Everywhere</a>.</p>
+ <p>The widgets in the holographic theme are translucent on their brackground, so
+ applications must ensure that any background they use with this theme is itself
+ dark; otherwise, it will be difficult to see the widgets. This UI style also includes a
+ full action bar by default.</p>
- Styles used by the Holo theme are named using the convention Type.Holo.Etc.
- (For example, Widget.Holo.Button, TextAppearance.Holo.Widget.PopupMenu.Large.)
+ <p>Styles used by the Holo theme are named using the convention Type.Holo.Etc
+ (for example, {@code Widget.Holo.Button} and {@code
+ TextAppearance.Holo.Widget.PopupMenu.Large}).
Specific resources used by Holo are named using the convention @type/foo_bar_baz_holo
with trailing _dark or _light specifiers if they are not shared between both light and
dark versions of the theme. -->
@@ -951,15 +962,12 @@
<item name="listDividerAlertDialog">@android:drawable/list_divider_holo_dark</item>
<item name="expandableListPreferredItemPaddingLeft">40dip</item>
- <item name="expandableListPreferredChildPaddingLeft">
- ?android:attr/expandableListPreferredItemPaddingLeft</item>
+ <item name="expandableListPreferredChildPaddingLeft">?android:attr/expandableListPreferredItemPaddingLeft</item>
<item name="expandableListPreferredItemIndicatorLeft">3dip</item>
<item name="expandableListPreferredItemIndicatorRight">0dip</item>
- <item name="expandableListPreferredChildIndicatorLeft">
- ?android:attr/expandableListPreferredItemIndicatorLeft</item>
- <item name="expandableListPreferredChildIndicatorRight">
- ?android:attr/expandableListPreferredItemIndicatorRight</item>
+ <item name="expandableListPreferredChildIndicatorLeft">?android:attr/expandableListPreferredItemIndicatorLeft</item>
+ <item name="expandableListPreferredChildIndicatorRight">?android:attr/expandableListPreferredItemIndicatorRight</item>
<!-- Gallery attributes -->
<item name="galleryItemBackground">@android:drawable/gallery_item_background</item>
@@ -1156,10 +1164,10 @@
</style>
- <!-- New Honeycomb holographic theme. Light version. The widgets in the
+ <!-- Honeycomb holographic theme (light version). The widgets in the
holographic theme are translucent on their brackground, so applications
must ensure that any background they use with this theme is itself
- light; otherwise, it will be difficult to see the widgets. The new
+ light; otherwise, it will be difficult to see the widgets. This
UI style also includes a full action bar by default. -->
<style name="Theme.Holo.Light" parent="Theme.Light">
<item name="colorForeground">@android:color/bright_foreground_holo_light</item>
@@ -1257,15 +1265,12 @@
<item name="activatedBackgroundIndicator">@android:drawable/activated_background_holo_light</item>
<item name="expandableListPreferredItemPaddingLeft">40dip</item>
- <item name="expandableListPreferredChildPaddingLeft">
- ?android:attr/expandableListPreferredItemPaddingLeft</item>
+ <item name="expandableListPreferredChildPaddingLeft">?android:attr/expandableListPreferredItemPaddingLeft</item>
<item name="expandableListPreferredItemIndicatorLeft">3dip</item>
<item name="expandableListPreferredItemIndicatorRight">0dip</item>
- <item name="expandableListPreferredChildIndicatorLeft">
- ?android:attr/expandableListPreferredItemIndicatorLeft</item>
- <item name="expandableListPreferredChildIndicatorRight">
- ?android:attr/expandableListPreferredItemIndicatorRight</item>
+ <item name="expandableListPreferredChildIndicatorLeft">?android:attr/expandableListPreferredItemIndicatorLeft</item>
+ <item name="expandableListPreferredChildIndicatorRight">?android:attr/expandableListPreferredItemIndicatorRight</item>
<item name="listDividerAlertDialog">@android:drawable/list_divider_holo_light</item>
@@ -1522,6 +1527,7 @@
</style>
<!-- Dialog themes for Holo -->
+ <eat-comment />
<!-- Holo theme for dialog windows and activities, which is used by the
{@link android.app.Dialog} class. This changes the window to be
@@ -1554,27 +1560,27 @@
<item name="listPreferredItemPaddingRight">16dip</item>
</style>
- <!-- Variation of Theme.Holo.Dialog that has a nice minumum width for
+ <!-- Variant of Theme.Holo.Dialog that has a nice minimum width for
a regular dialog. -->
<style name="Theme.Holo.Dialog.MinWidth">
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
</style>
- <!-- Variation of Theme.Holo.Dialog that does not include a title bar. -->
+ <!-- Variant of Theme.Holo.Dialog that does not include a title bar. -->
<style name="Theme.Holo.Dialog.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
- <!-- Variation of Theme.Holo.Dialog.NoActionVar that has a nice minumum width for
+ <!-- Variant of Theme.Holo.Dialog.NoActionBar that has a nice minimum width for
a regular dialog. -->
<style name="Theme.Holo.Dialog.NoActionBar.MinWidth">
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
</style>
- <!-- Variation of Theme.Holo.Dialog that does not include a frame (or background).
+ <!-- Variant of Theme.Holo.Dialog that does not include a frame (or background).
The view hierarchy of the dialog is responsible for drawing all of
its pixels. -->
<style name="Theme.Holo.Dialog.NoFrame">
@@ -1646,20 +1652,20 @@
<item name="listPreferredItemPaddingRight">16dip</item>
</style>
- <!-- Variation of Theme.Holo.Light.Dialog that has a nice minumum width for
+ <!-- Variant of Theme.Holo.Light.Dialog that has a nice minimum width for
a regular dialog. -->
<style name="Theme.Holo.Light.Dialog.MinWidth">
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
</style>
- <!-- Variation of Theme.Holo.Light.Dialog that does not include a title bar. -->
+ <!-- Variant of Theme.Holo.Light.Dialog that does not include a title bar. -->
<style name="Theme.Holo.Light.Dialog.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
- <!-- Variation of Theme.Holo.Light.Dialog.NoActionBar that has a nice minumum width for
+ <!-- Variant of Theme.Holo.Light.Dialog.NoActionBar that has a nice minimum width for
a regular dialog. -->
<style name="Theme.Holo.Light.Dialog.NoActionBar.MinWidth">
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
@@ -1700,7 +1706,8 @@
<item name="android:windowShowWallpaper">true</item>
</style>
- <!-- Variant of the holographic (dark) theme with no title bar -->
+ <!--Default holographic (dark) for windows that want to have the user's selected
+ wallpaper appear behind them and without an action bar. -->
<style name="Theme.Holo.Wallpaper.NoTitleBar">
<item name="android:windowNoTitle">true</item>
</style>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 8135986..abe4aad 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -31,6 +31,24 @@
===============================================================
-->
<resources>
+
+ <!-- The default theme for apps that target API level 14 and higher.
+ <p>The DeviceDefault themes are aliases for a specific device’s native look and feel. The
+ DeviceDefault theme family and widget style family offer ways for you to target your app
+ to a device’s native theme with all device customizations intact.</p>
+ <p>For example, when you set your app's {@code targetSdkVersion} to 14 or higher, this
+ theme is applied to your application by default. As such, your app might appear with the
+ {@link #Theme_Holo Holo} styles on one device, but with a different set of styles on
+ another device. This is great if you want your app to fit with the device's native look and
+ feel. If, however, you prefer to keep your UI style the same across all devices, you should
+ apply a specific theme such as {@link #Theme_Holo Holo} or one of your own design. For more
+ information, read <a
+ href="http://android-developers.blogspot.com/2012/01/holo-everywhere.html">Holo
+ Everywhere</a>.</p>
+ <p>Styles used by the DeviceDefault theme are named using the convention
+ Type.DeviceDefault.Etc (for example, {@code Widget.DeviceDefault.Button} and
+ {@code TextAppearance.DeviceDefault.Widget.PopupMenu.Large}).</p>
+ -->
<style name="Theme.DeviceDefault" parent="Theme.Holo" >
<!-- Text styles -->
<item name="textAppearance">@android:style/TextAppearance.DeviceDefault</item>
@@ -176,12 +194,16 @@
<!-- DatePicker style -->
<item name="datePickerStyle">@style/Widget.DeviceDefault.DatePicker</item>
</style>
+
+ <!-- Variant of {@link #Theme_DeviceDefault} with no action bar -->
<style name="Theme.DeviceDefault.NoActionBar" parent="Theme.Holo.NoActionBar" >
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar -->
<style name="Theme.DeviceDefault.NoActionBar.Fullscreen" parent="Theme.Holo.NoActionBar.Fullscreen" >
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault} with a light-colored style -->
<style name="Theme.DeviceDefault.Light" parent="Theme.Holo.Light" >
<!-- Text styles -->
<item name="textAppearance">@android:style/TextAppearance.DeviceDefault.Light</item>
@@ -322,12 +344,17 @@
<!-- DatePicker style -->
<item name="datePickerStyle">@style/Widget.DeviceDefault.Light.DatePicker</item>
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar -->
<style name="Theme.DeviceDefault.Light.NoActionBar" parent="Theme.Holo.Light.NoActionBar" >
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar -->
<style name="Theme.DeviceDefault.Light.NoActionBar.Fullscreen" parent="Theme.Holo.Light.NoActionBar.Fullscreen" >
</style>
+ <!-- DeviceDefault theme for dialog windows and activities. This changes the window to be
+ floating (not fill the entire screen), and puts a frame around its contents. You can set this
+ theme on an activity if you would like to make an activity that looks like a Dialog. -->
<style name="Theme.DeviceDefault.Dialog" parent="Theme.Holo.Dialog" >
<item name="android:windowTitleStyle">@android:style/DialogWindowTitle.DeviceDefault</item>
<item name="android:windowAnimationStyle">@android:style/Animation.DeviceDefault.Dialog</item>
@@ -338,15 +365,23 @@
<item name="textAppearance">@android:style/TextAppearance.DeviceDefault</item>
<item name="textAppearanceInverse">@android:style/TextAppearance.DeviceDefault.Inverse</item>
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault_Dialog} that has a nice minimum width for a
+ regular dialog. -->
<style name="Theme.DeviceDefault.Dialog.MinWidth" parent="Theme.Holo.Dialog.MinWidth" >
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar -->
<style name="Theme.DeviceDefault.Dialog.NoActionBar" parent="Theme.Holo.Dialog.NoActionBar" >
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width
+ for a regular dialog. -->
<style name="Theme.DeviceDefault.Dialog.NoActionBar.MinWidth" parent="Theme.Holo.Dialog.NoActionBar.MinWidth" >
</style>
+ <!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be
+ floating (not fill the entire screen), and puts a frame around its contents. You can set this
+ theme on an activity if you would like to make an activity that looks like a Dialog.-->
<style name="Theme.DeviceDefault.Light.Dialog" parent="Theme.Holo.Light.Dialog" >
<item name="android:windowTitleStyle">@android:style/DialogWindowTitle.DeviceDefault.Light</item>
<item name="android:windowAnimationStyle">@android:style/Animation.DeviceDefault.Dialog</item>
@@ -357,42 +392,71 @@
<item name="textAppearance">@android:style/TextAppearance.DeviceDefault.Light</item>
<item name="textAppearanceInverse">@android:style/TextAppearance.DeviceDefault.Light.Inverse</item>
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} that has a nice minimum width for a
+ regular dialog. -->
<style name="Theme.DeviceDefault.Light.Dialog.MinWidth" parent="Theme.Holo.Light.Dialog.MinWidth" >
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} without an action bar -->
<style name="Theme.DeviceDefault.Light.Dialog.NoActionBar" parent="Theme.Holo.Light.Dialog.NoActionBar" >
</style>
+ <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog_NoActionBar} that has a nice minimum
+ width for a regular dialog. -->
<style name="Theme.DeviceDefault.Light.Dialog.NoActionBar.MinWidth" parent="Theme.Holo.Light.Dialog.NoActionBar.MinWidth" >
</style>
+ <!-- DeviceDefault theme for a window that will be displayed either full-screen on smaller
+ screens (small, normal) or as a dialog on larger screens (large, xlarge). -->
<style name="Theme.DeviceDefault.DialogWhenLarge" parent="Theme.Holo.DialogWhenLarge" >
</style>
+ <!-- DeviceDefault theme for a window without an action bar that will be displayed either
+ full-screen on smaller screens (small, normal) or as a dialog on larger screens (large,
+ xlarge). -->
<style name="Theme.DeviceDefault.DialogWhenLarge.NoActionBar" parent="Theme.Holo.DialogWhenLarge.NoActionBar" >
</style>
+ <!-- DeviceDefault light theme for a window that will be displayed either full-screen on smaller
+ screens (small, normal) or as a dialog on larger screens (large, xlarge). -->
<style name="Theme.DeviceDefault.Light.DialogWhenLarge" parent="Theme.Holo.Light.DialogWhenLarge" >
</style>
+ <!-- DeviceDefault light theme for a window without an action bar that will be displayed either
+ full-screen on smaller screens (small, normal) or as a dialog on larger screens (large,
+ xlarge). -->
<style name="Theme.DeviceDefault.Light.DialogWhenLarge.NoActionBar" parent="Theme.Holo.Light.DialogWhenLarge.NoActionBar" >
</style>
+ <!-- DeviceDefault theme for panel windows. This removes all extraneous window
+ decorations, so you basically have an empty rectangle in which to place your content. It makes
+ the window floating, with a transparent background, and turns off dimming behind the window. -->
<style name="Theme.DeviceDefault.Panel" parent="Theme.Holo.Panel" >
</style>
+ <!-- DeviceDefault light theme for panel windows. This removes all extraneous window
+ decorations, so you basically have an empty rectangle in which to place your content. It makes
+ the window floating, with a transparent background, and turns off dimming behind the window. -->
<style name="Theme.DeviceDefault.Light.Panel" parent="Theme.Holo.Light.Panel" >
</style>
+ <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
+ behind them. -->
<style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Holo.Wallpaper" >
</style>
+ <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
+ behind them and without an action bar. -->
<style name="Theme.DeviceDefault.Wallpaper.NoTitleBar" parent="Theme.Holo.Wallpaper.NoTitleBar" >
</style>
+ <!-- DeviceDefault style for input methods, which is used by the
+ {@link android.inputmethodservice.InputMethodService} class.-->
<style name="Theme.DeviceDefault.InputMethod" parent="Theme.Holo.InputMethod" >
</style>
+ <!-- Variant of the DeviceDefault (light) theme that has a solid (opaque) action bar with an
+ inverse color profile. -->
<style name="Theme.DeviceDefault.Light.DarkActionBar" parent="Theme.Holo.Light.DarkActionBar" >
<item name="android:actionBarStyle">@android:style/Widget.DeviceDefault.Light.ActionBar.Solid.Inverse</item>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
index 3ffa085..7233e7f 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
@@ -46,7 +46,7 @@
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
- if (!UtilHelper.isWifiOnly()) {
+ if (!UtilHelper.isWifiOnly(getContext())) {
suite.addTestSuite(WifiApStress.class);
suite.addTestSuite(WifiStressTest.class);
} else {
@@ -64,7 +64,7 @@
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- if (!UtilHelper.isWifiOnly()) {
+ if (!UtilHelper.isWifiOnly(getContext())) {
String valueStr = (String) icicle.get("softap_iterations");
if (valueStr != null) {
int iteration = Integer.parseInt(valueStr);
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
index 20aae47..9819c54 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
@@ -40,7 +40,7 @@
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
- if (!UtilHelper.isWifiOnly()) {
+ if (!UtilHelper.isWifiOnly(getContext())) {
suite.addTestSuite(ConnectivityManagerMobileTest.class);
} else {
// create a new test suite
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java
index 1b966bf..b9fe6ed 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java
@@ -16,12 +16,31 @@
package com.android.connectivitymanagertest;
-import android.os.SystemProperties;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
public class UtilHelper {
- public static boolean isWifiOnly() {
- return "wifi-only".equals(SystemProperties.get("ro.carrier"));
+
+ private static Boolean mIsWifiOnly = null;
+ private static final Object sLock = new Object();
+
+ /**
+ * Return true if device is a wifi only device.
+ */
+ public static boolean isWifiOnly(Context context) {
+ synchronized (sLock) {
+ // cache the result from pkgMgr statically. It will never change, since its a
+ // device configuration setting
+ if (mIsWifiOnly == null) {
+ PackageManager pkgMgr = context.getPackageManager();
+ mIsWifiOnly = Boolean.valueOf(!pkgMgr
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ && pkgMgr.hasSystemFeature(PackageManager.FEATURE_WIFI));
+ String deviceType = mIsWifiOnly ? "wifi-only" : "telephony";
+ Log.d("ConnectivityManagerTest", String.format("detected a %s device", deviceType));
+ }
+ }
+ return mIsWifiOnly;
}
-
-
}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
index b1f4bf1..52326d5 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
@@ -16,37 +16,31 @@
package com.android.connectivitymanagertest.functional;
-import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
-import com.android.connectivitymanagertest.UtilHelper;
-
-import android.content.Intent;
import android.content.Context;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.app.Instrumentation;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Settings;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.State;
-import android.net.NetworkInfo.DetailedState;
import android.net.wifi.WifiManager;
-
-import android.test.suitebuilder.annotation.LargeTest;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.provider.Settings;
import android.test.ActivityInstrumentationTestCase2;
-import com.android.connectivitymanagertest.ConnectivityManagerTestRunner;
-import com.android.connectivitymanagertest.NetworkState;
+import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
-public class ConnectivityManagerMobileTest
- extends ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> {
+import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
+import com.android.connectivitymanagertest.ConnectivityManagerTestRunner;
+import com.android.connectivitymanagertest.NetworkState;
+import com.android.connectivitymanagertest.UtilHelper;
+
+public class ConnectivityManagerMobileTest extends
+ ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> {
private static final String LOG_TAG = "ConnectivityManagerMobileTest";
- private static final String PKG_NAME = "com.android.connectivitymanagertest";
private String TEST_ACCESS_POINT;
private ConnectivityManagerTestActivity cmActivity;
private WakeLock wl;
+ private boolean mIsWifiOnlyDevice;
public ConnectivityManagerMobileTest() {
super(ConnectivityManagerTestActivity.class);
@@ -69,7 +63,8 @@
log("airplane is not disabled, disable it.");
cmActivity.setAirplaneMode(getInstrumentation().getContext(), false);
}
- if (!UtilHelper.isWifiOnly()) {
+ mIsWifiOnlyDevice = UtilHelper.isWifiOnly(mRunner.getTargetContext());
+ if (!mIsWifiOnlyDevice) {
if (!cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT)) {
// Note: When the test fails in setUp(), tearDown is not called. In that case,
@@ -166,7 +161,7 @@
public void testConnectToWifi() {
assertNotNull("SSID is null", TEST_ACCESS_POINT);
NetworkInfo networkInfo;
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
//Prepare for connectivity verification
networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
@@ -185,7 +180,7 @@
log("wifi state is enabled");
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
@@ -197,7 +192,7 @@
cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
assertTrue(false);
}
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
log("Mobile state transition validation failed.");
log("reason: " +
@@ -232,13 +227,13 @@
ConnectivityManagerTestActivity.LONG_TIMEOUT));
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
NetworkInfo networkInfo;
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
//Prepare for connectivity state verification
networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
@@ -258,7 +253,7 @@
// Wait for Wifi to be connected and mobile to be disconnected
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
@@ -288,7 +283,7 @@
sleep(ConnectivityManagerTestActivity.SHORT_TIMEOUT);
NetworkInfo networkInfo;
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
networkInfo.getState(),
@@ -304,7 +299,7 @@
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
@@ -316,7 +311,7 @@
cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
assertTrue(false);
}
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
log("Mobile state transition validation failed.");
log("reason: " +
@@ -393,7 +388,7 @@
cmActivity.setAirplaneMode(getInstrumentation().getContext(), true);
NetworkInfo networkInfo;
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
@@ -419,7 +414,7 @@
cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
assertTrue("State validation failed", false);
}
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
log("state validation for Mobile failed");
log("reason: " +
@@ -471,7 +466,7 @@
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
index 2069789..feb63cd 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
@@ -16,10 +16,6 @@
package com.android.connectivitymanagertest.stress;
-import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
-import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
-import com.android.connectivitymanagertest.UtilHelper;
-
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.State;
@@ -31,15 +27,15 @@
import android.net.wifi.WifiManager;
import android.os.Environment;
import android.os.PowerManager;
-import android.os.IPowerManager;
-import android.os.SystemClock;
-import android.os.ServiceManager;
import android.provider.Settings;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
-
import android.util.Log;
+import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
+import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
+import com.android.connectivitymanagertest.UtilHelper;
+
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
@@ -79,6 +75,7 @@
private String mPassword;
private ConnectivityManagerStressTestRunner mRunner;
private BufferedWriter mOutputWriter = null;
+ private boolean mIsWifiOnlyDevice;
public WifiStressTest() {
super(ConnectivityManagerTestActivity.class);
@@ -100,6 +97,7 @@
mOutputWriter = new BufferedWriter(new FileWriter(new File(
Environment.getExternalStorageDirectory(), OUTPUT_FILE), true));
mAct.turnScreenOn();
+ mIsWifiOnlyDevice = UtilHelper.isWifiOnly(mRunner.getTargetContext());
if (!mAct.mWifiManager.isWifiEnabled()) {
log("Enable wi-fi before stress tests.");
if (!mAct.enableWifi()) {
@@ -271,7 +269,7 @@
assertTrue("Wait for Wi-Fi to idle timeout",
mAct.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
6 * ConnectivityManagerTestActivity.SHORT_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
// use long timeout as the pppd startup may take several retries.
assertTrue("Wait for cellular connection timeout",
mAct.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
@@ -282,7 +280,7 @@
assertEquals("Wi-Fi is reconnected", State.DISCONNECTED,
mAct.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState());
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertEquals("Cellular connection is down", State.CONNECTED,
mAct.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState());
assertTrue("Mobile is connected, but no data connection.", mAct.pingTest(null));
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
index b4a0581..a9f144b 100644
--- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
+++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
@@ -14,12 +14,12 @@
package android.accessibilityservice;
-import com.android.frameworks.coretests.R;
-
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
+import com.android.frameworks.coretests.R;
+
/**
* Activity for testing the accessibility APIs for "interrogation" of
* the screen content. These APIs allow exploring the screen and
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
index 259a094..fa48093 100644
--- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
@@ -14,26 +14,21 @@
package android.accessibilityservice;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
-import android.content.Context;
import android.graphics.Rect;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
-import android.view.View;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
-import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.IAccessibilityManager;
import com.android.frameworks.coretests.R;
+import com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -48,21 +43,15 @@
*/
public class InterrogationActivityTest
extends ActivityInstrumentationTestCase2<InterrogationActivity> {
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static String LOG_TAG = "InterrogationActivityTest";
- // Timeout before give up wait for the system to process an accessibility setting change.
- private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000;
-
// Timeout for the accessibility state of an Activity to be fully initialized.
- private static final int TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS = 100;
+ private static final int TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS = 5000;
// Handle to a connection to the AccessibilityManagerService
- private static int sConnectionId = View.NO_ID;
-
- // The last received accessibility event
- private volatile AccessibilityEvent mLastAccessibilityEvent;
+ private UiTestAutomationBridge mUiTestAutomationBridge;
public InterrogationActivityTest() {
super(InterrogationActivity.class);
@@ -70,16 +59,39 @@
@Override
public void setUp() throws Exception {
- ensureConnection();
- bringUpActivityWithInitalizedAccessbility();
+ super.setUp();
+ mUiTestAutomationBridge = new UiTestAutomationBridge();
+ mUiTestAutomationBridge.connect();
+ mUiTestAutomationBridge.executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ // wait for the first accessibility event
+ @Override
+ public void run() {
+ // bring up the activity
+ getActivity();
+ }
+ },
+ new Predicate<AccessibilityEvent>() {
+ @Override
+ public boolean apply(AccessibilityEvent event) {
+ return (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ && event.getPackageName().equals(getActivity().getPackageName()));
+ }
+ },
+ TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mUiTestAutomationBridge.disconnect();
+ super.tearDown();
}
@LargeTest
public void testFindAccessibilityNodeInfoByViewId() throws Exception {
final long startTimeMillis = SystemClock.uptimeMillis();
try {
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ AccessibilityNodeInfo button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertNotNull(button);
assertEquals(0, button.getChildCount());
@@ -125,8 +137,8 @@
final long startTimeMillis = SystemClock.uptimeMillis();
try {
// find a view by text
- List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfosByTextInActiveWindow(sConnectionId, "butto");
+ List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge
+ .findAccessibilityNodeInfosByTextInActiveWindow("butto");
assertEquals(9, buttons.size());
} finally {
if (DEBUG) {
@@ -141,12 +153,9 @@
public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception {
final long startTimeMillis = SystemClock.uptimeMillis();
try {
- bringUpActivityWithInitalizedAccessbility();
-
// find a view by text
- List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfosByTextInActiveWindow(sConnectionId,
- "contentDescription");
+ List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge
+ .findAccessibilityNodeInfosByTextInActiveWindow("contentDescription");
assertEquals(1, buttons.size());
} finally {
if (DEBUG) {
@@ -177,8 +186,8 @@
classNameAndTextList.add("android.widget.ButtonButton8");
classNameAndTextList.add("android.widget.ButtonButton9");
- AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.root);
+ AccessibilityNodeInfo root = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root);
assertNotNull("We must find the existing root.", root);
Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
@@ -216,16 +225,16 @@
final long startTimeMillis = SystemClock.uptimeMillis();
try {
// find a view and make sure it is not focused
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ AccessibilityNodeInfo button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertFalse(button.isFocused());
// focus the view
assertTrue(button.performAction(ACTION_FOCUS));
// find the view again and make sure it is focused
- button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertTrue(button.isFocused());
} finally {
if (DEBUG) {
@@ -240,24 +249,24 @@
final long startTimeMillis = SystemClock.uptimeMillis();
try {
// find a view and make sure it is not focused
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ AccessibilityNodeInfo button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertFalse(button.isFocused());
// focus the view
assertTrue(button.performAction(ACTION_FOCUS));
// find the view again and make sure it is focused
- button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertTrue(button.isFocused());
// unfocus the view
assertTrue(button.performAction(ACTION_CLEAR_FOCUS));
// find the view again and make sure it is not focused
- button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertFalse(button.isFocused());
} finally {
if (DEBUG) {
@@ -273,16 +282,16 @@
final long startTimeMillis = SystemClock.uptimeMillis();
try {
// find a view and make sure it is not selected
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ AccessibilityNodeInfo button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertFalse(button.isSelected());
// select the view
assertTrue(button.performAction(ACTION_SELECT));
// find the view again and make sure it is selected
- button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertTrue(button.isSelected());
} finally {
if (DEBUG) {
@@ -297,24 +306,24 @@
final long startTimeMillis = SystemClock.uptimeMillis();
try {
// find a view and make sure it is not selected
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ AccessibilityNodeInfo button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertFalse(button.isSelected());
// select the view
assertTrue(button.performAction(ACTION_SELECT));
// find the view again and make sure it is selected
- button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertTrue(button.isSelected());
// unselect the view
assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
// find the view again and make sure it is not selected
- button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
assertFalse(button.isSelected());
} finally {
if (DEBUG) {
@@ -330,23 +339,33 @@
final long startTimeMillis = SystemClock.uptimeMillis();
try {
// find a view and make sure it is not focused
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
- assertFalse(button.isSelected());
+ final AccessibilityNodeInfo button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+ assertFalse(button.isFocused());
- // focus the view
- assertTrue(button.performAction(ACTION_FOCUS));
-
- synchronized (this) {
- try {
- wait(TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS);
- } catch (InterruptedException ie) {
- /* ignore */
+ AccessibilityEvent event = mUiTestAutomationBridge
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ // focus the view
+ assertTrue(button.performAction(ACTION_FOCUS));
}
- }
+ },
+ new Predicate<AccessibilityEvent>() {
+ @Override
+ public boolean apply(AccessibilityEvent event) {
+ return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getText().get(0).equals(button.getText()));
+ }
+ },
+ TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS);
+
+ // check the last event
+ assertNotNull(event);
// check that last event source
- AccessibilityNodeInfo source = mLastAccessibilityEvent.getSource();
+ AccessibilityNodeInfo source = event.getSource();
assertNotNull(source);
// bounds
@@ -389,8 +408,9 @@
final long startTimeMillis = SystemClock.uptimeMillis();
try {
// find a view and make sure it is not focused
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5);
+ AccessibilityNodeInfo button = mUiTestAutomationBridge
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
+ assertNotNull(button);
AccessibilityNodeInfo parent = button.getParent();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
@@ -410,71 +430,4 @@
}
}
}
-
- private void bringUpActivityWithInitalizedAccessbility() {
- mLastAccessibilityEvent = null;
- // bring up the activity
- getActivity();
-
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- if (mLastAccessibilityEvent != null) {
- final int eventType = mLastAccessibilityEvent.getEventType();
- if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- return;
- }
- }
- final long remainingTimeMillis = TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS
- - (SystemClock.uptimeMillis() - startTimeMillis);
- if (remainingTimeMillis <= 0) {
- return;
- }
- synchronized (this) {
- try {
- wait(remainingTimeMillis);
- } catch (InterruptedException e) {
- /* ignore */
- }
- }
- }
- }
-
- private void ensureConnection() throws Exception {
- if (sConnectionId == View.NO_ID) {
- IEventListener listener = new IEventListener.Stub() {
- public void setConnection(IAccessibilityServiceConnection connection,
- int connectionId) {
- sConnectionId = connectionId;
- if (connection != null) {
- AccessibilityInteractionClient.getInstance().addConnection(connectionId,
- connection);
- } else {
- AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
- }
- synchronized (this) {
- notifyAll();
- }
- }
-
- public void onInterrupt() {}
-
- public void onAccessibilityEvent(AccessibilityEvent event) {
- mLastAccessibilityEvent = AccessibilityEvent.obtain(event);
- synchronized (this) {
- notifyAll();
- }
- }
- };
-
- AccessibilityManager accessibilityManager =
- AccessibilityManager.getInstance(getInstrumentation().getContext());
-
- synchronized (this) {
- IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
- manager.registerEventListener(listener);
- wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING);
- }
- }
- }
}
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index 6c01d44..4a9a684 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -131,7 +131,7 @@
</a></li>
<li><a href="<?cs var:toroot ?>guide/topics/ui/menus.html">
<span class="en">Menus</span>
- </a></li>
+ </a> <span class="new">updated</span></li>
<li><a href="<?cs var:toroot ?>guide/topics/ui/actionbar.html">
<span class="en">Action Bar</span>
</a></li>
diff --git a/docs/html/guide/topics/ui/actionbar.jd b/docs/html/guide/topics/ui/actionbar.jd
index b83bde7..e59fa0f 100644
--- a/docs/html/guide/topics/ui/actionbar.jd
+++ b/docs/html/guide/topics/ui/actionbar.jd
@@ -113,9 +113,10 @@
href="{@docRoot}guide/topics/ui/menus.html#OptionsMenu">options menu</a> directly in the action bar,
as "action items." Action items can also provide an "action view," which provides an embedded
widget for even more immediate action behaviors. Menu items that are not promoted
-to an action item are available in the overflow menu, revealed by either the device MENU button
+to an action item are available in the overflow menu, revealed by either the device <em>Menu</em>
+button
(when available) or by an "overflow menu" button in the action bar (when the device does not
-include a MENU button).</p>
+include a <em>Menu</em> button).</p>
</li>
</ul>
@@ -125,6 +126,10 @@
landscape handset), showing the logo on the left, navigation tabs, and an action item on the
right (plus the overflow menu button).</p>
+<p class="note"><strong>Note:</strong> If you're looking for information about the contextual
+action bar for displaying contextual action items, see the <a
+href="{@docRoot}guide/topics/ui/menus.html#context-menu">Menu</a> guide.</p>
+
<div class="design-announce">
<p><strong>Action Bar Design</strong></p>
@@ -225,9 +230,10 @@
href="{@docRoot}guide/topics/ui/menus.html#OptionsMenu">options menu</a>. To do this, you can
declare that the menu item should appear in the action bar as an "action item." An action item can
include an icon and/or a text title. If a menu item does not appear as an action item, then the
-system places it in the overflow menu. The overflow menu is revealed either by the device MENU
+system places it in the overflow menu. The overflow menu is revealed either by the device
+<em>Menu</em>
button (if provided by the device) or an additional button in the action bar (if the device does not
-provide the MENU button).</p>
+provide the <em>Menu</em> button).</p>
<div class="figure" style="width:359px">
<img src="{@docRoot}images/ui/actionbar-item-withtext.png" height="57" alt="" />
@@ -1421,7 +1427,7 @@
</style>
<!-- style for the action bar tab text -->
- <style name="CustomTabTextStyle">
+ <style name="CustomTabTextStyle" parent="@android:style/TextAppearance.Holo">
<item name="android:textColor">#2456c2</item>
</style>
</resources>
@@ -1437,8 +1443,7 @@
manifest file like this:</p>
<pre>
-<application android:theme="@style/CustomActivityTheme"
- ... />
+<application android:theme="@style/CustomActivityTheme" ... />
</pre>
<p>For more information about using style and theme resources in your application, read <a
@@ -1457,7 +1462,7 @@
parent action bar style such as {@link android.R.style#Widget_Holo_ActionBar
Widget.Holo.ActionBar}.</p>
-<p>For example, if you want to change the action bar's background, you could use the following
+<p>For example, if you want to change the action bar's background, you can use the following
styles:</p>
<pre>
@@ -1465,14 +1470,15 @@
<resources>
<!-- the theme applied to the application or activity -->
<style name="CustomActivityTheme" parent="@android:style/Theme.Holo">
- <item name="android:actionBarTabTextStyle">@style/customTabTextStyle</item>
+ <item name="android:actionBarStyle">@style/MyActionBar</item>
<!-- other activity and action bar styles here -->
</style>
- <!-- style for the action bar, simply to change the background -->
- <style parent="@android:style/Widget.Holo.ActionBar">
+ <!-- style for the action bar backgrounds -->
+ <style name="MyActionBar" parent="@android:style/Widget.Holo.ActionBar">
<item name="android:background">@drawable/ab_background</item>
- <item name="android:backgroundSplit">@drawable/ab_background</item>
+ <item name="android:backgroundStacked">@drawable/ab_background</item>
+ <item name="android:backgroundSplit">@drawable/ab_split_background</item>
</style>
</resources>
</pre>
diff --git a/docs/html/guide/topics/ui/menus.jd b/docs/html/guide/topics/ui/menus.jd
index 7b5b3dc..a2313b3 100644
--- a/docs/html/guide/topics/ui/menus.jd
+++ b/docs/html/guide/topics/ui/menus.jd
@@ -6,77 +6,129 @@
<div id="qv-wrapper">
<div id="qv">
<h2>In this document</h2>
- <ol>
- <li><a href="#xml">Creating a Menu Resource</a></li>
- <li><a href="#Inflating">Inflating a Menu Resource</a>
- <li><a href="#options-menu">Creating an Options Menu</a>
- <ol>
- <li><a href="#ChangingTheMenu">Changing menu items at runtime</a></li>
- </ol>
- </li>
- <li><a href="#context-menu">Creating a Context Menu</a></li>
- <li><a href="#submenu">Creating a Submenu</a></li>
- <li><a href="#features">Other Menu Features</a>
- <ol>
- <li><a href="#groups">Menu groups</a></li>
- <li><a href="#checkable">Checkable menu items</a></li>
- <li><a href="#shortcuts">Shortcut keys</a></li>
- <li><a href="#intents">Dynamically adding menu intents</a></li>
- </ol>
- </li>
- </ol>
+<ol>
+ <li><a href="#xml">Defining a Menu in XML</a></li>
+ <li><a href="#options-menu">Creating an Options Menu</a>
+ <ol>
+ <li><a href="#RespondingOptionsMenu">Handling click events</a></li>
+ <li><a href="#ChangingTheMenu">Changing menu items at runtime</a></li>
+ </ol>
+ </li>
+ <li><a href="#context-menu">Creating Contextual Menus</a>
+ <ol>
+ <li><a href="#FloatingContextMenu">Creating a floating context menu</a></li>
+ <li><a href="#CAB">Using the contextual action bar</a></li>
+ </ol>
+ </li>
+ <li><a href="#PopupMenu">Creating a Popup Menu</a>
+ <ol>
+ <li><a href="#PopupEvents">Handling click events</a></li>
+ </ol>
+ </li>
+ <li><a href="#groups">Creating Menu Groups</a>
+ <ol>
+ <li><a href="#checkable">Using checkable menu items</a></li>
+ </ol>
+ </li>
+ <li><a href="#intents">Adding Menu Items Based on an Intent</a>
+ <ol>
+ <li><a href="#AllowingToAdd">Allowing your activity to be added to other menus</a></li>
+ </ol>
+ </li>
+</ol>
<h2>Key classes</h2>
<ol>
<li>{@link android.view.Menu}</li>
<li>{@link android.view.MenuItem}</li>
<li>{@link android.view.ContextMenu}</li>
- <li>{@link android.view.SubMenu}</li>
+ <li>{@link android.view.ActionMode}</li>
</ol>
<h2>See also</h2>
<ol>
<li><a href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a></li>
<li><a href="{@docRoot}guide/topics/resources/menu-resource.html">Menu Resource</a></li>
+ <li><a
+href="http://android-developers.blogspot.com/2012/01/say-goodbye-to-menu-button.html">Say
+Goodbye to the Menu Button</a></li>
</ol>
</div>
</div>
-<p>Menus are an important part of an activity's user interface, which provide users a familiar
-way to perform actions. Android offers a simple framework for you to add standard
-menus to your application.</p>
+<p>Menus are a common user interface component in many types of applications. To provide a familiar
+and consistent user experience, you should use the {@link android.view.Menu} APIs to present user
+actions and other options in your activities.</p>
-<p>There are three types of application menus:</p>
+<p>Beginning with Android 3.0 (API level 11), Android-powered devices are no longer required to
+provide a dedicated <em>Menu</em> button. With this change, Android apps should migrate away from a
+dependence on the traditional 6-item menu panel and instead provide an action bar to present common
+user actions.</p>
+
+<p>Although the design and user experience for some menu items have changed, the semantics to define
+a set of actions and options is still based on the {@link android.view.Menu} APIs. This
+guide shows how to create the three fundamental types of menus or action presentations on all
+versions of Android:</p>
+
<dl>
- <dt><strong>Options Menu</strong></dt>
- <dd>The primary collection of menu items for an activity, which appears when the user touches
-the MENU button. When your application is running on Android 3.0 or later, you can provide
-quick access to select menu items by placing them directly in the <a
-href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a>, as "action items."</dd>
- <dt><strong>Context Menu</strong></dt>
- <dd>A floating list of menu items that appears when the user touches and holds a view
-that's registered to provide a context menu.
+ <dt><strong>Options menu and action bar</strong></dt>
+ <dd>The <a href="#options-menu">options menu</a> is the primary collection of menu items for an
+activity. It's where you should place actions that have a global impact on the app, such as
+"Search," "Compose email," and "Settings."
+ <p>If you're developing for Android 2.3 or lower, users can
+reveal the options menu panel by pressing the <em>Menu</em> button.</p>
+ <p>On Android 3.0 and higher, items from the options menu are presented by the <a
+href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a> as a combination of on-screen action
+items and overflow options. Beginning with Android 3.0, the <em>Menu</em> button is deprecated (some
+devices
+don't have one), so you should migrate toward using the action bar to provide access to actions and
+other options.</p>
+ <p>See the section about <a href="#options-menu">Creating an Options Menu</a>.</p>
+ </dd>
+
+ <dt><strong>Context menu and contextual action mode</strong></dt>
+
+ <dd>A context menu is a <a href="#FloatingContextMenu">floating menu</a> that appears when the
+user performs a long-click on an element. It provides actions that affect the selected content or
+context frame.
+ <p>When developing for Android 3.0 and higher, you should instead use the <a
+href="#CAB">contextual action mode</a> to enable actions on selected content. This mode displays
+action items that affect the selected content in a bar at the top of the screen and allows the user
+to select multiple items.</p>
+ <p>See the section about <a href="#context-menu">Creating Contextual Menus</a>.</p>
</dd>
- <dt><strong>Submenu</strong></dt>
- <dd>A floating list of menu items that appears when the user touches a menu item that contains
-a nested menu.</dd>
+
+ <dt><strong>Popup menu</strong></dt>
+ <dd>A popup menu displays a list of items in a vertical list that's anchored to the view that
+invoked the menu. It's good for providing an overflow of actions that relate to specific content or
+to provide options for a second part of a command. Actions in a popup menu should
+<strong>not</strong> directly affect the corresponding content—that's what contextual actions
+are for. Rather, the popup menu is for extended actions that relate to regions of content in your
+activity.
+ <p>See the section about <a href="#PopupMenu">Creating a Popup Menu</a>.</p>
+</dd>
</dl>
-<p>This document shows you how to create each type of menu, using XML to define the content of
-the menu and callback methods in your activity to respond when the user selects an item.</p>
+<h2 id="xml">Defining a Menu in XML</h2>
-<h2 id="xml">Creating a Menu Resource</h2>
+<p>For all menu types, Android provides a standard XML format to define menu items.
+Instead of building a menu in your activity's code, you should define a menu and all its items in an
+XML <a href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>. You can then
+inflate the menu resource (load it as a {@link android.view.Menu} object) in your activity or
+fragment.</p>
-<p>Instead of instantiating a {@link android.view.Menu} in your application code, you should
-define a menu and all its items in an XML <a
-href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>, then inflate the menu
-resource (load it as a programmable object) in your application code. Using a menu resource to
-define your menu is a good practice because it separates the content for the menu from your
-application code. It's also easier to visualize the structure and content of a menu in XML.</p>
+<p>Using a menu resource is a good practice for a few reasons:</p>
+<ul>
+ <li>It's easier to visualize the menu structure in XML.</li>
+ <li>It separates the content for the menu from your application's behavioral code.</li>
+ <li>It allows you to create alternative menu configurations for different platform versions,
+screen sizes, and other configurations by leveraging the <a
+href="{@docRoot}guide/topics/resources/index.html">app resources</a> framework.</li>
+</ul>
-<p>To create a menu resource, create an XML file inside your project's <code>res/menu/</code>
+<p>To define the menu, create an XML file inside your project's <code>res/menu/</code>
directory and build the menu with the following elements:</p>
<dl>
<dt><code><menu></code></dt>
@@ -90,8 +142,8 @@
<dt><code><group></code></dt>
<dd>An optional, invisible container for {@code <item>} elements. It allows you to
-categorize menu items so they share properties such as active state and visibility. See the
-section about <a href="#groups">Menu groups</a>.</dd>
+categorize menu items so they share properties such as active state and visibility. For more
+information, see the section about <a href="#groups">Creating Menu Groups</a>.</dd>
</dl>
@@ -101,14 +153,17 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/new_game"
android:icon="@drawable/ic_new_game"
- android:title="@string/new_game" />
+ android:title="@string/new_game"
+ android:showAsAction="ifRoom"/>
<item android:id="@+id/help"
android:icon="@drawable/ic_help"
android:title="@string/help" />
</menu>
</pre>
-<p>This example defines a menu with two items. Each item includes the attributes:</p>
+<p>The <code><item></code> element supports several attributes you can use to define an item's
+appearance and behavior. The items in the above menu include the following attributes:</p>
+
<dl>
<dt>{@code android:id}</dt>
<dd>A resource ID that's unique to the item, which allows the application can recognize the item
@@ -117,312 +172,24 @@
<dd>A reference to a drawable to use as the item's icon.</dd>
<dt>{@code android:title}</dt>
<dd>A reference to a string to use as the item's title.</dd>
+ <dt>{@code android:showAsAction}</dt>
+ <dd>Specifies when and how this item should appear as an action item in the <a
+href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>.</dd>
</dl>
-<p>There are many more attributes you can include in an {@code <item>}, including some that
- specify how the item may appear in the <a
-href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a>. For more information about the XML
-syntax and attributes for a menu resource, see the <a
-href="{@docRoot}guide/topics/resources/menu-resource.html">Menu Resource</a> reference.</p>
+<p>These are the most important attributes you should use, but there are many more available.
+For information about all the supported attributes, see the <a
+href="{@docRoot}guide/topics/resources/menu-resource.html">Menu Resource</a> document.</p>
-
-
-<h2 id="Inflating">Inflating a Menu Resource</h2>
-
-<p>From your application code, you can inflate a menu resource (convert the XML resource into a
-programmable object) using
-{@link android.view.MenuInflater#inflate(int,Menu) MenuInflater.inflate()}. For
-example, the following code inflates the <code>game_menu.xml</code> file defined above, during the
-{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} callback method, to
-use the menu as the activity's Options Menu:</p>
-
-<pre>
-@Override
-public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.game_menu, menu);
- return true;
-}
-</pre>
-
-<p>The {@link android.app.Activity#getMenuInflater()} method returns a {@link
-android.view.MenuInflater} for the activity. With this object, you can call {@link
-android.view.MenuInflater#inflate(int,Menu) inflate()}, which inflates a menu resource into a
-{@link android.view.Menu} object. In this example, the menu resource defined by
-<code>game_menu.xml</code>
-is inflated into the {@link android.view.Menu} that was passed into {@link
-android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}. (This callback method for
-the Options Menu is discussed more in the next section.)</p>
-
-
-
-<h2 id="options-menu">Creating an Options Menu</h2>
-
-<div class="figure" style="width:200px">
- <img src="{@docRoot}images/options_menu.png" height="333" alt="" />
- <p class="img-caption"><strong>Figure 1.</strong> Screenshot of the Options Menu in the
-Browser.</p>
-</div>
-
-<p>The Options Menu is where you should include basic activity actions and necessary navigation
-items (for example, a button to open the application settings). Items in the Options Menu are
-accessible in two distinct ways: the MENU button or in the <a
-href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a> (on devices running Android 3.0
-or higher).</p>
-
-<p>When running on a device with Android 2.3 and lower, the Options Menu appears at the bottom of
-the screen, as shown in figure 1. When opened, the first visible portion of the Options Menu is
-the icon menu. It holds the first six menu items. If you add more than six items to the
-Options Menu, Android places the sixth item and those after it into the overflow menu, which the
-user can open by touching the "More" menu item.</p>
-
-<p>On Android 3.0 and higher, items from the Options Menu is placed in the Action Bar, which appears
-at the top of the activity in place of the traditional title bar. By default all items from the
-Options Menu are placed in the overflow menu, which the user can open by touching the menu icon
-on the right side of the Action Bar. However, you can place select menu items directly in the
-Action Bar as "action items," for instant access, as shown in figure 2.</p>
-
-<p>When the Android system creates the Options Menu for the first time, it calls your
-activity's {@link android.app.Activity#onCreateOptionsMenu(Menu)
-onCreateOptionsMenu()} method. Override this method in your activity
-and populate the {@link android.view.Menu} that is passed into the method,
-{@link android.view.Menu} by inflating a menu resource as described above in <a
-href="#Inflating">Inflating a Menu Resource</a>. For example:</p>
-
-<pre>
-@Override
-public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.game_menu, menu);
- return true;
-}
-</pre>
-
-<div class="figure" style="width:450px">
-<img src="{@docRoot}images/ui/actionbar.png" alt="" />
-<p class="img-caption"><strong>Figure 2.</strong> Action bar from the <a
-href="{@docRoot}resources/samples/HoneycombGallery/index.html">Honeycomb Gallery</a> app, including
-navigation tabs and a camera action item (plus the overflow menu button).</p>
-</div>
-
-<p>You can also populate the menu in code, using {@link android.view.Menu#add(int,int,int,int)
-add()} to add items to the {@link android.view.Menu}.</p>
-
-<p class="note"><strong>Note:</strong> On Android 2.3 and lower, the system calls {@link
-android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} to create the Options Menu
-when the user opens it for the first time, but on Android 3.0 and greater, the system creates it as
-soon as the activity is created, in order to populate the Action Bar.</p>
-
-
-<h3 id="RespondingOptionsMenu">Responding to user action</h3>
-
-<p>When the user selects a menu item from the Options Menu (including action items in the
-Action Bar), the system calls your activity's
-{@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}
-method. This method passes the
-{@link android.view.MenuItem} that the user selected. You can identify the menu item by calling
-{@link android.view.MenuItem#getItemId()}, which returns the unique ID for the menu
-item (defined by the {@code android:id} attribute in the menu resource or with an integer
-given to the {@link android.view.Menu#add(int,int,int,int) add()} method). You can match this ID
-against known menu items and perform the appropriate action. For example:</p>
-
-<pre>
-@Override
-public boolean onOptionsItemSelected(MenuItem item) {
- // Handle item selection
- switch (item.getItemId()) {
- case R.id.new_game:
- newGame();
- return true;
- case R.id.help:
- showHelp();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
-}
-</pre>
-
-<p>In this example, {@link android.view.MenuItem#getItemId()} queries the ID for the selected menu
-item and the switch statement compares the ID against the resource IDs that were assigned to menu
-items in the XML resource. When a switch case successfully handles the menu item, it
-returns {@code true} to indicate that the item selection was handled. Otherwise, the default
-statement passes the menu item to the super class, in
-case it can handle the item selected. (If you've directly extended the {@link android.app.Activity}
-class, then the super class returns {@code false}, but it's a good practice to
-pass unhandled menu items to the super class instead of directly returning {@code false}.)</p>
-
-<p>Additionally, Android 3.0 adds the ability for you to define the on-click behavior for a menu
-item in the <a href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a> XML,
-using the {@code android:onClick} attribute. So you don't need to implement {@link
-android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}. Using the {@code
-android:onClick} attribute, you can specify a method to call when the user selects the menu item.
-Your activity must then implement the method specified in the {@code android:onClick} attribute so
-that it accepts a single {@link android.view.MenuItem} parameter—when the system calls this
-method, it passes the menu item selected.</p>
-
-<p class="note"><strong>Tip:</strong> If your application contains multiple activities and
-some of them provide the same Options Menu, consider creating
-an activity that implements nothing except the {@link android.app.Activity#onCreateOptionsMenu(Menu)
-onCreateOptionsMenu()} and {@link android.app.Activity#onOptionsItemSelected(MenuItem)
-onOptionsItemSelected()} methods. Then extend this class for each activity that should share the
-same Options Menu. This way, you have to manage only one set of code for handling menu
-actions and each descendant class inherits the menu behaviors.<br/><br/>
-If you want to add menu items to one of your descendant activities,
-override {@link android.app.Activity#onCreateOptionsMenu(Menu)
-onCreateOptionsMenu()} in that activity. Call {@code super.onCreateOptionsMenu(menu)} so the
-original menu items are created, then add new menu items with {@link
-android.view.Menu#add(int,int,int,int) menu.add()}. You can also override the super class's
-behavior for individual menu items.</p>
-
-
-<h3 id="ChangingTheMenu">Changing menu items at runtime</h3>
-
-<p>Once the activity is created, the {@link android.app.Activity#onCreateOptionsMenu(Menu)
-onCreateOptionsMenu()} method is
-called only once, as described above. The system keeps and re-uses the {@link
-android.view.Menu} you define in this method until your activity is destroyed. If you want to change
-the Options Menu any time after it's first created, you must override the
-{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} method. This passes
-you the {@link android.view.Menu} object as it currently exists. This is useful if you'd like to
-remove, add, disable, or enable menu items depending on the current state of your application.</p>
-
-<p>On Android 2.3 and lower, the system calls {@link android.app.Activity#onPrepareOptionsMenu(Menu)
-onPrepareOptionsMenu()} each time the user opens the Options Menu.</p>
-
-<p>On Android 3.0 and higher, you must call {@link android.app.Activity#invalidateOptionsMenu
-invalidateOptionsMenu()} when you want to update the menu, because the menu is always open. The
-system will then call {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}
-so you can update the menu items.</p>
-
-<p class="note"><strong>Note:</strong>
-You should never change items in the Options Menu based on the {@link android.view.View} currently
-in focus. When in touch mode (when the user is not using a trackball or d-pad), views
-cannot take focus, so you should never use focus as the basis for modifying
-items in the Options Menu. If you want to provide menu items that are context-sensitive to a {@link
-android.view.View}, use a <a href="#context-menu">Context Menu</a>.</p>
-
-<p>If you're developing for Android 3.0 or higher, be sure to also read the <a
-href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a> developer guide.</p>
-
-
-
-
-<h2 id="context-menu">Creating a Context Menu</h2>
-
-<p>A context menu is conceptually similar to the menu displayed when the user performs a
-"right-click" on a PC. You should use a context menu to provide the user access to
-actions that pertain to a specific item in the user interface. On Android, a context menu is
-displayed when the user performs a "long press" (press and hold) on an item.</p>
-
-<p>You can create a context menu for any View, though context menus are most often used for items in
-a {@link android.widget.ListView}. When the user performs a long-press on an item in a ListView and
-the list is registered to provide a context menu, the list item signals to the user that a context
-menu is available by animating its background color—it transitions from
-orange to white before opening the context menu. (The Contacts application demonstrates this
-feature.)</p>
-
-<div class="sidebox-wrapper">
-<div class="sidebox">
-<h3>Register a ListView</h3>
-<p>If your activity uses a {@link android.widget.ListView} and
-you want all list items to provide a context menu, register all items for a context
-menu by passing the {@link android.widget.ListView} to {@link
-android.app.Activity#registerForContextMenu(View) registerForContextMenu()}. For
-example, if you're using a {@link android.app.ListActivity}, register all list items like this:</p>
-<p><code>registerForContextMenu({@link android.app.ListActivity#getListView()});</code></p>
-</div>
-</div>
-
-<p>In order for a View to provide a context menu, you must "register" the view for a context
-menu. Call {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} and
-pass it the {@link android.view.View} you want to give a context menu. When this View then
-receives a long-press, it displays a context menu.</p>
-
-<p>To define the context menu's appearance and behavior, override your activity's context menu
-callback methods, {@link android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo)
-onCreateContextMenu()} and
-{@link android.app.Activity#onContextItemSelected(MenuItem) onContextItemSelected()}.</p>
-
-<p>For example, here's an {@link
-android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo)
-onCreateContextMenu()} that uses the {@code context_menu.xml} menu resource:</p>
-<pre>
-@Override
-public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- super.onCreateContextMenu(menu, v, menuInfo);
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.context_menu, menu);
-}
-</pre>
-
-<p>{@link android.view.MenuInflater} is used to inflate the context menu from a <a
-href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>. (You can also use
-{@link android.view.Menu#add(int,int,int,int) add()} to add menu items.) The callback method
-parameters include the {@link android.view.View}
-that the user selected and a {@link android.view.ContextMenu.ContextMenuInfo} object that provides
-additional information about the item selected. You might use these parameters to determine
-which context menu should be created, but in this example, all context menus for the activity are
-the same.</p>
-
-<p>Then when the user selects an item from the context menu, the system calls {@link
-android.app.Activity#onContextItemSelected(MenuItem) onContextItemSelected()}. Here is an example
-of how you can handle selected items:</p>
-
-<pre>
-@Override
-public boolean onContextItemSelected(MenuItem item) {
- AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
- switch (item.getItemId()) {
- case R.id.edit:
- editNote(info.id);
- return true;
- case R.id.delete:
- deleteNote(info.id);
- return true;
- default:
- return super.onContextItemSelected(item);
- }
-}
-</pre>
-
-<p>The structure of this code is similar to the example for <a href="#options-menu">Creating an
-Options Menu</a>, in which {@link android.view.MenuItem#getItemId()} queries the ID for the selected
-menu item and a switch statement matches the item to the IDs that are defined in the menu resource.
-And like the options menu example, the default statement calls the super class in case it
-can handle menu items not handled here, if necessary.</p>
-
-<p>In this example, the selected item is an item from a {@link android.widget.ListView}. To
-perform an action on the selected item, the application needs to know the list
-ID for the selected item (it's position in the ListView). To get the ID, the application calls
-{@link android.view.MenuItem#getMenuInfo()}, which returns a {@link
-android.widget.AdapterView.AdapterContextMenuInfo} object that includes the list ID for the
-selected item in the {@link android.widget.AdapterView.AdapterContextMenuInfo#id id} field. The
-local methods <code>editNote()</code> and <code>deleteNote()</code> methods accept this list ID to
-perform an action on the data specified by the list ID.</p>
-
-<p class="note"><strong>Note:</strong> Items in a context menu do not support icons or shortcut
-keys.</p>
-
-
-
-<h2 id="submenu">Creating Submenus</h2>
-
-<p>A submenu is a menu that the user can open by selecting an item in another menu. You can add a
-submenu to any menu (except a submenu). Submenus are useful when your application has a lot of
-functions that can be organized into topics, like items in a PC application's menu bar (File, Edit,
-View, etc.).</p>
-
-<p>When creating your <a href="{@docRoot}guide/topics/resources/menu-resource.html">menu
-resource</a>, you can create a submenu by adding a {@code <menu>} element as the child of an
-{@code <item>}. For example:</p>
+<p>You can add a submenu to an item in any menu (except a submenu) by adding a {@code <menu>}
+element as the child of an {@code <item>}. Submenus are useful when your application has a lot
+of functions that can be organized into topics, like items in a PC application's menu bar (File,
+Edit, View, etc.). For example:</p>
<pre>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/file"
- android:icon="@drawable/file"
android:title="@string/file" >
<!-- "file" submenu -->
<menu>
@@ -435,23 +202,625 @@
</menu>
</pre>
-<p>When the user selects an item from a submenu, the parent menu's respective on-item-selected
-callback method receives the event. For instance, if the above menu is applied as an Options Menu,
-then the {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} method
-is called when a submenu item is selected.</p>
-
-<p>You can also use {@link android.view.Menu#addSubMenu(int,int,int,int) addSubMenu()} to
-dynamically add a {@link android.view.SubMenu} to an existing {@link android.view.Menu}. This
-returns the new {@link android.view.SubMenu} object, to which you can add
-submenu items, using {@link android.view.Menu#add(int,int,int,int) add()}</p>
+<p>To use the menu in your activity, you need to inflate the menu resource (convert the XML
+resource into a programmable object) using {@link android.view.MenuInflater#inflate(int,Menu)
+MenuInflater.inflate()}. In the following sections, you'll see how to inflate a menu for each
+menu type.</p>
-<h2 id="features">Other Menu Features</h2>
+<h2 id="options-menu">Creating an Options Menu</h2>
-<p>Here are some other features that you can apply to most menu items.</p>
+<div class="figure" style="width:200px;margin:0">
+ <img src="{@docRoot}images/options_menu.png" height="333" alt="" />
+ <p class="img-caption"><strong>Figure 1.</strong> Options menu in the
+Browser, on Android 2.3.</p>
+</div>
-<h3 id="groups">Menu groups</h3>
+<p>The options menu is where you should include actions and other options that are relevant to the
+current activity context, such as "Search," "Compose email," and "Settings."</p>
+
+<p>Where the items in your options menu appear on the screen depends on the version for which you've
+developed your application:</p>
+
+<ul>
+ <li>If you've developed your application for <strong>Android 2.3.x (API level 10) or
+lower</strong>, the contents of your options menu appear at the bottom of the screen when the user
+presses the <em>Menu</em> button, as shown in figure 1. When opened, the first visible portion is
+the icon
+menu, which holds up to six menu items. If your menu includes more than six items, Android places
+the sixth item and the rest into the overflow menu, which the user can open by selecting
+<em>More</em>.</li>
+
+ <li>If you've developed your application for <strong>Android 3.0 (API level 11) and
+higher</strong>, items from the options menu are available in the <a
+href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>. By default, the system
+places all items in the action overflow, which the user can reveal with the action overflow icon on
+the right side of the action bar (or by pressing the device <em>Menu</em> button, if available). To
+enable
+quick access to important actions, you can promote a few items to appear in the action bar by adding
+{@code android:showAsAction="ifRoom"} to the corresponding {@code <item>} elements (see figure
+2). <p>For more information about action items and other action bar behaviors, see the <a
+href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a> guide. </p>
+<p class="note"><strong>Note:</strong> Even if you're <em>not</em> developing for Android 3.0 or
+higher, you can build your own action bar layout for a similar effect. For an example of how you can
+support older versions of Android with an action bar, see the <a
+href="{@docRoot}resources/samples/ActionBarCompat/index.html">Action Bar Compatibility</a>
+sample.</p>
+</li>
+</ul>
+
+<img src="{@docRoot}images/ui/actionbar.png" alt="" />
+<p class="img-caption"><strong>Figure 2.</strong> Action bar from the <a
+href="{@docRoot}resources/samples/HoneycombGallery/index.html">Honeycomb Gallery</a> app, showing
+navigation tabs and a camera action item (plus the action overflow button).</p>
+
+<p>You can declare items for the options menu from either your {@link android.app.Activity}
+subclass or a {@link android.app.Fragment} subclass. If both your activity and fragment(s)
+declare items for the options menu, they are combined in the UI. The activity's items appear
+first, followed by those of each fragment in the order in which each fragment is added to the
+activity. If necessary, you can re-order the menu items with the {@code android:orderInCategory}
+attribute in each {@code <item>} you need to move.</p>
+
+<p>To specify the options menu for an activity, override {@link
+android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} (fragments provide their
+own {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} callback). In this
+method, you can inflate your menu resource (<a href="#xml">defined in XML</a>) into the {@link
+android.view.Menu} provided in the callback. For example:</p>
+
+<pre>
+@Override
+public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = {@link android.app.Activity#getMenuInflater()};
+ inflater.inflate(R.menu.game_menu, menu);
+ return true;
+}
+</pre>
+
+<p>You can also add menu items using {@link android.view.Menu#add(int,int,int,int)
+add()} and retrieve items with {@link android.view.Menu#findItem findItem()} to revise their
+properties with {@link android.view.MenuItem} APIs.</p>
+
+<p>If you've developed your application for Android 2.3.x and lower, the system calls {@link
+android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} to create the options menu
+when the user opens the menu for the first time. If you've developed for Android 3.0 and higher, the
+system calls {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} when
+starting the activity, in order to show items to the action bar.</p>
+
+
+
+<h3 id="RespondingOptionsMenu">Handling click events</h3>
+
+<p>When the user selects an item from the options menu (including action items in the action bar),
+the system calls your activity's {@link android.app.Activity#onOptionsItemSelected(MenuItem)
+onOptionsItemSelected()} method. This method passes the {@link android.view.MenuItem} selected. You
+can identify the item by calling {@link android.view.MenuItem#getItemId()}, which returns the unique
+ID for the menu item (defined by the {@code android:id} attribute in the menu resource or with an
+integer given to the {@link android.view.Menu#add(int,int,int,int) add()} method). You can match
+this ID against known menu items to perform the appropriate action. For example:</p>
+
+<pre>
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle item selection
+ switch (item.getItemId()) {
+ case R.id.new_game:
+ newGame();
+ return true;
+ case R.id.help:
+ showHelp();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+}
+</pre>
+
+<p>When you successfully handle a menu item, return {@code true}. If you don't handle the menu
+item, you should call the superclass implementation of {@link
+android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} (the default
+implementation returns false).</p>
+
+<p>If your activity includes fragments, the system first calls {@link
+android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} for the activity then
+for each fragment (in the order each fragment was added) until one returns
+{@code true} or all fragments have been called.</p>
+
+<p class="note"><strong>Tip:</strong> Android 3.0 adds the ability for you to define the on-click
+behavior for a menu item in XML, using the {@code android:onClick} attribute. The value for the
+attribute must be the name of a method defined by the activity using the menu. The method
+must be public and accept a single {@link android.view.MenuItem} parameter—when the system
+calls this method, it passes the menu item selected. For more information and an example, see the <a
+href="{@docRoot}guide/topics/resources/menu-resource.html">Menu Resource</a> document.</p>
+
+<p class="note"><strong>Tip:</strong> If your application contains multiple activities and
+some of them provide the same options menu, consider creating
+an activity that implements nothing except the {@link android.app.Activity#onCreateOptionsMenu(Menu)
+onCreateOptionsMenu()} and {@link android.app.Activity#onOptionsItemSelected(MenuItem)
+onOptionsItemSelected()} methods. Then extend this class for each activity that should share the
+same options menu. This way, you can manage one set of code for handling menu
+actions and each descendant class inherits the menu behaviors.
+If you want to add menu items to one of the descendant activities,
+override {@link android.app.Activity#onCreateOptionsMenu(Menu)
+onCreateOptionsMenu()} in that activity. Call {@code super.onCreateOptionsMenu(menu)} so the
+original menu items are created, then add new menu items with {@link
+android.view.Menu#add(int,int,int,int) menu.add()}. You can also override the super class's
+behavior for individual menu items.</p>
+
+
+<h3 id="ChangingTheMenu">Changing menu items at runtime</h3>
+
+<p>After the system calls {@link android.app.Activity#onCreateOptionsMenu(Menu)
+onCreateOptionsMenu()}, it retains an instance of the {@link android.view.Menu} you populate and
+will not call {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}
+again unless the menu is invalidated for some reason. However, you should use {@link
+android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} only to create the initial
+menu state and not to make changes during the activity lifecycle.</p>
+
+<p>If you want to modify the options menu based on
+events that occur during the activity lifecycle, you can do so in
+the {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} method. This
+method passes you the {@link android.view.Menu} object as it currently exists so you can modify it,
+such as add, remove, or disable items. (Fragments also provide an {@link
+android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()} callback.)</p>
+
+<p>On Android 2.3.x and lower, the system calls {@link
+android.app.Activity#onPrepareOptionsMenu(Menu)
+onPrepareOptionsMenu()} each time the user opens the options menu (presses the <em>Menu</em>
+button).</p>
+
+<p>On Android 3.0 and higher, the options menu is considered to always be open when menu items are
+presented in the action bar. When an event occurs and you want to perform a menu update, you must
+call {@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()} to request that the
+system call {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}.</p>
+
+<p class="note"><strong>Note:</strong>
+You should never change items in the options menu based on the {@link android.view.View} currently
+in focus. When in touch mode (when the user is not using a trackball or d-pad), views
+cannot take focus, so you should never use focus as the basis for modifying
+items in the options menu. If you want to provide menu items that are context-sensitive to a {@link
+android.view.View}, use a <a href="#context-menu">Context Menu</a>.</p>
+
+
+
+
+<h2 id="context-menu">Creating Contextual Menus</h2>
+
+<div class="figure" style="width:420px;margin-top:-1em">
+ <img src="{@docRoot}images/ui/menu-context.png" alt="" />
+ <p class="img-caption"><strong>Figure 3.</strong> Screenshots of a floating context menu (left)
+and the contextual action bar (right).</p>
+</div>
+
+<p>A contextual menu offers actions that affect a specific item or context frame in the UI. You
+can provide a context menu for any view, but they are most often used for items in a {@link
+android.widget.ListView}, {@link android.widget.GridView}, or other view collections in which
+the user can perform direct actions on each item.</p>
+
+<p>There are two ways to provide contextual actions:</p>
+<ul>
+ <li>In a <a href="#FloatingContextMenu">floating context menu</a>. A menu appears as a
+floating list of menu items (similar to a dialog) when the user performs a long-click (press and
+hold) on a view that declares support for a context menu. Users can perform a contextual
+action on one item at a time.</li>
+
+ <li>In the <a href="#CAB">contextual action mode</a>. This mode is a system implementation of
+{@link android.view.ActionMode} that displays a <em>contextual action bar</em> at the top of the
+screen with action items that affect the selected item(s). When this mode is active, users
+can perform an action on multiple items at once (if your app allows it).</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> The contextual action mode is available on Android 3.0 (API
+level 11) and higher and is the preferred technique for displaying contextual actions when
+available. If your app supports versions lower than 3.0 then you should fall back to a floating
+context menu on those devices.</p>
+
+
+<h3 id="FloatingContextMenu">Creating a floating context menu</h3>
+
+<p>To provide a floating context menu:</p>
+<ol>
+ <li>Register the {@link android.view.View} to which the context menu should be associated by
+calling {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} and pass
+it the {@link android.view.View}.
+ <p>If your activity uses a {@link android.widget.ListView} or {@link android.widget.GridView} and
+you want each item to provide the same context menu, register all items for a context menu by
+passing the {@link android.widget.ListView} or {@link android.widget.GridView} to {@link
+android.app.Activity#registerForContextMenu(View) registerForContextMenu()}.</p>
+</li>
+
+ <li>Implement the {@link
+android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} method
+in your {@link android.app.Activity} or {@link android.app.Fragment}.
+ <p>When the registered view receives a long-click event, the system calls your {@link
+android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()}
+method. This is where you define the menu items, usually by inflating a menu resource. For
+example:</p>
+<pre>
+@Override
+public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.context_menu, menu);
+}
+</pre>
+
+<p>{@link android.view.MenuInflater} allows you to inflate the context menu from a <a
+href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>. The callback method
+parameters include the {@link android.view.View}
+that the user selected and a {@link android.view.ContextMenu.ContextMenuInfo} object that provides
+additional information about the item selected. If your activity has several views that each provide
+a different context menu, you might use these parameters to determine which context menu to
+inflate.</p>
+</li>
+
+<li>Implement {@link android.app.Activity#onContextItemSelected(MenuItem)
+onContextItemSelected()}.
+ <p>When the user selects a menu item, the system calls this method so you can perform the
+appropriate action. For example:</p>
+
+<pre>
+@Override
+public boolean onContextItemSelected(MenuItem item) {
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+ switch (item.getItemId()) {
+ case R.id.edit:
+ editNote(info.id);
+ return true;
+ case R.id.delete:
+ deleteNote(info.id);
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+}
+</pre>
+
+<p>The {@link android.view.MenuItem#getItemId()} method queries the ID for
+the selected menu item, which you should assign to each menu item in XML using the {@code
+android:id} attribute, as shown in the section about <a href="#xml">Defining a Menu in
+XML</a>.</p>
+
+<p>When you successfully handle a menu item, return {@code true}. If you don't handle the menu item,
+you should pass the menu item to the superclass implementation. If your activity includes fragments,
+the activity receives this callback first. By calling the superclass when unhandled, the system
+passes the event to the respective callback method in each fragment, one at a time (in the order
+each fragment was added) until {@code true} or {@code false} is returned. (The default
+implementation for {@link android.app.Activity} and {@code android.app.Fragment} return {@code
+false}, so you should always call the superclass when unhandled.)</p>
+</li>
+</ol>
+
+
+<h3 id="CAB">Using the contextual action mode</h3>
+
+<p>The contextual action mode is a system implementation of {@link android.view.ActionMode} that
+focuses user interaction toward performing contextual actions. When a
+user enables this mode by selecting an item, a <em>contextual action bar</em> appears at the top of
+the screen to present actions the user can perform on the currently selected item(s). While this
+mode is enabled, the user can select multiple items (if you allow it), deselect items, and continue
+to navigate within the activity (as much as you're willing to allow). The action mode is disabled
+and the contextual action bar disappears when the user deselects all items, presses the BACK button,
+or selects the <em>Done</em> action on the left side of the bar.</p>
+
+<p class="note"><strong>Note:</strong> The contextual action bar is not necessarily
+associated with the <a href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>. They operate
+independently, even though the contextual action bar visually overtakes the action bar
+position.</p>
+
+<p>If you're developing for Android 3.0 (API level 11) or higher, you
+should usually use the contextual action mode to present contextual actions, instead of the <a
+href="#FloatingContextMenu">floating context menu</a>.</p>
+
+<p>For views that provide contextual actions, you should usually invoke the contextual action mode
+upon one of two events (or both):</p>
+<ul>
+ <li>The user performs a long-click on the view.</li>
+ <li>The user selects a checkbox or similar UI component within the view.</li>
+</ul>
+
+<p>How your application invokes the contextual action mode and defines the behavior for each
+action depends on your design. There are basically two designs:</p>
+<ul>
+ <li>For contextual actions on individual, arbitrary views.</li>
+ <li>For batch contextual actions on groups of items in a {@link
+android.widget.ListView} or {@link android.widget.GridView} (allowing the user to select multiple
+items and perform an action on them all).</li>
+</ul>
+
+<p>The following sections describe the setup required for each scenario.</p>
+
+
+<h4 id="CABforViews">Enabling the contextual action mode for individual views</h4>
+
+<p>If you want to invoke the contextual action mode only when the user selects specific
+views, you should:</p>
+<ol>
+ <li>Implement the {@link android.view.ActionMode.Callback} interface. In its callback methods, you
+can specify the actions for the contextual action bar, respond to click events on action items, and
+handle other lifecycle events for the action mode.</li>
+ <li>Call {@link android.app.Activity#startActionMode startActionMode()} when you want to show the
+bar (such as when the user long-clicks the view).</li>
+</ol>
+
+<p>For example:</p>
+
+<ol>
+ <li>Implement the {@link android.view.ActionMode.Callback ActionMode.Callback} interface:
+<pre>
+private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
+
+ // Called when the action mode is created; startActionMode() was called
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ // Inflate a menu resource providing context menu items
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.context_menu, menu);
+ return true;
+ }
+
+ // Called each time the action mode is shown. Always called after onCreateActionMode, but
+ // may be called multiple times if the mode is invalidated.
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false; // Return false if nothing is done
+ }
+
+ // Called when the user selects a contextual menu item
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_share:
+ shareCurrentItem();
+ mode.finish(); // Action picked, so close the CAB
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // Called when the user exits the action mode
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ mActionMode = null;
+ }
+};
+</pre>
+
+<p>Notice that these event callbacks are almost exactly the same as the callbacks for the <a
+href="#options-menu">options menu</a>, except each of these also pass the {@link
+android.view.ActionMode} object associated with the event. You can use {@link
+android.view.ActionMode} APIs to make various changes to the CAB, such as revise the title and
+subtitle with {@link android.view.ActionMode#setTitle setTitle()} and {@link
+android.view.ActionMode#setSubtitle setSubtitle()} (useful to indicate how many items are
+selected).</p>
+
+<p>Also notice that the above sample sets the {@code mActionMode} variable null when the
+action mode is destroyed. In the next step, you'll see how it's initialized and how saving
+the member variable in your activity or fragment can be useful.</p>
+</li>
+
+ <li>Call {@link android.app.Activity#startActionMode startActionMode()} to enable the contextual
+action mode when appropriate, such as in response to a long-click on a {@link
+android.view.View}:</p>
+
+<pre>
+someView.setOnLongClickListener(new View.OnLongClickListener() {
+ // Called when the user long-clicks on someView
+ public boolean onLongClick(View view) {
+ if (mActionMode != null) {
+ return false;
+ }
+
+ // Start the CAB using the ActionMode.Callback defined above
+ mActionMode = getActivity().startActionMode(mActionModeCallback);
+ view.setSelected(true);
+ return true;
+ }
+});
+</pre>
+
+<p>When you call {@link android.app.Activity#startActionMode startActionMode()}, the system returns
+the {@link android.view.ActionMode} created. By saving this in a member variable, you can
+make changes to the contextual action bar in response to other events. In the above sample, the
+{@link android.view.ActionMode} is used to ensure that the {@link android.view.ActionMode} instance
+is not recreated if it's already active, by checking whether the member is null before starting the
+action mode.</p>
+</li>
+</ol>
+
+
+
+<h4 id="CABforListView">Enabling batch contextual actions in a ListView or GridView</h4>
+
+<p>If you have a collection of items in a {@link android.widget.ListView} or {@link
+android.widget.GridView} (or another extension of {@link android.widget.AbsListView}) and want to
+allow users to perform batch actions, you should:</p>
+
+<ul>
+ <li>Implement the {@link android.widget.AbsListView.MultiChoiceModeListener} interface and set it
+for the view group with {@link android.widget.AbsListView#setMultiChoiceModeListener
+setMultiChoiceModeListener()}. In the listener's callback methods, you can specify the actions
+for the contextual action bar, respond to click events on action items, and handle other callbacks
+inherited from the {@link android.view.ActionMode.Callback} interface.</li>
+
+ <li>Call {@link android.widget.AbsListView#setChoiceMode setChoiceMode()} with the {@link
+android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL} argument.</li>
+</ul>
+
+<p>For example:</p>
+
+<pre>
+ListView listView = getListView();
+listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position,
+ long id, boolean checked) {
+ // Here you can do something when items are selected/de-selected,
+ // such as update the title in the CAB
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ // Respond to clicks on the actions in the CAB
+ switch (item.getItemId()) {
+ case R.id.menu_delete:
+ deleteSelectedItems();
+ mode.finish(); // Action picked, so close the CAB
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ // Inflate the menu for the CAB
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.context, menu);
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ // Here you can make any necessary updates to the activity when
+ // the CAB is removed. By default, selected items are deselected/unchecked.
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ // Here you can perform updates to the CAB due to
+ // an {@link android.view.ActionMode#invalidate} request
+ return false;
+ }
+});
+</pre>
+
+<p>That's it. Now when the user selects an item with a long-click, the system calls the {@link
+android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()}
+method and displays the contextual action bar with the specified actions. While the contextual
+action bar is visible, users can select additional items.</p>
+
+<p>In some cases in which the contextual actions provide common action items, you might
+want to add a checkbox or a similar UI element that allows users to select items, because they
+might not discover the long-click behavior. When a user selects the checkbox, you
+can invoke the contextual action mode by setting the respective list item to the checked
+state with {@link android.widget.AbsListView#setItemChecked setItemChecked()}.</p>
+
+
+
+
+<h2 id="PopupMenu">Creating a Popup Menu</h2>
+
+<div class="figure" style="width:220px">
+<img src="{@docRoot}images/ui/popupmenu.png" alt="" />
+<p><strong>Figure 4.</strong> A popup menu in the Gmail app, anchored to the overflow
+button at the top-right.</p>
+</div>
+
+<p>A {@link android.widget.PopupMenu} is a modal menu anchored to a {@link android.view.View}.
+It appears below the anchor view if there is room, or above the view otherwise. It's useful for:</p>
+<ul>
+ <li>Providing an overflow-style menu for actions that <em>relate to</em> specific content (such as
+Gmail's email headers, shown in figure 4).
+ <p class="note"><strong>Note:</strong> This is not the same as a context menu, which is
+generally for actions that <em>affect</em> selected content. For actions that affect selected
+content, use the <a href="#CAB">contextual action mode</a> or <a
+href="#FloatingContextMenu">floating context menu</a>.</p></li>
+ <li>Providing a second part of a command sentence (such as a button marked "Add"
+that produces a popup menu with different "Add" options).</li>
+ <li>Providing a drop-down similar to {@link android.widget.Spinner} that does not retain
+a persistent selection.</li>
+</ul>
+
+
+<p class="note"><strong>Note:</strong> {@link android.widget.PopupMenu} is available with API
+level 11 and higher.</p>
+
+<p>If you <a href="#xml">define your menu in XML</a>, here's how you can show the popup menu:</p>
+<ol>
+ <li>Instantate a {@link android.widget.PopupMenu} with its constructor, which takes the
+current application {@link android.content.Context} and the {@link android.view.View} to which the
+menu should be anchored.</li>
+ <li>Use {@link android.view.MenuInflater} to inflate your menu resource into the {@link
+android.view.Menu} object returned by {@link
+android.widget.PopupMenu#getMenu() PopupMenu.getMenu()}. On API level 14 and above, you can use
+{@link android.widget.PopupMenu#inflate PopupMenu.inflate()} instead.</li>
+ <li>Call {@link android.widget.PopupMenu#show() PopupMenu.show()}.</li>
+</ol>
+
+<p>For example, here's a button with the {@link android.R.attr#onClick android:onClick} attribute
+that shows a popup menu:</p>
+
+<pre>
+<ImageButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_overflow_holo_dark"
+ android:contentDescription="@string/descr_overflow_button"
+ android:onClick="showPopup" />
+</pre>
+
+<p>The activity can then show the popup menu like this:</p>
+
+<pre>
+public void showPopup(View v) {
+ PopupMenu popup = new PopupMenu(this, v);
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.actions, popup.getMenu());
+ popup.show();
+}
+</pre>
+
+<p>In API level 14 and higher, you can combine the two lines that inflate the menu with {@link
+android.widget.PopupMenu#inflate PopupMenu.inflate()}.</p>
+
+<p>The menu is dismissed when the user selects an item or touches outside the menu
+area. You can listen for the dismiss event using {@link
+android.widget.PopupMenu.OnDismissListener}.</p>
+
+<h3 id="PopupEvents">Handling click events</h3>
+
+<p>To perform an
+action when the user selects a menu item, you must implement the {@link
+android.widget.PopupMenu.OnMenuItemClickListener} interface and register it with your {@link
+android.widget.PopupMenu} by calling {@link android.widget.PopupMenu#setOnMenuItemClickListener
+setOnMenuItemclickListener()}. When the user selects an item, the system calls the {@link
+android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} callback in
+your interface.</p>
+
+<p>For example:</p>
+
+<pre>
+public void showMenu(View v) {
+ PopupMenu popup = new PopupMenu(this, v);
+
+ // This activity implements OnMenuItemClickListener
+ popup.setOnMenuItemClickListener(this);
+ popup.inflate(R.menu.actions);
+ popup.show();
+}
+
+@Override
+public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.archive:
+ archive(item);
+ return true;
+ case R.id.delete:
+ delete(item);
+ return true;
+ default:
+ return false;
+ }
+}
+</pre>
+
+
+<h2 id="groups">Creating Menu Groups</h2>
<p>A menu group is a collection of menu items that share certain traits. With a group, you
can:</p>
@@ -473,38 +842,41 @@
<pre>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/item1"
- android:icon="@drawable/item1"
- android:title="@string/item1" />
+ <item android:id="@+id/menu_save"
+ android:icon="@drawable/menu_save"
+ android:title="@string/menu_save" />
<!-- menu group -->
- <group android:id="@+id/group1">
- <item android:id="@+id/groupItem1"
- android:title="@string/groupItem1" />
- <item android:id="@+id/groupItem2"
- android:title="@string/groupItem2" />
+ <group android:id="@+id/group_delete">
+ <item android:id="@+id/menu_archive"
+ android:title="@string/menu_archive" />
+ <item android:id="@+id/menu_delete"
+ android:title="@string/menu_delete" />
</group>
</menu>
</pre>
-<p>The items that are in the group appear the same as the first item that is not in a
-group—all three items in the menu are siblings. However, you can modify the traits of the two
-items in the group by referencing the group ID and using the methods listed above.</p>
+<p>The items that are in the group appear at the same level as the first item—all three items
+in the menu are siblings. However, you can modify the traits of the two
+items in the group by referencing the group ID and using the methods listed above. The system
+will also never separate grouped items. For example, if you declare {@code
+android:showAsAction="ifRoom"} for each item, they will either both appear in the action
+bar or both appear in the action overflow.</p>
-<h3 id="checkable">Checkable menu items</h3>
+<h3 id="checkable">Using checkable menu items</h3>
<div class="figure" style="width:200px">
<img src="{@docRoot}images/radio_buttons.png" height="333" alt="" />
- <p class="img-caption"><strong>Figure 3.</strong> Screenshot of a submenu with checkable
+ <p class="img-caption"><strong>Figure 5.</strong> Screenshot of a submenu with checkable
items.</p>
</div>
<p>A menu can be useful as an interface for turning options on and off, using a checkbox for
stand-alone options, or radio buttons for groups of
-mutually exclusive options. Figure 2 shows a submenu with items that are checkable with radio
+mutually exclusive options. Figure 5 shows a submenu with items that are checkable with radio
buttons.</p>
-<p class="note"><strong>Note:</strong> Menu items in the Icon Menu (from the Options Menu) cannot
+<p class="note"><strong>Note:</strong> Menu items in the Icon Menu (from the options menu) cannot
display a checkbox or radio button. If you choose to make items in the Icon Menu checkable,
you must manually indicate the checked state by swapping the icon and/or text
each time the state changes.</p>
@@ -550,15 +922,15 @@
<pre>
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.vibrate:
- case R.id.dont_vibrate:
- if (item.isChecked()) item.setChecked(false);
- else item.setChecked(true);
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
+ switch (item.getItemId()) {
+ case R.id.vibrate:
+ case R.id.dont_vibrate:
+ if (item.isChecked()) item.setChecked(false);
+ else item.setChecked(true);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
}
</pre>
@@ -575,30 +947,8 @@
href="{@docRoot}guide/topics/data/data-storage.html#pref">Shared Preferences</a>.</p>
-<h3 id="shortcuts">Shortcut keys</h3>
-<p>To facilitate quick access to items in the Options Menu when the user's device has a hardware
-keyboard, you can add quick-access shortcut keys using letters and/or numbers, with the
-{@code android:alphabeticShortcut} and {@code android:numericShortcut} attributes in the {@code
-<item>} element. You can also use the methods {@link
-android.view.MenuItem#setAlphabeticShortcut(char)} and {@link
-android.view.MenuItem#setNumericShortcut(char)}. Shortcut keys are <em>not</em>
-case sensitive.</p>
-
-<p>For example, if you apply the "s" character as an alphabetic shortcut to a "save" menu item, then
-when the menu is open (or while the user holds the MENU button) and the user presses the "s" key,
-the "save" menu item is selected.</p>
-
-<p>This shortcut key is displayed as a tip in the menu item, below the menu item name
-(except for items in the Icon Menu, which are displayed only if the user holds the MENU
-button).</p>
-
-<p class="note"><strong>Note:</strong> Shortcut keys for menu items only work on devices with a
-hardware keyboard. Shortcuts cannot be added to items in a Context Menu.</p>
-
-
-
-<h3 id="intents">Dynamically adding menu intents</h3>
+<h2 id="intents">Adding Menu Items Based on an Intent</h2>
<p>Sometimes you'll want a menu item to launch an activity using an {@link android.content.Intent}
(whether it's an activity in your application or another application). When you know the intent you
@@ -671,7 +1021,7 @@
argument.</p>
-<h4>Allowing your activity to be added to other menus</h4>
+<h3 id="AllowingToAdd">Allowing your activity to be added to other menus</h3>
<p>You can also offer the services of your activity to other applications, so your
application can be included in the menu of others (reverse the roles described above).</p>
@@ -681,7 +1031,7 @@
and/or {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} values for the intent filter
category. For example:</p>
<pre>
-<intent-filter label="Resize Image">
+<intent-filter label="@string/resize_image">
...
<category android:name="android.intent.category.ALTERNATIVE" />
<category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
diff --git a/docs/html/images/ui/menu-context.png b/docs/html/images/ui/menu-context.png
new file mode 100644
index 0000000..f6975fb
--- /dev/null
+++ b/docs/html/images/ui/menu-context.png
Binary files differ
diff --git a/docs/html/images/ui/popupmenu.png b/docs/html/images/ui/popupmenu.png
new file mode 100644
index 0000000..5c99821
--- /dev/null
+++ b/docs/html/images/ui/popupmenu.png
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index fdf860a..b9d6758 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -1,5 +1,5 @@
home=true
-metaDescription=The official site for Android developers. Provides the Android SDK and documentation for app developers and designers.
+page.metaDescription=The official site for Android developers. Provides the Android SDK and documentation for app developers and designers.
@jd:body
diff --git a/docs/html/resources/dashboard/opengl.jd b/docs/html/resources/dashboard/opengl.jd
index 357c1ea..d55ab2b 100644
--- a/docs/html/resources/dashboard/opengl.jd
+++ b/docs/html/resources/dashboard/opengl.jd
@@ -57,7 +57,7 @@
<div class="dashboard-panel">
<img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A9.5,90.5" />
+src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1%20only|GL%202.0%20%26%201.1&chd=t%3A10.7,89.3" />
<table>
<tr>
@@ -65,15 +65,15 @@
<th scope="col">Distribution</th>
</tr>
<tr>
-<td>1.1</th>
-<td>9.5%</td>
+<td>1.1 only</th>
+<td>10.7%</td>
</tr>
<tr>
-<td>2.0</th>
-<td>90.5%</td>
+<td>2.0 & 1.1</th>
+<td>89.3%</td>
</tr>
</table>
-<p><em>Data collected during a 7-day period ending on January 3, 2012</em></p>
+<p><em>Data collected during a 7-day period ending on February 1, 2012</em></p>
</div>
diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd
index 2618a04..4ea52af 100644
--- a/docs/html/resources/dashboard/platform-versions.jd
+++ b/docs/html/resources/dashboard/platform-versions.jd
@@ -52,7 +52,7 @@
<div class="dashboard-panel">
<img alt="" height="250" width="470"
-src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.6,1.1,8.5,30.4,0.6,54.9,0.1,1.5,1.7,0.3,0.3&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2|Android%204.0|Android%204.0.3&chco=c4df9b,6fad0c" />
+src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.6,1.0,7.6,27.8,0.5,58.1,0.1,1.4,1.9,0.3,0.7&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2|Android%204.0|Android%204.0.3&chco=c4df9b,6fad0c" />
<table>
<tr>
@@ -62,24 +62,24 @@
<th>Distribution</th>
</tr>
<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td> <td>3</td><td>0.6%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td> <td>4</td><td>1.1%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td> <td>7</td><td>8.5%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td> <td>8</td><td>30.4%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td> <td>4</td><td>1.0%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td> <td>7</td><td>7.6%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td> <td>8</td><td>27.8%</td></tr>
<tr><td><a href="{@docRoot}sdk/android-2.3.html">Android 2.3 -<br/>
- Android 2.3.2</a></td><td rowspan="2">Gingerbread</td> <td>9</td><td>0.6%</td></tr>
+ Android 2.3.2</a></td><td rowspan="2">Gingerbread</td> <td>9</td><td>0.5%</td></tr>
<tr><td><a href="{@docRoot}sdk/android-2.3.3.html">Android 2.3.3 -<br/>
- Android 2.3.7</a></td><!-- Gingerbread --> <td>10</td><td>54.9%</td></tr>
+ Android 2.3.7</a></td><!-- Gingerbread --> <td>10</td><td>58.1%</td></tr>
<tr><td><a href="{@docRoot}sdk/android-3.0.html">Android 3.0</a></td>
<td rowspan="3">Honeycomb</td> <td>11</td><td>0.1%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>1.5%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>1.7%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>1.4%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>1.9%</td></tr>
<tr><td><a href="{@docRoot}sdk/android-4.0.html">Android 4.0 -<br/>
Android 4.0.2</a></td>
<td rowspan="2">Ice Cream Sandwich</td><td>14</td><td>0.3%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-4.0.3.html">Android 4.0.3</a></td><!-- ICS --><td>15</td><td>0.3%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-4.0.3.html">Android 4.0.3</a></td><!-- ICS --><td>15</td><td>0.7%</td></tr>
</table>
-<p><em>Data collected during a 14-day period ending on January 3, 2012</em></p>
+<p><em>Data collected during a 14-day period ending on February 1, 2012</em></p>
<!--
<p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p>
-->
@@ -108,9 +108,9 @@
<div class="dashboard-panel">
<img alt="" height="250" width="660" style="padding:5px;background:#fff"
-src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C07/01%7C07/15%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C11/15%7C12/01%7C12/15%7C01/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2012%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.2,99.0,98.8,98.7,98.5,98.5,98.2,98.1,98.0,99.9,99.9,99.7,99.2|97.7,97.6,97.5,97.5,97.5,97.5,97.1,97.1,97.0,99.1,99.1,99.0,98.6|95.5,95.5,95.5,95.6,95.7,95.8,95.6,95.9,95.7,97.7,97.8,97.8,97.5|77.6,79.0,80.2,81.1,82.4,83.3,83.8,84.9,85.1,87.5,88.2,88.6,89.0|17.8,20.6,24.3,27.5,31.2,34.7,38.3,41.3,44.0,48.9,52.9,55.7,58.5|16.8,20.0,23.7,26.9,30.6,34.1,37.8,40.8,43.5,48.4,52.4,55.2,57.9|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,2.0,2.3,2.6,3.2|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.2,1.3,1.7&chm=b,c3df9b,0,1,0|b,b8dc82,1,2,0|tAndroid 2.1,608920,2,0,15,,t::-5|b,addb67,2,3,0|tAndroid 2.2,517617,3,0,15,,t::-5|b,a3db4b,3,4,0|b,98dc2e,4,5,0|tAndroid 2.3.3,334d0a,5,0,15,,t::-5|b,8cd41b,5,6,0|b,7ec113,6,7,0|B,6fad0c,7,8,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android 2.2|Android 2.3|Android 2.3.3|Android 3.1|Android 3.2&chco=add274,a2d15a,97d13e,8bcb28,7dba1e,6ea715,5f920e,507d08" />
+src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C11/15%7C12/01%7C12/15%7C01/01%7C01/15%7C02/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2012%7C%7C2012%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:98.2,98.1,97.9,97.9,97.7,97.6,97.5,99.4,99.4,99.2,98.6,98.4,98.5|96.9,96.9,96.9,96.9,96.6,96.6,96.5,98.6,98.6,98.5,98.0,97.8,97.9|94.9,95.0,95.1,95.2,95.1,95.4,95.2,97.2,97.3,97.3,96.9,96.8,96.9|79.6,80.5,81.8,82.7,83.3,84.4,84.6,87.0,87.7,88.1,88.4,88.8,89.2|23.7,26.9,30.6,34.1,37.8,40.8,43.5,48.4,52.4,55.2,57.9,59.7,61.3|0.0,0.0,0.0,0.0,0.0,0.0,0.0,2.0,2.3,2.6,3.2,3.2,3.3|0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.2,1.3,1.7,1.8,1.9&chm=b,c3df9b,0,1,0|b,b6dc7d,1,2,0|tAndroid%202.1,5b831d,2,0,15,,t::-5|b,aadb5e,2,3,0|tAndroid%202.2,496c13,3,0,15,,t::-5|b,9ddb3d,3,4,0|tAndroid%202.3.3,38540b,4,0,15,,t::-5|b,91da1e,4,5,0|b,80c414,5,6,0|B,6fad0c,6,7,0&chg=7,25&chdl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3.3|Android%203.1|Android%203.2&chco=add274,a0d155,94d134,84c323,73ad18,62960f,507d08" />
-<p><em>Last historical dataset collected during a 14-day period ending on January 3, 2012</em></p>
+<p><em>Last historical dataset collected during a 14-day period ending on February 1, 2012</em></p>
</div><!-- end dashboard-panel -->
diff --git a/docs/html/resources/dashboard/screens.jd b/docs/html/resources/dashboard/screens.jd
index 79d59d9..ae5cdc7 100644
--- a/docs/html/resources/dashboard/screens.jd
+++ b/docs/html/resources/dashboard/screens.jd
@@ -60,7 +60,7 @@
<div class="dashboard-panel">
<img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A3.1,0.1,3.1,71.0,1.0,17.5,2.9,1.3" />
+src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Normal%20/%20xhdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A4.8,0.2,2.9,67.1,0.7,18.4,1.8,2.5,1.6" />
<table>
<tr>
@@ -71,31 +71,31 @@
<th scope="col">xhdpi</th>
</tr>
<tr><th scope="row">small</th>
-<td>1.3%</td> <!-- small/ldpi -->
+<td>1.6%</td> <!-- small/ldpi -->
<td></td> <!-- small/mdpi -->
-<td>2.9%</td> <!-- small/hdpi -->
+<td>2.5%</td> <!-- small/hdpi -->
<td></td> <!-- small/xhdpi -->
</tr>
<tr><th scope="row">normal</th>
-<td>1.0%</td> <!-- normal/ldpi -->
-<td>17.5%</td> <!-- normal/mdpi -->
-<td>71%</td> <!-- normal/hdpi -->
-<td></td> <!-- normal/xhdpi -->
+<td>0.7%</td> <!-- normal/ldpi -->
+<td>18.4%</td> <!-- normal/mdpi -->
+<td>67.1%</td> <!-- normal/hdpi -->
+<td>1.8%</td> <!-- normal/xhdpi -->
</tr>
<tr><th scope="row">large</th>
-<td>0.1%</td> <!-- large/ldpi -->
-<td>3.1%</td> <!-- large/mdpi -->
+<td>0.2%</td> <!-- large/ldpi -->
+<td>2.9%</td> <!-- large/mdpi -->
<td></td> <!-- large/hdpi -->
<td></td> <!-- large/xhdpi -->
</tr>
<tr><th scope="row">xlarge</th>
<td></td> <!-- xlarge/ldpi -->
-<td>3.1%</td> <!-- xlarge/mdpi -->
+<td>4.8%</td> <!-- xlarge/mdpi -->
<td></td> <!-- xlarge/hdpi -->
<td></td> <!-- xlarge/xhdpi -->
</tr>
</table>
-<p><em>Data collected during a 7-day period ending on December 1, 2011</em></p>
+<p><em>Data collected during a 7-day period ending on February 1, 2012</em></p>
</div>
diff --git a/docs/html/resources/tutorials/hello-world.jd b/docs/html/resources/tutorials/hello-world.jd
index 9afab6a..cc8cb3e 100644
--- a/docs/html/resources/tutorials/hello-world.jd
+++ b/docs/html/resources/tutorials/hello-world.jd
@@ -24,7 +24,7 @@
management to greatly speed up your development cycles.</p>
<p>This tutorial assumes that you're using Eclipse. If you're using the command line, see
-<a href="{@docRoot}/guide/developing/building/building-cmdline.html">Building and Running from the
+<a href="{@docRoot}guide/developing/building/building-cmdline.html">Building and Running from the
Command Line</a>. You can then return to this tutorial and ignore anything about Eclipse.</p>
<p>Before you start, you should already have the SDK installed, and if you're
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 82ed199..96a71e3 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -28,8 +28,8 @@
the the colors are distributed evenly along the gradient line.
@param tile The Shader tiling mode
*/
- public LinearGradient(float x0, float y0, float x1, float y1,
- int colors[], float positions[], TileMode tile) {
+ public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],
+ TileMode tile) {
if (colors.length < 2) {
throw new IllegalArgumentException("needs >= 2 number of colors");
}
@@ -50,8 +50,8 @@
@param color1 The color at the end of the gradient line.
@param tile The Shader tiling mode
*/
- public LinearGradient(float x0, float y0, float x1, float y1,
- int color0, int color1, TileMode tile) {
+ public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
+ TileMode tile) {
native_instance = nativeCreate2(x0, y0, x1, y1, color0, color1, tile.nativeInt);
native_shader = nativePostCreate2(native_instance, x0, y0, x1, y1, color0, color1,
tile.nativeInt);
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 7830224..0ada1fb 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -76,13 +76,21 @@
}
@Override
- public boolean equals(Object obj) {
- Rect r = (Rect) obj;
- if (r != null) {
- return left == r.left && top == r.top && right == r.right
- && bottom == r.bottom;
- }
- return false;
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Rect r = (Rect) o;
+ return left == r.left && top == r.top && right == r.right && bottom == r.bottom;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = left;
+ result = 31 * result + top;
+ result = 31 * result + right;
+ result = 31 * result + bottom;
+ return result;
}
@Override
diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java
index 00e9609..293dfcc 100644
--- a/graphics/java/android/graphics/RectF.java
+++ b/graphics/java/android/graphics/RectF.java
@@ -79,6 +79,24 @@
bottom = r.bottom;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Rect r = (Rect) o;
+ return left == r.left && top == r.top && right == r.right && bottom == r.bottom;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (left != +0.0f ? Float.floatToIntBits(left) : 0);
+ result = 31 * result + (top != +0.0f ? Float.floatToIntBits(top) : 0);
+ result = 31 * result + (right != +0.0f ? Float.floatToIntBits(right) : 0);
+ result = 31 * result + (bottom != +0.0f ? Float.floatToIntBits(bottom) : 0);
+ return result;
+ }
+
public String toString() {
return "RectF(" + left + ", " + top + ", "
+ right + ", " + bottom + ")";
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 50964d5..5b50beb 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -173,9 +173,20 @@
}
/**
- * Specify radii for each of the 4 corners. For each corner, the array
- * contains 2 values, [X_radius, Y_radius]. The corners are ordered
- * top-left, top-right, bottom-right, bottom-left
+ * <p>Specify radii for each of the 4 corners. For each corner, the array
+ * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are ordered
+ * top-left, top-right, bottom-right, bottom-left. This property
+ * is honored only when the shape is of type {@link #RECTANGLE}.</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param radii 4 pairs of X and Y radius for each corner, specified in pixels.
+ * The length of this array must be >= 8
+ *
+ * @see #mutate()
+ * @see #setCornerRadii(float[])
+ * @see #setShape(int)
*/
public void setCornerRadii(float[] radii) {
mGradientState.setCornerRadii(radii);
@@ -184,23 +195,57 @@
}
/**
- * Specify radius for the corners of the gradient. If this is > 0, then the
- * drawable is drawn in a round-rectangle, rather than a rectangle.
+ * <p>Specify radius for the corners of the gradient. If this is > 0, then the
+ * drawable is drawn in a round-rectangle, rather than a rectangle. This property
+ * is honored only when the shape is of type {@link #RECTANGLE}.</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param radius The radius in pixels of the corners of the rectangle shape
+ *
+ * @see #mutate()
+ * @see #setCornerRadii(float[])
+ * @see #setShape(int)
*/
public void setCornerRadius(float radius) {
mGradientState.setCornerRadius(radius);
mPathIsDirty = true;
invalidateSelf();
}
-
+
/**
- * Set the stroke width and color for the drawable. If width is zero,
- * then no stroke is drawn.
+ * <p>Set the stroke width and color for the drawable. If width is zero,
+ * then no stroke is drawn.</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param width The width in pixels of the stroke
+ * @param color The color of the stroke
+ *
+ * @see #mutate()
+ * @see #setStroke(int, int, float, float)
*/
public void setStroke(int width, int color) {
setStroke(width, color, 0, 0);
}
-
+
+ /**
+ * <p>Set the stroke width and color for the drawable. If width is zero,
+ * then no stroke is drawn. This method can also be used to dash the stroke.</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param width The width in pixels of the stroke
+ * @param color The color of the stroke
+ * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes
+ * @param dashGap The gap in pixels between dashes
+ *
+ * @see #mutate()
+ * @see #setStroke(int, int)
+ */
public void setStroke(int width, int color, float dashWidth, float dashGap) {
mGradientState.setStroke(width, color, dashWidth, dashGap);
@@ -218,13 +263,37 @@
mStrokePaint.setPathEffect(e);
invalidateSelf();
}
-
+
+
+ /**
+ * <p>Sets the size of the shape drawn by this drawable.</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param width The width of the shape used by this drawable
+ * @param height The height of the shape used by this drawable
+ *
+ * @see #mutate()
+ * @see #setGradientType(int)
+ */
public void setSize(int width, int height) {
mGradientState.setSize(width, height);
mPathIsDirty = true;
invalidateSelf();
}
-
+
+ /**
+ * <p>Sets the type of shape used to draw the gradient.</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param shape The desired shape for this drawable: {@link #LINE},
+ * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING}
+ *
+ * @see #mutate()
+ */
public void setShape(int shape) {
mRingPath = null;
mPathIsDirty = true;
@@ -232,24 +301,73 @@
invalidateSelf();
}
+ /**
+ * <p>Sets the type of gradient used by this drawable..</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT},
+ * {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}
+ *
+ * @see #mutate()
+ */
public void setGradientType(int gradient) {
mGradientState.setGradientType(gradient);
mRectIsDirty = true;
invalidateSelf();
}
+ /**
+ * <p>Sets the center location of the gradient. The radius is honored only when
+ * the gradient type is set to {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}.</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param x The x coordinate of the gradient's center
+ * @param y The y coordinate of the gradient's center
+ *
+ * @see #mutate()
+ * @see #setGradientType(int)
+ */
public void setGradientCenter(float x, float y) {
mGradientState.setGradientCenter(x, y);
mRectIsDirty = true;
invalidateSelf();
}
+ /**
+ * <p>Sets the radius of the gradient. The radius is honored only when the
+ * gradient type is set to {@link #RADIAL_GRADIENT}.</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param gradientRadius The radius of the gradient in pixels
+ *
+ * @see #mutate()
+ * @see #setGradientType(int)
+ */
public void setGradientRadius(float gradientRadius) {
mGradientState.setGradientRadius(gradientRadius);
mRectIsDirty = true;
invalidateSelf();
}
+ /**
+ * <p>Sets whether or not this drawable will honor its <code>level</code>
+ * property.</p>
+ * <p><strong>Note</strong>: changing this property will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing this property.</p>
+ *
+ * @param useLevel True if this drawable should honor its level, false otherwise
+ *
+ * @see #mutate()
+ * @see #setLevel(int)
+ * @see #getLevel()
+ */
public void setUseLevel(boolean useLevel) {
mGradientState.mUseLevel = useLevel;
mRectIsDirty = true;
@@ -261,6 +379,47 @@
return alpha * scale >> 8;
}
+ /**
+ * Returns the orientation of the gradient defined in this drawable.
+ */
+ public Orientation getOrientation() {
+ return mGradientState.mOrientation;
+ }
+
+ /**
+ * <p>Changes the orientation of the gradient defined in this drawable.</p>
+ * <p><strong>Note</strong>: changing orientation will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing the orientation.</p>
+ *
+ * @param orientation The desired orientation (angle) of the gradient
+ *
+ * @see #mutate()
+ */
+ public void setOrientation(Orientation orientation) {
+ mGradientState.mOrientation = orientation;
+ mRectIsDirty = true;
+ invalidateSelf();
+ }
+
+ /**
+ * <p>Sets the colors used to draw the gradient. Each color is specified as an
+ * ARGB integer and the array must contain at least 2 colors.</p>
+ * <p><strong>Note</strong>: changing orientation will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing the orientation.</p>
+ *
+ * @param colors 2 or more ARGB colors
+ *
+ * @see #mutate()
+ * @see #setColor(int)
+ */
+ public void setColors(int[] colors) {
+ mGradientState.setColors(colors);
+ mRectIsDirty = true;
+ invalidateSelf();
+ }
+
@Override
public void draw(Canvas canvas) {
if (!ensureValidRect()) {
@@ -442,6 +601,17 @@
return ringPath;
}
+ /**
+ * <p>Changes this drawbale to use a single color instead of a gradient.</p>
+ * <p><strong>Note</strong>: changing orientation will affect all instances
+ * of a drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing the orientation.</p>
+ *
+ * @param argb The color used to fill the shape
+ *
+ * @see #mutate()
+ * @see #setColors(int[])
+ */
public void setColor(int argb) {
mGradientState.setSolidColor(argb);
mFillPaint.setColor(argb);
@@ -450,10 +620,9 @@
@Override
public int getChangingConfigurations() {
- return super.getChangingConfigurations()
- | mGradientState.mChangingConfigurations;
+ return super.getChangingConfigurations() | mGradientState.mChangingConfigurations;
}
-
+
@Override
public void setAlpha(int alpha) {
if (alpha != mAlpha) {
@@ -480,7 +649,6 @@
@Override
public int getOpacity() {
- // XXX need to figure out the actual opacity...
return PixelFormat.TRANSLUCENT;
}
@@ -911,11 +1079,6 @@
private float mGradientRadius = 0.5f;
private boolean mUseLevel;
private boolean mUseLevelForShape;
-
-
- GradientState() {
- mOrientation = Orientation.TOP_BOTTOM;
- }
GradientState(Orientation orientation, int[] colors) {
mOrientation = orientation;
@@ -987,6 +1150,11 @@
mCenterY = y;
}
+ public void setColors(int[] colors) {
+ mHasSolidColor = false;
+ mColors = colors;
+ }
+
public void setSolidColor(int argb) {
mHasSolidColor = true;
mSolidColor = argb;
@@ -1055,4 +1223,3 @@
}
}
}
-
diff --git a/include/gui/DisplayEventReceiver.h b/include/gui/DisplayEventReceiver.h
index dccc164..a281377 100644
--- a/include/gui/DisplayEventReceiver.h
+++ b/include/gui/DisplayEventReceiver.h
@@ -63,7 +63,9 @@
public:
/*
* DisplayEventReceiver creates and registers an event connection with
- * SurfaceFlinger. Events start being delivered immediately.
+ * SurfaceFlinger. VSync events are disabled by default. Call setVSyncRate
+ * or requestNextVsync to receive them.
+ * Other events start being delivered immediately.
*/
DisplayEventReceiver();
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index 6b12c14..74a1e62 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -248,7 +248,7 @@
static sp<IAudioPolicyService> gAudioPolicyService;
// mapping between stream types and outputs
- static DefaultKeyedVector<int, audio_io_handle_t> gStreamOutputMap;
+ static DefaultKeyedVector<audio_stream_type_t, audio_io_handle_t> gStreamOutputMap;
// list of output descriptors containing cached parameters
// (sampling rate, framecount, channel count...)
static DefaultKeyedVector<audio_io_handle_t, OutputDescriptor *> gOutputs;
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index c4cc947..a295e9a 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -42,10 +42,10 @@
typedef void *buffer_id;
typedef void *node_id;
- // Given the calling process' pid, returns true iff
+ // Given a node_id and the calling process' pid, returns true iff
// the implementation of the OMX interface lives in the same
// process.
- virtual bool livesLocally(pid_t pid) = 0;
+ virtual bool livesLocally(node_id node, pid_t pid) = 0;
struct ComponentInfo {
String8 mName;
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index b741ed6..c496da6 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -444,23 +444,31 @@
void uninit();
+ // Return string entry as UTF16; if the pool is UTF8, the string will
+ // be converted before returning.
inline const char16_t* stringAt(const ResStringPool_ref& ref, size_t* outLen) const {
return stringAt(ref.index, outLen);
}
const char16_t* stringAt(size_t idx, size_t* outLen) const;
+ // Note: returns null if the string pool is not UTF8.
const char* string8At(size_t idx, size_t* outLen) const;
+ // Return string whether the pool is UTF8 or UTF16. Does not allow you
+ // to distinguish null.
+ const String8 string8ObjectAt(size_t idx) const;
+
const ResStringPool_span* styleAt(const ResStringPool_ref& ref) const;
const ResStringPool_span* styleAt(size_t idx) const;
ssize_t indexOfString(const char16_t* str, size_t strLen) const;
size_t size() const;
+ size_t styleCount() const;
+ size_t bytes() const;
-#ifndef HAVE_ANDROID_OS
+ bool isSorted() const;
bool isUTF8() const;
-#endif
private:
status_t mError;
@@ -746,7 +754,9 @@
/**
* Header for a resource table. Its data contains a series of
* additional chunks:
- * * A ResStringPool_header containing all table values.
+ * * A ResStringPool_header containing all table values. This string pool
+ * contains all of the string values in the entire resource table (not
+ * the names of entries or type identifiers however).
* * One or more ResTable_package chunks.
*
* Specific entries within a resource table can be uniquely identified
@@ -984,68 +994,15 @@
uint32_t screenSizeDp;
};
- inline void copyFromDeviceNoSwap(const ResTable_config& o) {
- const size_t size = dtohl(o.size);
- if (size >= sizeof(ResTable_config)) {
- *this = o;
- } else {
- memcpy(this, &o, size);
- memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
- }
- }
+ void copyFromDeviceNoSwap(const ResTable_config& o);
- inline void copyFromDtoH(const ResTable_config& o) {
- copyFromDeviceNoSwap(o);
- size = sizeof(ResTable_config);
- mcc = dtohs(mcc);
- mnc = dtohs(mnc);
- density = dtohs(density);
- screenWidth = dtohs(screenWidth);
- screenHeight = dtohs(screenHeight);
- sdkVersion = dtohs(sdkVersion);
- minorVersion = dtohs(minorVersion);
- smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
- screenWidthDp = dtohs(screenWidthDp);
- screenHeightDp = dtohs(screenHeightDp);
- }
+ void copyFromDtoH(const ResTable_config& o);
- inline void swapHtoD() {
- size = htodl(size);
- mcc = htods(mcc);
- mnc = htods(mnc);
- density = htods(density);
- screenWidth = htods(screenWidth);
- screenHeight = htods(screenHeight);
- sdkVersion = htods(sdkVersion);
- minorVersion = htods(minorVersion);
- smallestScreenWidthDp = htods(smallestScreenWidthDp);
- screenWidthDp = htods(screenWidthDp);
- screenHeightDp = htods(screenHeightDp);
- }
-
- inline int compare(const ResTable_config& o) const {
- int32_t diff = (int32_t)(imsi - o.imsi);
- if (diff != 0) return diff;
- diff = (int32_t)(locale - o.locale);
- if (diff != 0) return diff;
- diff = (int32_t)(screenType - o.screenType);
- if (diff != 0) return diff;
- diff = (int32_t)(input - o.input);
- if (diff != 0) return diff;
- diff = (int32_t)(screenSize - o.screenSize);
- if (diff != 0) return diff;
- diff = (int32_t)(version - o.version);
- if (diff != 0) return diff;
- diff = (int32_t)(screenLayout - o.screenLayout);
- if (diff != 0) return diff;
- diff = (int32_t)(uiMode - o.uiMode);
- if (diff != 0) return diff;
- diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp);
- if (diff != 0) return diff;
- diff = (int32_t)(screenSizeDp - o.screenSizeDp);
- return (int)diff;
- }
-
+ void swapHtoD();
+
+ int compare(const ResTable_config& o) const;
+ int compareLogical(const ResTable_config& o) const;
+
// Flags indicating a set of config values. These flag constants must
// match the corresponding ones in android.content.pm.ActivityInfo and
// attrs_manifest.xml.
@@ -1068,158 +1025,10 @@
// Compare two configuration, returning CONFIG_* flags set for each value
// that is different.
- inline int diff(const ResTable_config& o) const {
- int diffs = 0;
- if (mcc != o.mcc) diffs |= CONFIG_MCC;
- if (mnc != o.mnc) diffs |= CONFIG_MNC;
- if (locale != o.locale) diffs |= CONFIG_LOCALE;
- if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION;
- if (density != o.density) diffs |= CONFIG_DENSITY;
- if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN;
- if (((inputFlags^o.inputFlags)&(MASK_KEYSHIDDEN|MASK_NAVHIDDEN)) != 0)
- diffs |= CONFIG_KEYBOARD_HIDDEN;
- if (keyboard != o.keyboard) diffs |= CONFIG_KEYBOARD;
- if (navigation != o.navigation) diffs |= CONFIG_NAVIGATION;
- if (screenSize != o.screenSize) diffs |= CONFIG_SCREEN_SIZE;
- if (version != o.version) diffs |= CONFIG_VERSION;
- if (screenLayout != o.screenLayout) diffs |= CONFIG_SCREEN_LAYOUT;
- if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
- if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
- if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
- return diffs;
- }
+ int diff(const ResTable_config& o) const;
// Return true if 'this' is more specific than 'o'.
- inline bool
- isMoreSpecificThan(const ResTable_config& o) const {
- // The order of the following tests defines the importance of one
- // configuration parameter over another. Those tests first are more
- // important, trumping any values in those following them.
- if (imsi || o.imsi) {
- if (mcc != o.mcc) {
- if (!mcc) return false;
- if (!o.mcc) return true;
- }
-
- if (mnc != o.mnc) {
- if (!mnc) return false;
- if (!o.mnc) return true;
- }
- }
-
- if (locale || o.locale) {
- if (language[0] != o.language[0]) {
- if (!language[0]) return false;
- if (!o.language[0]) return true;
- }
-
- if (country[0] != o.country[0]) {
- if (!country[0]) return false;
- if (!o.country[0]) return true;
- }
- }
-
- if (smallestScreenWidthDp || o.smallestScreenWidthDp) {
- if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
- if (!smallestScreenWidthDp) return false;
- if (!o.smallestScreenWidthDp) return true;
- }
- }
-
- if (screenSizeDp || o.screenSizeDp) {
- if (screenWidthDp != o.screenWidthDp) {
- if (!screenWidthDp) return false;
- if (!o.screenWidthDp) return true;
- }
-
- if (screenHeightDp != o.screenHeightDp) {
- if (!screenHeightDp) return false;
- if (!o.screenHeightDp) return true;
- }
- }
-
- if (screenLayout || o.screenLayout) {
- if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0) {
- if (!(screenLayout & MASK_SCREENSIZE)) return false;
- if (!(o.screenLayout & MASK_SCREENSIZE)) return true;
- }
- if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0) {
- if (!(screenLayout & MASK_SCREENLONG)) return false;
- if (!(o.screenLayout & MASK_SCREENLONG)) return true;
- }
- }
-
- if (orientation != o.orientation) {
- if (!orientation) return false;
- if (!o.orientation) return true;
- }
-
- if (uiMode || o.uiMode) {
- if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0) {
- if (!(uiMode & MASK_UI_MODE_TYPE)) return false;
- if (!(o.uiMode & MASK_UI_MODE_TYPE)) return true;
- }
- if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0) {
- if (!(uiMode & MASK_UI_MODE_NIGHT)) return false;
- if (!(o.uiMode & MASK_UI_MODE_NIGHT)) return true;
- }
- }
-
- // density is never 'more specific'
- // as the default just equals 160
-
- if (touchscreen != o.touchscreen) {
- if (!touchscreen) return false;
- if (!o.touchscreen) return true;
- }
-
- if (input || o.input) {
- if (((inputFlags^o.inputFlags) & MASK_KEYSHIDDEN) != 0) {
- if (!(inputFlags & MASK_KEYSHIDDEN)) return false;
- if (!(o.inputFlags & MASK_KEYSHIDDEN)) return true;
- }
-
- if (((inputFlags^o.inputFlags) & MASK_NAVHIDDEN) != 0) {
- if (!(inputFlags & MASK_NAVHIDDEN)) return false;
- if (!(o.inputFlags & MASK_NAVHIDDEN)) return true;
- }
-
- if (keyboard != o.keyboard) {
- if (!keyboard) return false;
- if (!o.keyboard) return true;
- }
-
- if (navigation != o.navigation) {
- if (!navigation) return false;
- if (!o.navigation) return true;
- }
- }
-
- if (screenSize || o.screenSize) {
- if (screenWidth != o.screenWidth) {
- if (!screenWidth) return false;
- if (!o.screenWidth) return true;
- }
-
- if (screenHeight != o.screenHeight) {
- if (!screenHeight) return false;
- if (!o.screenHeight) return true;
- }
- }
-
- if (version || o.version) {
- if (sdkVersion != o.sdkVersion) {
- if (!sdkVersion) return false;
- if (!o.sdkVersion) return true;
- }
-
- if (minorVersion != o.minorVersion) {
- if (!minorVersion) return false;
- if (!o.minorVersion) return true;
- }
- }
- return false;
- }
+ bool isMoreSpecificThan(const ResTable_config& o) const;
// Return true if 'this' is a better match than 'o' for the 'requested'
// configuration. This assumes that match() has already been used to
@@ -1231,222 +1040,7 @@
// they are not equal then one must be generic because only generic and
// '==requested' will pass the match() call. So if this is not generic,
// it wins. If this IS generic, o wins (return false).
- inline bool
- isBetterThan(const ResTable_config& o,
- const ResTable_config* requested) const {
- if (requested) {
- if (imsi || o.imsi) {
- if ((mcc != o.mcc) && requested->mcc) {
- return (mcc);
- }
-
- if ((mnc != o.mnc) && requested->mnc) {
- return (mnc);
- }
- }
-
- if (locale || o.locale) {
- if ((language[0] != o.language[0]) && requested->language[0]) {
- return (language[0]);
- }
-
- if ((country[0] != o.country[0]) && requested->country[0]) {
- return (country[0]);
- }
- }
-
- if (smallestScreenWidthDp || o.smallestScreenWidthDp) {
- // The configuration closest to the actual size is best.
- // We assume that larger configs have already been filtered
- // out at this point. That means we just want the largest one.
- return smallestScreenWidthDp >= o.smallestScreenWidthDp;
- }
-
- if (screenSizeDp || o.screenSizeDp) {
- // "Better" is based on the sum of the difference between both
- // width and height from the requested dimensions. We are
- // assuming the invalid configs (with smaller dimens) have
- // already been filtered. Note that if a particular dimension
- // is unspecified, we will end up with a large value (the
- // difference between 0 and the requested dimension), which is
- // good since we will prefer a config that has specified a
- // dimension value.
- int myDelta = 0, otherDelta = 0;
- if (requested->screenWidthDp) {
- myDelta += requested->screenWidthDp - screenWidthDp;
- otherDelta += requested->screenWidthDp - o.screenWidthDp;
- }
- if (requested->screenHeightDp) {
- myDelta += requested->screenHeightDp - screenHeightDp;
- otherDelta += requested->screenHeightDp - o.screenHeightDp;
- }
- //ALOGI("Comparing this %dx%d to other %dx%d in %dx%d: myDelta=%d otherDelta=%d",
- // screenWidthDp, screenHeightDp, o.screenWidthDp, o.screenHeightDp,
- // requested->screenWidthDp, requested->screenHeightDp, myDelta, otherDelta);
- return (myDelta <= otherDelta);
- }
-
- if (screenLayout || o.screenLayout) {
- if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0
- && (requested->screenLayout & MASK_SCREENSIZE)) {
- // A little backwards compatibility here: undefined is
- // considered equivalent to normal. But only if the
- // requested size is at least normal; otherwise, small
- // is better than the default.
- int mySL = (screenLayout & MASK_SCREENSIZE);
- int oSL = (o.screenLayout & MASK_SCREENSIZE);
- int fixedMySL = mySL;
- int fixedOSL = oSL;
- if ((requested->screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) {
- if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL;
- if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL;
- }
- // For screen size, the best match is the one that is
- // closest to the requested screen size, but not over
- // (the not over part is dealt with in match() below).
- if (fixedMySL == fixedOSL) {
- // If the two are the same, but 'this' is actually
- // undefined, then the other is really a better match.
- if (mySL == 0) return false;
- return true;
- }
- return fixedMySL >= fixedOSL;
- }
- if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0
- && (requested->screenLayout & MASK_SCREENLONG)) {
- return (screenLayout & MASK_SCREENLONG);
- }
- }
-
- if ((orientation != o.orientation) && requested->orientation) {
- return (orientation);
- }
-
- if (uiMode || o.uiMode) {
- if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0
- && (requested->uiMode & MASK_UI_MODE_TYPE)) {
- return (uiMode & MASK_UI_MODE_TYPE);
- }
- if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0
- && (requested->uiMode & MASK_UI_MODE_NIGHT)) {
- return (uiMode & MASK_UI_MODE_NIGHT);
- }
- }
-
- if (screenType || o.screenType) {
- if (density != o.density) {
- // density is tough. Any density is potentially useful
- // because the system will scale it. Scaling down
- // is generally better than scaling up.
- // Default density counts as 160dpi (the system default)
- // TODO - remove 160 constants
- int h = (density?density:160);
- int l = (o.density?o.density:160);
- bool bImBigger = true;
- if (l > h) {
- int t = h;
- h = l;
- l = t;
- bImBigger = false;
- }
-
- int reqValue = (requested->density?requested->density:160);
- if (reqValue >= h) {
- // requested value higher than both l and h, give h
- return bImBigger;
- }
- if (l >= reqValue) {
- // requested value lower than both l and h, give l
- return !bImBigger;
- }
- // saying that scaling down is 2x better than up
- if (((2 * l) - reqValue) * h > reqValue * reqValue) {
- return !bImBigger;
- } else {
- return bImBigger;
- }
- }
-
- if ((touchscreen != o.touchscreen) && requested->touchscreen) {
- return (touchscreen);
- }
- }
-
- if (input || o.input) {
- const int keysHidden = inputFlags & MASK_KEYSHIDDEN;
- const int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN;
- if (keysHidden != oKeysHidden) {
- const int reqKeysHidden =
- requested->inputFlags & MASK_KEYSHIDDEN;
- if (reqKeysHidden) {
-
- if (!keysHidden) return false;
- if (!oKeysHidden) return true;
- // For compatibility, we count KEYSHIDDEN_NO as being
- // the same as KEYSHIDDEN_SOFT. Here we disambiguate
- // these by making an exact match more specific.
- if (reqKeysHidden == keysHidden) return true;
- if (reqKeysHidden == oKeysHidden) return false;
- }
- }
-
- const int navHidden = inputFlags & MASK_NAVHIDDEN;
- const int oNavHidden = o.inputFlags & MASK_NAVHIDDEN;
- if (navHidden != oNavHidden) {
- const int reqNavHidden =
- requested->inputFlags & MASK_NAVHIDDEN;
- if (reqNavHidden) {
-
- if (!navHidden) return false;
- if (!oNavHidden) return true;
- }
- }
-
- if ((keyboard != o.keyboard) && requested->keyboard) {
- return (keyboard);
- }
-
- if ((navigation != o.navigation) && requested->navigation) {
- return (navigation);
- }
- }
-
- if (screenSize || o.screenSize) {
- // "Better" is based on the sum of the difference between both
- // width and height from the requested dimensions. We are
- // assuming the invalid configs (with smaller sizes) have
- // already been filtered. Note that if a particular dimension
- // is unspecified, we will end up with a large value (the
- // difference between 0 and the requested dimension), which is
- // good since we will prefer a config that has specified a
- // size value.
- int myDelta = 0, otherDelta = 0;
- if (requested->screenWidth) {
- myDelta += requested->screenWidth - screenWidth;
- otherDelta += requested->screenWidth - o.screenWidth;
- }
- if (requested->screenHeight) {
- myDelta += requested->screenHeight - screenHeight;
- otherDelta += requested->screenHeight - o.screenHeight;
- }
- return (myDelta <= otherDelta);
- }
-
- if (version || o.version) {
- if ((sdkVersion != o.sdkVersion) && requested->sdkVersion) {
- return (sdkVersion > o.sdkVersion);
- }
-
- if ((minorVersion != o.minorVersion) &&
- requested->minorVersion) {
- return (minorVersion);
- }
- }
-
- return false;
- }
- return isMoreSpecificThan(o);
- }
+ bool isBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
// Return true if 'this' can be considered a match for the parameters in
// 'settings'.
@@ -1454,150 +1048,11 @@
// but a request for the default should not match odd specifics
// (ie, request with no mcc should not match a particular mcc's data)
// settings is the requested settings
- inline bool match(const ResTable_config& settings) const {
- if (imsi != 0) {
- if (mcc != 0 && mcc != settings.mcc) {
- return false;
- }
- if (mnc != 0 && mnc != settings.mnc) {
- return false;
- }
- }
- if (locale != 0) {
- if (language[0] != 0
- && (language[0] != settings.language[0]
- || language[1] != settings.language[1])) {
- return false;
- }
- if (country[0] != 0
- && (country[0] != settings.country[0]
- || country[1] != settings.country[1])) {
- return false;
- }
- }
- if (screenConfig != 0) {
- const int screenSize = screenLayout&MASK_SCREENSIZE;
- const int setScreenSize = settings.screenLayout&MASK_SCREENSIZE;
- // Any screen sizes for larger screens than the setting do not
- // match.
- if (screenSize != 0 && screenSize > setScreenSize) {
- return false;
- }
-
- const int screenLong = screenLayout&MASK_SCREENLONG;
- const int setScreenLong = settings.screenLayout&MASK_SCREENLONG;
- if (screenLong != 0 && screenLong != setScreenLong) {
- return false;
- }
+ bool match(const ResTable_config& settings) const;
- const int uiModeType = uiMode&MASK_UI_MODE_TYPE;
- const int setUiModeType = settings.uiMode&MASK_UI_MODE_TYPE;
- if (uiModeType != 0 && uiModeType != setUiModeType) {
- return false;
- }
+ void getLocale(char str[6]) const;
- const int uiModeNight = uiMode&MASK_UI_MODE_NIGHT;
- const int setUiModeNight = settings.uiMode&MASK_UI_MODE_NIGHT;
- if (uiModeNight != 0 && uiModeNight != setUiModeNight) {
- return false;
- }
-
- if (smallestScreenWidthDp != 0
- && smallestScreenWidthDp > settings.smallestScreenWidthDp) {
- return false;
- }
- }
- if (screenSizeDp != 0) {
- if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) {
- //ALOGI("Filtering out width %d in requested %d", screenWidthDp, settings.screenWidthDp);
- return false;
- }
- if (screenHeightDp != 0 && screenHeightDp > settings.screenHeightDp) {
- //ALOGI("Filtering out height %d in requested %d", screenHeightDp, settings.screenHeightDp);
- return false;
- }
- }
- if (screenType != 0) {
- if (orientation != 0 && orientation != settings.orientation) {
- return false;
- }
- // density always matches - we can scale it. See isBetterThan
- if (touchscreen != 0 && touchscreen != settings.touchscreen) {
- return false;
- }
- }
- if (input != 0) {
- const int keysHidden = inputFlags&MASK_KEYSHIDDEN;
- const int setKeysHidden = settings.inputFlags&MASK_KEYSHIDDEN;
- if (keysHidden != 0 && keysHidden != setKeysHidden) {
- // For compatibility, we count a request for KEYSHIDDEN_NO as also
- // matching the more recent KEYSHIDDEN_SOFT. Basically
- // KEYSHIDDEN_NO means there is some kind of keyboard available.
- //ALOGI("Matching keysHidden: have=%d, config=%d\n", keysHidden, setKeysHidden);
- if (keysHidden != KEYSHIDDEN_NO || setKeysHidden != KEYSHIDDEN_SOFT) {
- //ALOGI("No match!");
- return false;
- }
- }
- const int navHidden = inputFlags&MASK_NAVHIDDEN;
- const int setNavHidden = settings.inputFlags&MASK_NAVHIDDEN;
- if (navHidden != 0 && navHidden != setNavHidden) {
- return false;
- }
- if (keyboard != 0 && keyboard != settings.keyboard) {
- return false;
- }
- if (navigation != 0 && navigation != settings.navigation) {
- return false;
- }
- }
- if (screenSize != 0) {
- if (screenWidth != 0 && screenWidth > settings.screenWidth) {
- return false;
- }
- if (screenHeight != 0 && screenHeight > settings.screenHeight) {
- return false;
- }
- }
- if (version != 0) {
- if (sdkVersion != 0 && sdkVersion > settings.sdkVersion) {
- return false;
- }
- if (minorVersion != 0 && minorVersion != settings.minorVersion) {
- return false;
- }
- }
- return true;
- }
-
- void getLocale(char str[6]) const {
- memset(str, 0, 6);
- if (language[0]) {
- str[0] = language[0];
- str[1] = language[1];
- if (country[0]) {
- str[2] = '_';
- str[3] = country[0];
- str[4] = country[1];
- }
- }
- }
-
- String8 toString() const {
- char buf[200];
- sprintf(buf, "imsi=%d/%d lang=%c%c reg=%c%c orient=%d touch=%d dens=%d "
- "kbd=%d nav=%d input=%d ssz=%dx%d sw%ddp w%ddp h%ddp sz=%d long=%d "
- "ui=%d night=%d vers=%d.%d",
- mcc, mnc,
- language[0] ? language[0] : '-', language[1] ? language[1] : '-',
- country[0] ? country[0] : '-', country[1] ? country[1] : '-',
- orientation, touchscreen, density, keyboard, navigation, inputFlags,
- screenWidth, screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
- screenLayout&MASK_SCREENSIZE, screenLayout&MASK_SCREENLONG,
- uiMode&MASK_UI_MODE_TYPE, uiMode&MASK_UI_MODE_NIGHT,
- sdkVersion, minorVersion);
- return String8(buf);
- }
+ String8 toString() const;
};
/**
@@ -2056,8 +1511,14 @@
const char16_t* getBasePackageName(size_t idx) const;
uint32_t getBasePackageId(size_t idx) const;
+ // Return the number of resource tables that the object contains.
size_t getTableCount() const;
+ // Return the values string pool for the resource table at the given
+ // index. This string pool contains all of the strings for values
+ // contained in the resource table -- that is the item values themselves,
+ // but not the names their entries or types.
const ResStringPool* getTableStringBlock(size_t index) const;
+ // Return unique cookie identifier for the given resource table.
void* getTableCookie(size_t index) const;
// Return the configurations (ResTable_config) that we know about
diff --git a/include/utils/threads.h b/include/utils/threads.h
index ab3e8cd..b4a8b7c 100644
--- a/include/utils/threads.h
+++ b/include/utils/threads.h
@@ -526,6 +526,12 @@
// Do not call from this object's thread; will return WOULD_BLOCK in that case.
status_t join();
+#ifdef HAVE_ANDROID_OS
+ // Return the thread's kernel ID, same as the thread itself calling gettid() or
+ // androidGetTid(), or -1 if the thread is not running.
+ pid_t getTid() const;
+#endif
+
protected:
// exitPending() returns true if requestExit() has been called.
bool exitPending() const;
@@ -551,8 +557,10 @@
volatile bool mExitPending;
volatile bool mRunning;
sp<Thread> mHoldSelf;
-#if HAVE_ANDROID_OS
- int mTid;
+#ifdef HAVE_ANDROID_OS
+ // legacy for debugging, not used by getTid() as it is set by the child thread
+ // and so is not initialized until the child reaches that point
+ pid_t mTid;
#endif
};
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index bd213d5..afae70f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -754,7 +754,7 @@
// TODO: See LayerRenderer.cpp::generateMesh() for important
// information about this implementation
- if (!layer->region.isEmpty()) {
+ if (CC_LIKELY(!layer->region.isEmpty())) {
size_t count;
const android::Rect* rects = layer->region.getArray(&count);
@@ -1398,7 +1398,7 @@
if (!texture) return;
const AutoTexture autoCleanup(texture);
- if (bitmap->getConfig() == SkBitmap::kA8_Config) {
+ if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
drawAlphaBitmap(texture, left, top, paint);
} else {
drawTextureRect(left, top, right, bottom, texture, paint);
@@ -1454,9 +1454,9 @@
float bottom = FLT_MIN;
#if RENDER_LAYERS_AS_REGIONS
- bool hasActiveLayer = hasLayer();
+ const bool hasActiveLayer = hasLayer();
#else
- bool hasActiveLayer = false;
+ const bool hasActiveLayer = false;
#endif
// TODO: Support the colors array
@@ -1541,7 +1541,7 @@
texture->setWrap(GL_CLAMP_TO_EDGE, true);
- if (mSnapshot->transform->isPureTranslate()) {
+ if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) {
const float x = (int) floorf(dstLeft + mSnapshot->transform->getTranslateX() + 0.5f);
const float y = (int) floorf(dstTop + mSnapshot->transform->getTranslateY() + 0.5f);
@@ -1587,7 +1587,7 @@
const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(),
right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors);
- if (mesh && mesh->verticesCount > 0) {
+ if (CC_LIKELY(mesh && mesh->verticesCount > 0)) {
const bool pureTranslate = mSnapshot->transform->isPureTranslate();
#if RENDER_LAYERS_AS_REGIONS
// Mark the current layer dirty where we are going to draw the patch
@@ -1597,7 +1597,7 @@
const size_t count = mesh->quads.size();
for (size_t i = 0; i < count; i++) {
const Rect& bounds = mesh->quads.itemAt(i);
- if (pureTranslate) {
+ if (CC_LIKELY(pureTranslate)) {
const float x = (int) floorf(bounds.left + offsetX + 0.5f);
const float y = (int) floorf(bounds.top + offsetY + 0.5f);
dirtyLayer(x, y, x + bounds.getWidth(), y + bounds.getHeight());
@@ -1609,7 +1609,7 @@
}
#endif
- if (pureTranslate) {
+ if (CC_LIKELY(pureTranslate)) {
const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
@@ -1637,7 +1637,7 @@
float inverseScaleX = 1.0f;
float inverseScaleY = 1.0f;
// The quad that we use needs to account for scaling.
- if (!mSnapshot->transform->isPureTranslate()) {
+ if (CC_UNLIKELY(!mSnapshot->transform->isPureTranslate())) {
Matrix4 *mat = mSnapshot->transform;
float m00 = mat->data[Matrix4::kScaleX];
float m01 = mat->data[Matrix4::kSkewY];
@@ -1743,7 +1743,7 @@
// The quad that we use for AA and hairlines needs to account for scaling. For hairlines
// the line on the screen should always be one pixel wide regardless of scale. For
// AA lines, we only want one pixel of translucent boundary around the quad.
- if (!mSnapshot->transform->isPureTranslate()) {
+ if (CC_UNLIKELY(!mSnapshot->transform->isPureTranslate())) {
Matrix4 *mat = mSnapshot->transform;
float m00 = mat->data[Matrix4::kScaleX];
float m01 = mat->data[Matrix4::kSkewY];
@@ -1751,8 +1751,8 @@
float m10 = mat->data[Matrix4::kSkewX];
float m11 = mat->data[Matrix4::kScaleX];
float m12 = mat->data[6];
- float scaleX = sqrt(m00*m00 + m01*m01);
- float scaleY = sqrt(m10*m10 + m11*m11);
+ float scaleX = sqrtf(m00 * m00 + m01 * m01);
+ float scaleY = sqrtf(m10 * m10 + m11 * m11);
inverseScaleX = (scaleX != 0) ? (inverseScaleX / scaleX) : 0;
inverseScaleY = (scaleY != 0) ? (inverseScaleY / scaleY) : 0;
if (inverseScaleX != 1.0f || inverseScaleY != 1.0f) {
@@ -1770,11 +1770,7 @@
setupDrawColor(paint->getColor(), alpha);
setupDrawColorFilter();
setupDrawShader();
- if (isAA) {
- setupDrawBlending(true, mode);
- } else {
- setupDrawBlending(mode);
- }
+ setupDrawBlending(isAA, mode);
setupDrawProgram();
setupDrawModelViewIdentity(true);
setupDrawColorUniforms();
@@ -1792,7 +1788,7 @@
Vertex* vertices = &lines[0];
AAVertex wLines[verticesCount];
AAVertex* aaVertices = &wLines[0];
- if (!isAA) {
+ if (CC_UNLIKELY(!isAA)) {
setupDrawVertices(vertices);
} else {
void* widthCoords = ((GLbyte*) aaVertices) + gVertexAAWidthOffset;
@@ -2152,9 +2148,9 @@
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
#if RENDER_LAYERS_AS_REGIONS
- bool hasActiveLayer = hasLayer();
+ const bool hasActiveLayer = hasLayer();
#else
- bool hasActiveLayer = false;
+ const bool hasActiveLayer = false;
#endif
if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
@@ -2201,7 +2197,7 @@
const float oldX = x;
const float oldY = y;
const bool pureTranslate = mSnapshot->transform->isPureTranslate();
- if (pureTranslate) {
+ if (CC_LIKELY(pureTranslate)) {
x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
}
@@ -2218,7 +2214,7 @@
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
- if (mHasShadow) {
+ if (CC_UNLIKELY(mHasShadow)) {
mCaches.activeTexture(0);
mCaches.dropShadowCache.setFontRenderer(fontRenderer);
@@ -2277,9 +2273,9 @@
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
#if RENDER_LAYERS_AS_REGIONS
- bool hasActiveLayer = hasLayer();
+ const bool hasActiveLayer = hasLayer();
#else
- bool hasActiveLayer = false;
+ const bool hasActiveLayer = false;
#endif
if (fontRenderer.renderText(paint, clip, text, 0, bytesCount, count, x, y,
@@ -2326,7 +2322,7 @@
layer->setAlpha(alpha, mode);
#if RENDER_LAYERS_AS_REGIONS
- if (!layer->region.isEmpty()) {
+ if (CC_LIKELY(!layer->region.isEmpty())) {
if (layer->region.isRect()) {
composeLayerRect(layer, layer->regionRect);
} else if (layer->mesh) {
@@ -2342,7 +2338,7 @@
setupDrawPureColorUniforms();
setupDrawColorFilterUniforms();
setupDrawTexture(layer->getTexture());
- if (mSnapshot->transform->isPureTranslate()) {
+ if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) {
x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
@@ -2502,7 +2498,7 @@
break;
}
- if (underlineWidth > 0.0f) {
+ if (CC_LIKELY(underlineWidth > 0.0f)) {
const float textSize = paintCopy.getTextSize();
const float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
@@ -2571,7 +2567,7 @@
texture->setWrap(GL_CLAMP_TO_EDGE, true);
- if (mSnapshot->transform->isPureTranslate()) {
+ if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) {
const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
@@ -2631,8 +2627,8 @@
// the blending, turn blending off here
// If the blend mode cannot be implemented using shaders, fall
// back to the default SrcOver blend mode instead
- if (mode > SkXfermode::kScreen_Mode) {
- if (mCaches.extensions.hasFramebufferFetch()) {
+ if CC_UNLIKELY((mode > SkXfermode::kScreen_Mode)) {
+ if (CC_UNLIKELY(mCaches.extensions.hasFramebufferFetch())) {
description.framebufferMode = mode;
description.swapSrcDst = swapSrcDst;
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index afc8ba0..929dd68 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -206,7 +206,6 @@
return false;
}
- rsAssert(bcWrapper.getHeaderVersion() == 0);
if (bcWrapper.getBCFileType() == bcinfo::BC_WRAPPER) {
sdkVersion = bcWrapper.getTargetAPI();
}
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index 544ab74..24cf504 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -98,7 +98,8 @@
LOCAL_C_INCLUDES += \
external/zlib \
- external/icu4c/common
+ external/icu4c/common \
+ bionic/libc/private
LOCAL_LDLIBS += -lpthread
@@ -114,7 +115,10 @@
ifeq ($(TARGET_OS),linux)
include $(CLEAR_VARS)
-LOCAL_C_INCLUDES += external/zlib external/icu4c/common
+LOCAL_C_INCLUDES += \
+ external/zlib \
+ external/icu4c/common \
+ bionic/libc/private
LOCAL_LDLIBS := -lrt -ldl -lpthread
LOCAL_MODULE := libutils
LOCAL_SRC_FILES := $(commonSources) BackupData.cpp BackupHelpers.cpp
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index 15b83bb..3fa562e 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -221,7 +221,7 @@
static bool assertIdmapHeader(const uint32_t* map, size_t sizeBytes)
{
if (sizeBytes < ResTable::IDMAP_HEADER_SIZE_BYTES) {
- ALOGW("idmap assertion failed: size=%d bytes\n", sizeBytes);
+ ALOGW("idmap assertion failed: size=%d bytes\n", (int)sizeBytes);
return false;
}
if (*map != htodl(IDMAP_MAGIC)) { // htodl: map data expected to be in correct endianess
@@ -250,7 +250,7 @@
return UNKNOWN_ERROR;
}
if (typeCount > size) {
- ALOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, size);
+ ALOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, (int)size);
return UNKNOWN_ERROR;
}
const uint32_t typeOffset = map[type];
@@ -260,7 +260,7 @@
}
if (typeOffset + 1 > size) {
ALOGW("Resource ID map: type offset=%d exceeds reasonable value, size of map=%d\n",
- typeOffset, size);
+ typeOffset, (int)size);
return UNKNOWN_ERROR;
}
const uint32_t entryCount = map[typeOffset];
@@ -271,7 +271,7 @@
}
const uint32_t index = typeOffset + 2 + entry - entryOffset;
if (index > size) {
- ALOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, size);
+ ALOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, (int)size);
*outValue = 0;
return NO_ERROR;
}
@@ -659,6 +659,16 @@
return NULL;
}
+const String8 ResStringPool::string8ObjectAt(size_t idx) const
+{
+ size_t len;
+ const char *str = (const char*)string8At(idx, &len);
+ if (str != NULL) {
+ return String8(str);
+ }
+ return String8(stringAt(idx, &len));
+}
+
const ResStringPool_span* ResStringPool::styleAt(const ResStringPool_ref& ref) const
{
return styleAt(ref.index);
@@ -738,12 +748,25 @@
return (mError == NO_ERROR) ? mHeader->stringCount : 0;
}
-#ifndef HAVE_ANDROID_OS
+size_t ResStringPool::styleCount() const
+{
+ return (mError == NO_ERROR) ? mHeader->styleCount : 0;
+}
+
+size_t ResStringPool::bytes() const
+{
+ return (mError == NO_ERROR) ? mHeader->header.size : 0;
+}
+
+bool ResStringPool::isSorted() const
+{
+ return (mHeader->flags&ResStringPool_header::SORTED_FLAG)!=0;
+}
+
bool ResStringPool::isUTF8() const
{
return (mHeader->flags&ResStringPool_header::UTF8_FLAG)!=0;
}
-#endif
// --------------------------------------------------------------------
// --------------------------------------------------------------------
@@ -1367,6 +1390,873 @@
// --------------------------------------------------------------------
// --------------------------------------------------------------------
+void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
+ const size_t size = dtohl(o.size);
+ if (size >= sizeof(ResTable_config)) {
+ *this = o;
+ } else {
+ memcpy(this, &o, size);
+ memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
+ }
+}
+
+void ResTable_config::copyFromDtoH(const ResTable_config& o) {
+ copyFromDeviceNoSwap(o);
+ size = sizeof(ResTable_config);
+ mcc = dtohs(mcc);
+ mnc = dtohs(mnc);
+ density = dtohs(density);
+ screenWidth = dtohs(screenWidth);
+ screenHeight = dtohs(screenHeight);
+ sdkVersion = dtohs(sdkVersion);
+ minorVersion = dtohs(minorVersion);
+ smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
+ screenWidthDp = dtohs(screenWidthDp);
+ screenHeightDp = dtohs(screenHeightDp);
+}
+
+void ResTable_config::swapHtoD() {
+ size = htodl(size);
+ mcc = htods(mcc);
+ mnc = htods(mnc);
+ density = htods(density);
+ screenWidth = htods(screenWidth);
+ screenHeight = htods(screenHeight);
+ sdkVersion = htods(sdkVersion);
+ minorVersion = htods(minorVersion);
+ smallestScreenWidthDp = htods(smallestScreenWidthDp);
+ screenWidthDp = htods(screenWidthDp);
+ screenHeightDp = htods(screenHeightDp);
+}
+
+int ResTable_config::compare(const ResTable_config& o) const {
+ int32_t diff = (int32_t)(imsi - o.imsi);
+ if (diff != 0) return diff;
+ diff = (int32_t)(locale - o.locale);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenType - o.screenType);
+ if (diff != 0) return diff;
+ diff = (int32_t)(input - o.input);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenSize - o.screenSize);
+ if (diff != 0) return diff;
+ diff = (int32_t)(version - o.version);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenLayout - o.screenLayout);
+ if (diff != 0) return diff;
+ diff = (int32_t)(uiMode - o.uiMode);
+ if (diff != 0) return diff;
+ diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenSizeDp - o.screenSizeDp);
+ return (int)diff;
+}
+
+int ResTable_config::compareLogical(const ResTable_config& o) const {
+ if (mcc != o.mcc) {
+ return mcc < o.mcc ? -1 : 1;
+ }
+ if (mnc != o.mnc) {
+ return mnc < o.mnc ? -1 : 1;
+ }
+ if (language[0] != o.language[0]) {
+ return language[0] < o.language[0] ? -1 : 1;
+ }
+ if (language[1] != o.language[1]) {
+ return language[1] < o.language[1] ? -1 : 1;
+ }
+ if (country[0] != o.country[0]) {
+ return country[0] < o.country[0] ? -1 : 1;
+ }
+ if (country[1] != o.country[1]) {
+ return country[1] < o.country[1] ? -1 : 1;
+ }
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
+ return smallestScreenWidthDp < o.smallestScreenWidthDp ? -1 : 1;
+ }
+ if (screenWidthDp != o.screenWidthDp) {
+ return screenWidthDp < o.screenWidthDp ? -1 : 1;
+ }
+ if (screenHeightDp != o.screenHeightDp) {
+ return screenHeightDp < o.screenHeightDp ? -1 : 1;
+ }
+ if (screenWidth != o.screenWidth) {
+ return screenWidth < o.screenWidth ? -1 : 1;
+ }
+ if (screenHeight != o.screenHeight) {
+ return screenHeight < o.screenHeight ? -1 : 1;
+ }
+ if (density != o.density) {
+ return density < o.density ? -1 : 1;
+ }
+ if (orientation != o.orientation) {
+ return orientation < o.orientation ? -1 : 1;
+ }
+ if (touchscreen != o.touchscreen) {
+ return touchscreen < o.touchscreen ? -1 : 1;
+ }
+ if (input != o.input) {
+ return input < o.input ? -1 : 1;
+ }
+ if (screenLayout != o.screenLayout) {
+ return screenLayout < o.screenLayout ? -1 : 1;
+ }
+ if (uiMode != o.uiMode) {
+ return uiMode < o.uiMode ? -1 : 1;
+ }
+ if (version != o.version) {
+ return version < o.version ? -1 : 1;
+ }
+ return 0;
+}
+
+int ResTable_config::diff(const ResTable_config& o) const {
+ int diffs = 0;
+ if (mcc != o.mcc) diffs |= CONFIG_MCC;
+ if (mnc != o.mnc) diffs |= CONFIG_MNC;
+ if (locale != o.locale) diffs |= CONFIG_LOCALE;
+ if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION;
+ if (density != o.density) diffs |= CONFIG_DENSITY;
+ if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN;
+ if (((inputFlags^o.inputFlags)&(MASK_KEYSHIDDEN|MASK_NAVHIDDEN)) != 0)
+ diffs |= CONFIG_KEYBOARD_HIDDEN;
+ if (keyboard != o.keyboard) diffs |= CONFIG_KEYBOARD;
+ if (navigation != o.navigation) diffs |= CONFIG_NAVIGATION;
+ if (screenSize != o.screenSize) diffs |= CONFIG_SCREEN_SIZE;
+ if (version != o.version) diffs |= CONFIG_VERSION;
+ if (screenLayout != o.screenLayout) diffs |= CONFIG_SCREEN_LAYOUT;
+ if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
+ if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
+ return diffs;
+}
+
+bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
+ // The order of the following tests defines the importance of one
+ // configuration parameter over another. Those tests first are more
+ // important, trumping any values in those following them.
+ if (imsi || o.imsi) {
+ if (mcc != o.mcc) {
+ if (!mcc) return false;
+ if (!o.mcc) return true;
+ }
+
+ if (mnc != o.mnc) {
+ if (!mnc) return false;
+ if (!o.mnc) return true;
+ }
+ }
+
+ if (locale || o.locale) {
+ if (language[0] != o.language[0]) {
+ if (!language[0]) return false;
+ if (!o.language[0]) return true;
+ }
+
+ if (country[0] != o.country[0]) {
+ if (!country[0]) return false;
+ if (!o.country[0]) return true;
+ }
+ }
+
+ if (smallestScreenWidthDp || o.smallestScreenWidthDp) {
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
+ if (!smallestScreenWidthDp) return false;
+ if (!o.smallestScreenWidthDp) return true;
+ }
+ }
+
+ if (screenSizeDp || o.screenSizeDp) {
+ if (screenWidthDp != o.screenWidthDp) {
+ if (!screenWidthDp) return false;
+ if (!o.screenWidthDp) return true;
+ }
+
+ if (screenHeightDp != o.screenHeightDp) {
+ if (!screenHeightDp) return false;
+ if (!o.screenHeightDp) return true;
+ }
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0) {
+ if (!(screenLayout & MASK_SCREENSIZE)) return false;
+ if (!(o.screenLayout & MASK_SCREENSIZE)) return true;
+ }
+ if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0) {
+ if (!(screenLayout & MASK_SCREENLONG)) return false;
+ if (!(o.screenLayout & MASK_SCREENLONG)) return true;
+ }
+ }
+
+ if (orientation != o.orientation) {
+ if (!orientation) return false;
+ if (!o.orientation) return true;
+ }
+
+ if (uiMode || o.uiMode) {
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0) {
+ if (!(uiMode & MASK_UI_MODE_TYPE)) return false;
+ if (!(o.uiMode & MASK_UI_MODE_TYPE)) return true;
+ }
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0) {
+ if (!(uiMode & MASK_UI_MODE_NIGHT)) return false;
+ if (!(o.uiMode & MASK_UI_MODE_NIGHT)) return true;
+ }
+ }
+
+ // density is never 'more specific'
+ // as the default just equals 160
+
+ if (touchscreen != o.touchscreen) {
+ if (!touchscreen) return false;
+ if (!o.touchscreen) return true;
+ }
+
+ if (input || o.input) {
+ if (((inputFlags^o.inputFlags) & MASK_KEYSHIDDEN) != 0) {
+ if (!(inputFlags & MASK_KEYSHIDDEN)) return false;
+ if (!(o.inputFlags & MASK_KEYSHIDDEN)) return true;
+ }
+
+ if (((inputFlags^o.inputFlags) & MASK_NAVHIDDEN) != 0) {
+ if (!(inputFlags & MASK_NAVHIDDEN)) return false;
+ if (!(o.inputFlags & MASK_NAVHIDDEN)) return true;
+ }
+
+ if (keyboard != o.keyboard) {
+ if (!keyboard) return false;
+ if (!o.keyboard) return true;
+ }
+
+ if (navigation != o.navigation) {
+ if (!navigation) return false;
+ if (!o.navigation) return true;
+ }
+ }
+
+ if (screenSize || o.screenSize) {
+ if (screenWidth != o.screenWidth) {
+ if (!screenWidth) return false;
+ if (!o.screenWidth) return true;
+ }
+
+ if (screenHeight != o.screenHeight) {
+ if (!screenHeight) return false;
+ if (!o.screenHeight) return true;
+ }
+ }
+
+ if (version || o.version) {
+ if (sdkVersion != o.sdkVersion) {
+ if (!sdkVersion) return false;
+ if (!o.sdkVersion) return true;
+ }
+
+ if (minorVersion != o.minorVersion) {
+ if (!minorVersion) return false;
+ if (!o.minorVersion) return true;
+ }
+ }
+ return false;
+}
+
+bool ResTable_config::isBetterThan(const ResTable_config& o,
+ const ResTable_config* requested) const {
+ if (requested) {
+ if (imsi || o.imsi) {
+ if ((mcc != o.mcc) && requested->mcc) {
+ return (mcc);
+ }
+
+ if ((mnc != o.mnc) && requested->mnc) {
+ return (mnc);
+ }
+ }
+
+ if (locale || o.locale) {
+ if ((language[0] != o.language[0]) && requested->language[0]) {
+ return (language[0]);
+ }
+
+ if ((country[0] != o.country[0]) && requested->country[0]) {
+ return (country[0]);
+ }
+ }
+
+ if (smallestScreenWidthDp || o.smallestScreenWidthDp) {
+ // The configuration closest to the actual size is best.
+ // We assume that larger configs have already been filtered
+ // out at this point. That means we just want the largest one.
+ return smallestScreenWidthDp >= o.smallestScreenWidthDp;
+ }
+
+ if (screenSizeDp || o.screenSizeDp) {
+ // "Better" is based on the sum of the difference between both
+ // width and height from the requested dimensions. We are
+ // assuming the invalid configs (with smaller dimens) have
+ // already been filtered. Note that if a particular dimension
+ // is unspecified, we will end up with a large value (the
+ // difference between 0 and the requested dimension), which is
+ // good since we will prefer a config that has specified a
+ // dimension value.
+ int myDelta = 0, otherDelta = 0;
+ if (requested->screenWidthDp) {
+ myDelta += requested->screenWidthDp - screenWidthDp;
+ otherDelta += requested->screenWidthDp - o.screenWidthDp;
+ }
+ if (requested->screenHeightDp) {
+ myDelta += requested->screenHeightDp - screenHeightDp;
+ otherDelta += requested->screenHeightDp - o.screenHeightDp;
+ }
+ //ALOGI("Comparing this %dx%d to other %dx%d in %dx%d: myDelta=%d otherDelta=%d",
+ // screenWidthDp, screenHeightDp, o.screenWidthDp, o.screenHeightDp,
+ // requested->screenWidthDp, requested->screenHeightDp, myDelta, otherDelta);
+ return (myDelta <= otherDelta);
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0
+ && (requested->screenLayout & MASK_SCREENSIZE)) {
+ // A little backwards compatibility here: undefined is
+ // considered equivalent to normal. But only if the
+ // requested size is at least normal; otherwise, small
+ // is better than the default.
+ int mySL = (screenLayout & MASK_SCREENSIZE);
+ int oSL = (o.screenLayout & MASK_SCREENSIZE);
+ int fixedMySL = mySL;
+ int fixedOSL = oSL;
+ if ((requested->screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) {
+ if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL;
+ if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL;
+ }
+ // For screen size, the best match is the one that is
+ // closest to the requested screen size, but not over
+ // (the not over part is dealt with in match() below).
+ if (fixedMySL == fixedOSL) {
+ // If the two are the same, but 'this' is actually
+ // undefined, then the other is really a better match.
+ if (mySL == 0) return false;
+ return true;
+ }
+ return fixedMySL >= fixedOSL;
+ }
+ if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0
+ && (requested->screenLayout & MASK_SCREENLONG)) {
+ return (screenLayout & MASK_SCREENLONG);
+ }
+ }
+
+ if ((orientation != o.orientation) && requested->orientation) {
+ return (orientation);
+ }
+
+ if (uiMode || o.uiMode) {
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0
+ && (requested->uiMode & MASK_UI_MODE_TYPE)) {
+ return (uiMode & MASK_UI_MODE_TYPE);
+ }
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0
+ && (requested->uiMode & MASK_UI_MODE_NIGHT)) {
+ return (uiMode & MASK_UI_MODE_NIGHT);
+ }
+ }
+
+ if (screenType || o.screenType) {
+ if (density != o.density) {
+ // density is tough. Any density is potentially useful
+ // because the system will scale it. Scaling down
+ // is generally better than scaling up.
+ // Default density counts as 160dpi (the system default)
+ // TODO - remove 160 constants
+ int h = (density?density:160);
+ int l = (o.density?o.density:160);
+ bool bImBigger = true;
+ if (l > h) {
+ int t = h;
+ h = l;
+ l = t;
+ bImBigger = false;
+ }
+
+ int reqValue = (requested->density?requested->density:160);
+ if (reqValue >= h) {
+ // requested value higher than both l and h, give h
+ return bImBigger;
+ }
+ if (l >= reqValue) {
+ // requested value lower than both l and h, give l
+ return !bImBigger;
+ }
+ // saying that scaling down is 2x better than up
+ if (((2 * l) - reqValue) * h > reqValue * reqValue) {
+ return !bImBigger;
+ } else {
+ return bImBigger;
+ }
+ }
+
+ if ((touchscreen != o.touchscreen) && requested->touchscreen) {
+ return (touchscreen);
+ }
+ }
+
+ if (input || o.input) {
+ const int keysHidden = inputFlags & MASK_KEYSHIDDEN;
+ const int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN;
+ if (keysHidden != oKeysHidden) {
+ const int reqKeysHidden =
+ requested->inputFlags & MASK_KEYSHIDDEN;
+ if (reqKeysHidden) {
+
+ if (!keysHidden) return false;
+ if (!oKeysHidden) return true;
+ // For compatibility, we count KEYSHIDDEN_NO as being
+ // the same as KEYSHIDDEN_SOFT. Here we disambiguate
+ // these by making an exact match more specific.
+ if (reqKeysHidden == keysHidden) return true;
+ if (reqKeysHidden == oKeysHidden) return false;
+ }
+ }
+
+ const int navHidden = inputFlags & MASK_NAVHIDDEN;
+ const int oNavHidden = o.inputFlags & MASK_NAVHIDDEN;
+ if (navHidden != oNavHidden) {
+ const int reqNavHidden =
+ requested->inputFlags & MASK_NAVHIDDEN;
+ if (reqNavHidden) {
+
+ if (!navHidden) return false;
+ if (!oNavHidden) return true;
+ }
+ }
+
+ if ((keyboard != o.keyboard) && requested->keyboard) {
+ return (keyboard);
+ }
+
+ if ((navigation != o.navigation) && requested->navigation) {
+ return (navigation);
+ }
+ }
+
+ if (screenSize || o.screenSize) {
+ // "Better" is based on the sum of the difference between both
+ // width and height from the requested dimensions. We are
+ // assuming the invalid configs (with smaller sizes) have
+ // already been filtered. Note that if a particular dimension
+ // is unspecified, we will end up with a large value (the
+ // difference between 0 and the requested dimension), which is
+ // good since we will prefer a config that has specified a
+ // size value.
+ int myDelta = 0, otherDelta = 0;
+ if (requested->screenWidth) {
+ myDelta += requested->screenWidth - screenWidth;
+ otherDelta += requested->screenWidth - o.screenWidth;
+ }
+ if (requested->screenHeight) {
+ myDelta += requested->screenHeight - screenHeight;
+ otherDelta += requested->screenHeight - o.screenHeight;
+ }
+ return (myDelta <= otherDelta);
+ }
+
+ if (version || o.version) {
+ if ((sdkVersion != o.sdkVersion) && requested->sdkVersion) {
+ return (sdkVersion > o.sdkVersion);
+ }
+
+ if ((minorVersion != o.minorVersion) &&
+ requested->minorVersion) {
+ return (minorVersion);
+ }
+ }
+
+ return false;
+ }
+ return isMoreSpecificThan(o);
+}
+
+bool ResTable_config::match(const ResTable_config& settings) const {
+ if (imsi != 0) {
+ if (mcc != 0 && mcc != settings.mcc) {
+ return false;
+ }
+ if (mnc != 0 && mnc != settings.mnc) {
+ return false;
+ }
+ }
+ if (locale != 0) {
+ if (language[0] != 0
+ && (language[0] != settings.language[0]
+ || language[1] != settings.language[1])) {
+ return false;
+ }
+ if (country[0] != 0
+ && (country[0] != settings.country[0]
+ || country[1] != settings.country[1])) {
+ return false;
+ }
+ }
+ if (screenConfig != 0) {
+ const int screenSize = screenLayout&MASK_SCREENSIZE;
+ const int setScreenSize = settings.screenLayout&MASK_SCREENSIZE;
+ // Any screen sizes for larger screens than the setting do not
+ // match.
+ if (screenSize != 0 && screenSize > setScreenSize) {
+ return false;
+ }
+
+ const int screenLong = screenLayout&MASK_SCREENLONG;
+ const int setScreenLong = settings.screenLayout&MASK_SCREENLONG;
+ if (screenLong != 0 && screenLong != setScreenLong) {
+ return false;
+ }
+
+ const int uiModeType = uiMode&MASK_UI_MODE_TYPE;
+ const int setUiModeType = settings.uiMode&MASK_UI_MODE_TYPE;
+ if (uiModeType != 0 && uiModeType != setUiModeType) {
+ return false;
+ }
+
+ const int uiModeNight = uiMode&MASK_UI_MODE_NIGHT;
+ const int setUiModeNight = settings.uiMode&MASK_UI_MODE_NIGHT;
+ if (uiModeNight != 0 && uiModeNight != setUiModeNight) {
+ return false;
+ }
+
+ if (smallestScreenWidthDp != 0
+ && smallestScreenWidthDp > settings.smallestScreenWidthDp) {
+ return false;
+ }
+ }
+ if (screenSizeDp != 0) {
+ if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) {
+ //ALOGI("Filtering out width %d in requested %d", screenWidthDp, settings.screenWidthDp);
+ return false;
+ }
+ if (screenHeightDp != 0 && screenHeightDp > settings.screenHeightDp) {
+ //ALOGI("Filtering out height %d in requested %d", screenHeightDp, settings.screenHeightDp);
+ return false;
+ }
+ }
+ if (screenType != 0) {
+ if (orientation != 0 && orientation != settings.orientation) {
+ return false;
+ }
+ // density always matches - we can scale it. See isBetterThan
+ if (touchscreen != 0 && touchscreen != settings.touchscreen) {
+ return false;
+ }
+ }
+ if (input != 0) {
+ const int keysHidden = inputFlags&MASK_KEYSHIDDEN;
+ const int setKeysHidden = settings.inputFlags&MASK_KEYSHIDDEN;
+ if (keysHidden != 0 && keysHidden != setKeysHidden) {
+ // For compatibility, we count a request for KEYSHIDDEN_NO as also
+ // matching the more recent KEYSHIDDEN_SOFT. Basically
+ // KEYSHIDDEN_NO means there is some kind of keyboard available.
+ //ALOGI("Matching keysHidden: have=%d, config=%d\n", keysHidden, setKeysHidden);
+ if (keysHidden != KEYSHIDDEN_NO || setKeysHidden != KEYSHIDDEN_SOFT) {
+ //ALOGI("No match!");
+ return false;
+ }
+ }
+ const int navHidden = inputFlags&MASK_NAVHIDDEN;
+ const int setNavHidden = settings.inputFlags&MASK_NAVHIDDEN;
+ if (navHidden != 0 && navHidden != setNavHidden) {
+ return false;
+ }
+ if (keyboard != 0 && keyboard != settings.keyboard) {
+ return false;
+ }
+ if (navigation != 0 && navigation != settings.navigation) {
+ return false;
+ }
+ }
+ if (screenSize != 0) {
+ if (screenWidth != 0 && screenWidth > settings.screenWidth) {
+ return false;
+ }
+ if (screenHeight != 0 && screenHeight > settings.screenHeight) {
+ return false;
+ }
+ }
+ if (version != 0) {
+ if (sdkVersion != 0 && sdkVersion > settings.sdkVersion) {
+ return false;
+ }
+ if (minorVersion != 0 && minorVersion != settings.minorVersion) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void ResTable_config::getLocale(char str[6]) const {
+ memset(str, 0, 6);
+ if (language[0]) {
+ str[0] = language[0];
+ str[1] = language[1];
+ if (country[0]) {
+ str[2] = '_';
+ str[3] = country[0];
+ str[4] = country[1];
+ }
+ }
+}
+
+String8 ResTable_config::toString() const {
+ String8 res;
+
+ if (mcc != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("%dmcc", dtohs(mcc));
+ }
+ if (mnc != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("%dmnc", dtohs(mnc));
+ }
+ if (language[0] != 0) {
+ if (res.size() > 0) res.append("-");
+ res.append(language, 2);
+ }
+ if (country[0] != 0) {
+ if (res.size() > 0) res.append("-");
+ res.append(country, 2);
+ }
+ if (smallestScreenWidthDp != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("sw%ddp", dtohs(smallestScreenWidthDp));
+ }
+ if (screenWidthDp != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("w%ddp", dtohs(screenWidthDp));
+ }
+ if (screenHeightDp != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("h%ddp", dtohs(screenHeightDp));
+ }
+ if ((screenLayout&MASK_SCREENSIZE) != SCREENSIZE_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (screenLayout&ResTable_config::MASK_SCREENSIZE) {
+ case ResTable_config::SCREENSIZE_SMALL:
+ res.append("small");
+ break;
+ case ResTable_config::SCREENSIZE_NORMAL:
+ res.append("normal");
+ break;
+ case ResTable_config::SCREENSIZE_LARGE:
+ res.append("large");
+ break;
+ case ResTable_config::SCREENSIZE_XLARGE:
+ res.append("xlarge");
+ break;
+ default:
+ res.appendFormat("screenLayoutSize=%d",
+ dtohs(screenLayout&ResTable_config::MASK_SCREENSIZE));
+ break;
+ }
+ }
+ if ((screenLayout&MASK_SCREENLONG) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (screenLayout&ResTable_config::MASK_SCREENLONG) {
+ case ResTable_config::SCREENLONG_NO:
+ res.append("notlong");
+ break;
+ case ResTable_config::SCREENLONG_YES:
+ res.append("long");
+ break;
+ default:
+ res.appendFormat("screenLayoutLong=%d",
+ dtohs(screenLayout&ResTable_config::MASK_SCREENLONG));
+ break;
+ }
+ }
+ if (orientation != ORIENTATION_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (orientation) {
+ case ResTable_config::ORIENTATION_PORT:
+ res.append("port");
+ break;
+ case ResTable_config::ORIENTATION_LAND:
+ res.append("land");
+ break;
+ case ResTable_config::ORIENTATION_SQUARE:
+ res.append("square");
+ break;
+ default:
+ res.appendFormat("orientation=%d", dtohs(orientation));
+ break;
+ }
+ }
+ if ((uiMode&MASK_UI_MODE_TYPE) != UI_MODE_TYPE_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (uiMode&ResTable_config::MASK_UI_MODE_TYPE) {
+ case ResTable_config::UI_MODE_TYPE_DESK:
+ res.append("desk");
+ break;
+ case ResTable_config::UI_MODE_TYPE_CAR:
+ res.append("car");
+ break;
+ case ResTable_config::UI_MODE_TYPE_TELEVISION:
+ res.append("television");
+ break;
+ case ResTable_config::UI_MODE_TYPE_APPLIANCE:
+ res.append("appliance");
+ break;
+ default:
+ res.appendFormat("uiModeType=%d",
+ dtohs(screenLayout&ResTable_config::MASK_UI_MODE_TYPE));
+ break;
+ }
+ }
+ if ((uiMode&MASK_UI_MODE_NIGHT) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (uiMode&ResTable_config::MASK_UI_MODE_NIGHT) {
+ case ResTable_config::UI_MODE_NIGHT_NO:
+ res.append("notnight");
+ break;
+ case ResTable_config::UI_MODE_NIGHT_YES:
+ res.append("night");
+ break;
+ default:
+ res.appendFormat("uiModeNight=%d",
+ dtohs(uiMode&MASK_UI_MODE_NIGHT));
+ break;
+ }
+ }
+ if (density != DENSITY_DEFAULT) {
+ if (res.size() > 0) res.append("-");
+ switch (density) {
+ case ResTable_config::DENSITY_LOW:
+ res.append("ldpi");
+ break;
+ case ResTable_config::DENSITY_MEDIUM:
+ res.append("mdpi");
+ break;
+ case ResTable_config::DENSITY_TV:
+ res.append("tvdpi");
+ break;
+ case ResTable_config::DENSITY_HIGH:
+ res.append("hdpi");
+ break;
+ case ResTable_config::DENSITY_XHIGH:
+ res.append("xhdpi");
+ break;
+ case ResTable_config::DENSITY_XXHIGH:
+ res.append("xxhdpi");
+ break;
+ case ResTable_config::DENSITY_NONE:
+ res.append("nodpi");
+ break;
+ default:
+ res.appendFormat("density=%d", dtohs(density));
+ break;
+ }
+ }
+ if (touchscreen != TOUCHSCREEN_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (touchscreen) {
+ case ResTable_config::TOUCHSCREEN_NOTOUCH:
+ res.append("notouch");
+ break;
+ case ResTable_config::TOUCHSCREEN_FINGER:
+ res.append("finger");
+ break;
+ case ResTable_config::TOUCHSCREEN_STYLUS:
+ res.append("stylus");
+ break;
+ default:
+ res.appendFormat("touchscreen=%d", dtohs(touchscreen));
+ break;
+ }
+ }
+ if (keyboard != KEYBOARD_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (keyboard) {
+ case ResTable_config::KEYBOARD_NOKEYS:
+ res.append("nokeys");
+ break;
+ case ResTable_config::KEYBOARD_QWERTY:
+ res.append("qwerty");
+ break;
+ case ResTable_config::KEYBOARD_12KEY:
+ res.append("12key");
+ break;
+ default:
+ res.appendFormat("keyboard=%d", dtohs(keyboard));
+ break;
+ }
+ }
+ if ((inputFlags&MASK_KEYSHIDDEN) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (inputFlags&MASK_KEYSHIDDEN) {
+ case ResTable_config::KEYSHIDDEN_NO:
+ res.append("keysexposed");
+ break;
+ case ResTable_config::KEYSHIDDEN_YES:
+ res.append("keyshidden");
+ break;
+ case ResTable_config::KEYSHIDDEN_SOFT:
+ res.append("keyssoft");
+ break;
+ }
+ }
+ if (navigation != NAVIGATION_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (navigation) {
+ case ResTable_config::NAVIGATION_NONAV:
+ res.append("nonav");
+ break;
+ case ResTable_config::NAVIGATION_DPAD:
+ res.append("dpad");
+ break;
+ case ResTable_config::NAVIGATION_TRACKBALL:
+ res.append("trackball");
+ break;
+ case ResTable_config::NAVIGATION_WHEEL:
+ res.append("wheel");
+ break;
+ default:
+ res.appendFormat("navigation=%d", dtohs(navigation));
+ break;
+ }
+ }
+ if ((inputFlags&MASK_NAVHIDDEN) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (inputFlags&MASK_NAVHIDDEN) {
+ case ResTable_config::NAVHIDDEN_NO:
+ res.append("navsexposed");
+ break;
+ case ResTable_config::NAVHIDDEN_YES:
+ res.append("navhidden");
+ break;
+ default:
+ res.appendFormat("inputFlagsNavHidden=%d",
+ dtohs(inputFlags&MASK_NAVHIDDEN));
+ break;
+ }
+ }
+ if (screenSize != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("%dx%d", dtohs(screenWidth), dtohs(screenHeight));
+ }
+ if (version != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("v%d", dtohs(sdkVersion));
+ if (minorVersion != 0) {
+ res.appendFormat(".%d", dtohs(minorVersion));
+ }
+ }
+
+ return res;
+}
+
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+
struct ResTable::Header
{
Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
@@ -3953,43 +4843,9 @@
ResTable_config thisConfig;
thisConfig.copyFromDtoH(thisType->config);
- TABLE_GETENTRY(LOGI("Match entry 0x%x in type 0x%x (sz 0x%x): imsi:%d/%d=%d/%d "
- "lang:%c%c=%c%c cnt:%c%c=%c%c orien:%d=%d touch:%d=%d "
- "density:%d=%d key:%d=%d inp:%d=%d nav:%d=%d w:%d=%d h:%d=%d "
- "swdp:%d=%d wdp:%d=%d hdp:%d=%d\n",
+ TABLE_GETENTRY(LOGI("Match entry 0x%x in type 0x%x (sz 0x%x): %s\n",
entryIndex, typeIndex+1, dtohl(thisType->config.size),
- thisConfig.mcc, thisConfig.mnc,
- config ? config->mcc : 0, config ? config->mnc : 0,
- thisConfig.language[0] ? thisConfig.language[0] : '-',
- thisConfig.language[1] ? thisConfig.language[1] : '-',
- config && config->language[0] ? config->language[0] : '-',
- config && config->language[1] ? config->language[1] : '-',
- thisConfig.country[0] ? thisConfig.country[0] : '-',
- thisConfig.country[1] ? thisConfig.country[1] : '-',
- config && config->country[0] ? config->country[0] : '-',
- config && config->country[1] ? config->country[1] : '-',
- thisConfig.orientation,
- config ? config->orientation : 0,
- thisConfig.touchscreen,
- config ? config->touchscreen : 0,
- thisConfig.density,
- config ? config->density : 0,
- thisConfig.keyboard,
- config ? config->keyboard : 0,
- thisConfig.inputFlags,
- config ? config->inputFlags : 0,
- thisConfig.navigation,
- config ? config->navigation : 0,
- thisConfig.screenWidth,
- config ? config->screenWidth : 0,
- thisConfig.screenHeight,
- config ? config->screenHeight : 0,
- thisConfig.smallestScreenWidthDp,
- config ? config->smallestScreenWidthDp : 0,
- thisConfig.screenWidthDp,
- config ? config->screenWidthDp : 0,
- thisConfig.screenHeightDp,
- config ? config->screenHeightDp : 0));
+ thisConfig.toString().string()));
// Check to make sure this one is valid for the current parameters.
if (config && !thisConfig.match(*config)) {
@@ -4273,26 +5129,8 @@
TABLE_GETENTRY(
ResTable_config thisConfig;
thisConfig.copyFromDtoH(type->config);
- ALOGI("Adding config to type %d: imsi:%d/%d lang:%c%c cnt:%c%c "
- "orien:%d touch:%d density:%d key:%d inp:%d nav:%d w:%d h:%d "
- "swdp:%d wdp:%d hdp:%d\n",
- type->id,
- thisConfig.mcc, thisConfig.mnc,
- thisConfig.language[0] ? thisConfig.language[0] : '-',
- thisConfig.language[1] ? thisConfig.language[1] : '-',
- thisConfig.country[0] ? thisConfig.country[0] : '-',
- thisConfig.country[1] ? thisConfig.country[1] : '-',
- thisConfig.orientation,
- thisConfig.touchscreen,
- thisConfig.density,
- thisConfig.keyboard,
- thisConfig.inputFlags,
- thisConfig.navigation,
- thisConfig.screenWidth,
- thisConfig.screenHeight,
- thisConfig.smallestScreenWidthDp,
- thisConfig.screenWidthDp,
- thisConfig.screenHeightDp));
+ ALOGI("Adding config to type %d: %s\n",
+ type->id, thisConfig.toString().string()));
t->configs.add(type);
} else {
status_t err = validate_chunk(chunk, sizeof(ResChunk_header),
@@ -4622,186 +5460,9 @@
printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type);
continue;
}
- char density[16];
- uint16_t dval = dtohs(type->config.density);
- if (dval == ResTable_config::DENSITY_DEFAULT) {
- strcpy(density, "def");
- } else if (dval == ResTable_config::DENSITY_NONE) {
- strcpy(density, "no");
- } else {
- sprintf(density, "%d", (int)dval);
- }
- printf(" config %d", (int)configIndex);
- if (type->config.mcc != 0) {
- printf(" mcc=%d", dtohs(type->config.mcc));
- }
- if (type->config.mnc != 0) {
- printf(" mnc=%d", dtohs(type->config.mnc));
- }
- if (type->config.locale != 0) {
- printf(" lang=%c%c cnt=%c%c",
- type->config.language[0] ? type->config.language[0] : '-',
- type->config.language[1] ? type->config.language[1] : '-',
- type->config.country[0] ? type->config.country[0] : '-',
- type->config.country[1] ? type->config.country[1] : '-');
- }
- if (type->config.screenLayout != 0) {
- printf(" sz=%d",
- type->config.screenLayout&ResTable_config::MASK_SCREENSIZE);
- switch (type->config.screenLayout&ResTable_config::MASK_SCREENSIZE) {
- case ResTable_config::SCREENSIZE_SMALL:
- printf(" (small)");
- break;
- case ResTable_config::SCREENSIZE_NORMAL:
- printf(" (normal)");
- break;
- case ResTable_config::SCREENSIZE_LARGE:
- printf(" (large)");
- break;
- case ResTable_config::SCREENSIZE_XLARGE:
- printf(" (xlarge)");
- break;
- }
- printf(" lng=%d",
- type->config.screenLayout&ResTable_config::MASK_SCREENLONG);
- switch (type->config.screenLayout&ResTable_config::MASK_SCREENLONG) {
- case ResTable_config::SCREENLONG_NO:
- printf(" (notlong)");
- break;
- case ResTable_config::SCREENLONG_YES:
- printf(" (long)");
- break;
- }
- }
- if (type->config.orientation != 0) {
- printf(" orient=%d", type->config.orientation);
- switch (type->config.orientation) {
- case ResTable_config::ORIENTATION_PORT:
- printf(" (port)");
- break;
- case ResTable_config::ORIENTATION_LAND:
- printf(" (land)");
- break;
- case ResTable_config::ORIENTATION_SQUARE:
- printf(" (square)");
- break;
- }
- }
- if (type->config.uiMode != 0) {
- printf(" type=%d",
- type->config.uiMode&ResTable_config::MASK_UI_MODE_TYPE);
- switch (type->config.uiMode&ResTable_config::MASK_UI_MODE_TYPE) {
- case ResTable_config::UI_MODE_TYPE_NORMAL:
- printf(" (normal)");
- break;
- case ResTable_config::UI_MODE_TYPE_CAR:
- printf(" (car)");
- break;
- }
- printf(" night=%d",
- type->config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT);
- switch (type->config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT) {
- case ResTable_config::UI_MODE_NIGHT_NO:
- printf(" (no)");
- break;
- case ResTable_config::UI_MODE_NIGHT_YES:
- printf(" (yes)");
- break;
- }
- }
- if (dval != 0) {
- printf(" density=%s", density);
- }
- if (type->config.touchscreen != 0) {
- printf(" touch=%d", type->config.touchscreen);
- switch (type->config.touchscreen) {
- case ResTable_config::TOUCHSCREEN_NOTOUCH:
- printf(" (notouch)");
- break;
- case ResTable_config::TOUCHSCREEN_STYLUS:
- printf(" (stylus)");
- break;
- case ResTable_config::TOUCHSCREEN_FINGER:
- printf(" (finger)");
- break;
- }
- }
- if (type->config.inputFlags != 0) {
- printf(" keyhid=%d", type->config.inputFlags&ResTable_config::MASK_KEYSHIDDEN);
- switch (type->config.inputFlags&ResTable_config::MASK_KEYSHIDDEN) {
- case ResTable_config::KEYSHIDDEN_NO:
- printf(" (no)");
- break;
- case ResTable_config::KEYSHIDDEN_YES:
- printf(" (yes)");
- break;
- case ResTable_config::KEYSHIDDEN_SOFT:
- printf(" (soft)");
- break;
- }
- printf(" navhid=%d", type->config.inputFlags&ResTable_config::MASK_NAVHIDDEN);
- switch (type->config.inputFlags&ResTable_config::MASK_NAVHIDDEN) {
- case ResTable_config::NAVHIDDEN_NO:
- printf(" (no)");
- break;
- case ResTable_config::NAVHIDDEN_YES:
- printf(" (yes)");
- break;
- }
- }
- if (type->config.keyboard != 0) {
- printf(" kbd=%d", type->config.keyboard);
- switch (type->config.keyboard) {
- case ResTable_config::KEYBOARD_NOKEYS:
- printf(" (nokeys)");
- break;
- case ResTable_config::KEYBOARD_QWERTY:
- printf(" (qwerty)");
- break;
- case ResTable_config::KEYBOARD_12KEY:
- printf(" (12key)");
- break;
- }
- }
- if (type->config.navigation != 0) {
- printf(" nav=%d", type->config.navigation);
- switch (type->config.navigation) {
- case ResTable_config::NAVIGATION_NONAV:
- printf(" (nonav)");
- break;
- case ResTable_config::NAVIGATION_DPAD:
- printf(" (dpad)");
- break;
- case ResTable_config::NAVIGATION_TRACKBALL:
- printf(" (trackball)");
- break;
- case ResTable_config::NAVIGATION_WHEEL:
- printf(" (wheel)");
- break;
- }
- }
- if (type->config.screenWidth != 0) {
- printf(" w=%d", dtohs(type->config.screenWidth));
- }
- if (type->config.screenHeight != 0) {
- printf(" h=%d", dtohs(type->config.screenHeight));
- }
- if (type->config.smallestScreenWidthDp != 0) {
- printf(" swdp=%d", dtohs(type->config.smallestScreenWidthDp));
- }
- if (type->config.screenWidthDp != 0) {
- printf(" wdp=%d", dtohs(type->config.screenWidthDp));
- }
- if (type->config.screenHeightDp != 0) {
- printf(" hdp=%d", dtohs(type->config.screenHeightDp));
- }
- if (type->config.sdkVersion != 0) {
- printf(" sdk=%d", dtohs(type->config.sdkVersion));
- }
- if (type->config.minorVersion != 0) {
- printf(" mver=%d", dtohs(type->config.minorVersion));
- }
- printf("\n");
+ String8 configStr = type->config.toString();
+ printf(" config %s:\n", configStr.size() > 0
+ ? configStr.string() : "(default)");
size_t entryCount = dtohl(type->entryCount);
uint32_t entriesStart = dtohl(type->entriesStart);
if ((entriesStart&0x3) != 0) {
diff --git a/libs/utils/Threads.cpp b/libs/utils/Threads.cpp
index e343c62..ab207f5 100644
--- a/libs/utils/Threads.cpp
+++ b/libs/utils/Threads.cpp
@@ -34,6 +34,9 @@
# include <pthread.h>
# include <sched.h>
# include <sys/resource.h>
+#ifdef HAVE_ANDROID_OS
+# include <bionic_pthread.h>
+#endif
#elif defined(HAVE_WIN32_THREADS)
# include <windows.h>
# include <stdint.h>
@@ -86,7 +89,7 @@
char * threadName;
// we use this trampoline when we need to set the priority with
- // nice/setpriority.
+ // nice/setpriority, and name with prctl.
static int trampoline(const thread_data_t* t) {
thread_func_t f = t->entryFunction;
void* u = t->userData;
@@ -141,8 +144,13 @@
#ifdef HAVE_ANDROID_OS /* valgrind is rejecting RT-priority create reqs */
if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) {
- // We could avoid the trampoline if there was a way to get to the
- // android_thread_id_t (pid) from pthread_t
+ // Now that the pthread_t has a method to find the associated
+ // android_thread_id_t (pid) from pthread_t, it would be possible to avoid
+ // this trampoline in some cases as the parent could set the properties
+ // for the child. However, there would be a race condition because the
+ // child becomes ready immediately, and it doesn't work for the name.
+ // prctl(PR_SET_NAME) only works for self; prctl(PR_SET_THREAD_NAME) was
+ // proposed but not yet accepted.
thread_data_t* t = new thread_data_t;
t->priority = threadPriority;
t->threadName = threadName ? strdup(threadName) : NULL;
@@ -178,6 +186,13 @@
return 1;
}
+#ifdef HAVE_ANDROID_OS
+static pthread_t android_thread_id_t_to_pthread(android_thread_id_t thread)
+{
+ return (pthread_t) thread;
+}
+#endif
+
android_thread_id_t androidGetThreadId()
{
return (android_thread_id_t)pthread_self();
@@ -909,6 +924,23 @@
return mStatus;
}
+#ifdef HAVE_ANDROID_OS
+pid_t Thread::getTid() const
+{
+ // mTid is not defined until the child initializes it, and the caller may need it earlier
+ Mutex::Autolock _l(mLock);
+ pid_t tid;
+ if (mRunning) {
+ pthread_t pthread = android_thread_id_t_to_pthread(mThread);
+ tid = __pthread_gettid(pthread);
+ } else {
+ ALOGW("Thread (this=%p): getTid() is undefined before run()", this);
+ tid = -1;
+ }
+ return tid;
+}
+#endif
+
bool Thread::exitPending() const
{
Mutex::Autolock _l(mLock);
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index b640e9a..1c13fff 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -314,10 +314,8 @@
private final String mExternalStoragePath;
- // WARNING: Bulk inserts sounded like a great idea and gave us a good performance improvement,
- // but unfortunately it also introduced a number of bugs. All the known bugs were fixed,
- // but we need more testing before enabling.
- private static final boolean ENABLE_BULK_INSERTS = false;
+ /** whether to use bulk inserts or individual inserts for each item */
+ private static final boolean ENABLE_BULK_INSERTS = true;
// used when scanning the image database so we know whether we have to prune
// old thumbnail files
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index ee96a95..23cc0e2 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -28,7 +28,11 @@
libcamera_client \
libmtp \
libusbhost \
- libexif
+ libexif \
+ libstagefright_amrnb_common \
+
+LOCAL_STATIC_LIBRARIES := \
+ libstagefright_amrnbenc
LOCAL_C_INCLUDES += \
external/jhead \
diff --git a/media/libeffects/preprocessing/Android.mk b/media/libeffects/preprocessing/Android.mk
index 77d40b6..7f7c7e1 100755
--- a/media/libeffects/preprocessing/Android.mk
+++ b/media/libeffects/preprocessing/Android.mk
@@ -13,7 +13,7 @@
LOCAL_C_INCLUDES += \
external/webrtc/src \
external/webrtc/src/modules/interface \
- external/webrtc/src/modules/audio_processing/main/interface \
+ external/webrtc/src/modules/audio_processing/interface \
system/media/audio_effects/include
LOCAL_C_INCLUDES += $(call include-path-for, speex)
diff --git a/media/libeffects/preprocessing/PreProcessing.cpp b/media/libeffects/preprocessing/PreProcessing.cpp
index e988e06..9fd6764 100755
--- a/media/libeffects/preprocessing/PreProcessing.cpp
+++ b/media/libeffects/preprocessing/PreProcessing.cpp
@@ -24,8 +24,8 @@
#include <audio_effects/effect_aec.h>
#include <audio_effects/effect_agc.h>
#include <audio_effects/effect_ns.h>
-#include "modules/interface/module_common_types.h"
-#include "modules/audio_processing/main/interface/audio_processing.h"
+#include <module_common_types.h>
+#include <audio_processing.h>
#include "speex/speex_resampler.h"
@@ -220,8 +220,8 @@
// Automatic Gain Control (AGC)
//------------------------------------------------------------------------------
-static const int kAgcDefaultTargetLevel = 0;
-static const int kAgcDefaultCompGain = 90;
+static const int kAgcDefaultTargetLevel = 3;
+static const int kAgcDefaultCompGain = 9;
static const bool kAgcDefaultLimiter = true;
int AgcInit (preproc_effect_t *effect)
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index 124032b..df5017b 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -35,7 +35,8 @@
sp<AudioSystem::AudioFlingerClient> AudioSystem::gAudioFlingerClient;
audio_error_callback AudioSystem::gAudioErrorCallback = NULL;
// Cached values
-DefaultKeyedVector<int, audio_io_handle_t> AudioSystem::gStreamOutputMap(0);
+
+DefaultKeyedVector<audio_stream_type_t, audio_io_handle_t> AudioSystem::gStreamOutputMap(0);
DefaultKeyedVector<audio_io_handle_t, AudioSystem::OutputDescriptor *> AudioSystem::gOutputs(0);
// Cached values for recording queries, all protected by gLock
@@ -404,7 +405,7 @@
void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, int ioHandle, void *param2) {
ALOGV("ioConfigChanged() event %d", event);
OutputDescriptor *desc;
- uint32_t stream;
+ audio_stream_type_t stream;
if (ioHandle == 0) return;
@@ -413,7 +414,7 @@
switch (event) {
case STREAM_CONFIG_CHANGED:
if (param2 == 0) break;
- stream = *(uint32_t *)param2;
+ stream = *(audio_stream_type_t *)param2;
ALOGV("ioConfigChanged() STREAM_CONFIG_CHANGED stream %d, output %d", stream, ioHandle);
if (gStreamOutputMap.indexOfKey(stream) >= 0) {
gStreamOutputMap.replaceValueFor(stream, ioHandle);
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index d2f5f71..27c7e03 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -59,9 +59,10 @@
: BpInterface<IOMX>(impl) {
}
- virtual bool livesLocally(pid_t pid) {
+ virtual bool livesLocally(node_id node, pid_t pid) {
Parcel data, reply;
data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ data.writeIntPtr((intptr_t)node);
data.writeInt32(pid);
remote()->transact(LIVES_LOCALLY, data, &reply);
@@ -417,7 +418,9 @@
case LIVES_LOCALLY:
{
CHECK_INTERFACE(IOMX, data, reply);
- reply->writeInt32(livesLocally((pid_t)data.readInt32()));
+ node_id node = (void *)data.readIntPtr();
+ pid_t pid = (pid_t)data.readInt32();
+ reply->writeInt32(livesLocally(node, pid));
return OK;
}
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index a452ad5..03e8a06 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -78,8 +78,6 @@
LOCAL_STATIC_LIBRARIES := \
libstagefright_color_conversion \
- libstagefright_amrnbenc \
- libstagefright_amrwbenc \
libstagefright_avcenc \
libstagefright_m4vh263enc \
libstagefright_matroska \
@@ -141,7 +139,6 @@
################################################################################
LOCAL_SHARED_LIBRARIES += \
- libstagefright_amrnb_common \
libstagefright_enc_common \
libstagefright_avc_common \
libstagefright_foundation \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 8480b6d..8073af8 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -30,7 +30,7 @@
#include "include/MPEG2TSExtractor.h"
#include "include/WVMExtractor.h"
-#include "timedtext/TimedTextPlayer.h"
+#include "timedtext/TimedTextDriver.h"
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
@@ -192,7 +192,7 @@
mVideoBuffer(NULL),
mDecryptHandle(NULL),
mLastVideoTimeUs(-1),
- mTextPlayer(NULL) {
+ mTextDriver(NULL) {
CHECK_EQ(mClient.connect(), (status_t)OK);
DataSource::RegisterDefaultSniffers();
@@ -530,9 +530,9 @@
delete mAudioPlayer;
mAudioPlayer = NULL;
- if (mTextPlayer != NULL) {
- delete mTextPlayer;
- mTextPlayer = NULL;
+ if (mTextDriver != NULL) {
+ delete mTextDriver;
+ mTextDriver = NULL;
}
mVideoRenderer.clear();
@@ -1118,7 +1118,7 @@
}
if (mFlags & TEXTPLAYER_STARTED) {
- mTextPlayer->pause();
+ mTextDriver->pause();
modifyFlags(TEXT_RUNNING, CLEAR);
}
@@ -1272,9 +1272,9 @@
}
status_t AwesomePlayer::setTimedTextTrackIndex(int32_t index) {
- if (mTextPlayer != NULL) {
+ if (mTextDriver != NULL) {
if (index >= 0) { // to turn on a text track
- status_t err = mTextPlayer->setTimedTextTrackIndex(index);
+ status_t err = mTextDriver->setTimedTextTrackIndex(index);
if (err != OK) {
return err;
}
@@ -1290,7 +1290,7 @@
modifyFlags(TEXTPLAYER_STARTED, CLEAR);
}
- return mTextPlayer->setTimedTextTrackIndex(index);
+ return mTextDriver->setTimedTextTrackIndex(index);
}
} else {
return INVALID_OPERATION;
@@ -1319,7 +1319,7 @@
seekAudioIfNecessary_l();
if (mFlags & TEXTPLAYER_STARTED) {
- mTextPlayer->seekTo(mSeekTimeUs);
+ mTextDriver->seekToAsync(mSeekTimeUs);
}
if (!(mFlags & PLAYING)) {
@@ -1364,11 +1364,11 @@
Mutex::Autolock autoLock(mTimedTextLock);
CHECK(source != NULL);
- if (mTextPlayer == NULL) {
- mTextPlayer = new TimedTextPlayer(this, mListener, &mQueue);
+ if (mTextDriver == NULL) {
+ mTextDriver = new TimedTextDriver(mListener);
}
- mTextPlayer->addTextSource(source);
+ mTextDriver->addInBandTextSource(source);
}
status_t AwesomePlayer::initAudioDecoder() {
@@ -1695,7 +1695,7 @@
}
if ((mFlags & TEXTPLAYER_STARTED) && !(mFlags & (TEXT_RUNNING | SEEK_PREVIEW))) {
- mTextPlayer->resume();
+ mTextDriver->resume();
modifyFlags(TEXT_RUNNING, SET);
}
@@ -2241,11 +2241,11 @@
case KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE:
{
Mutex::Autolock autoLock(mTimedTextLock);
- if (mTextPlayer == NULL) {
- mTextPlayer = new TimedTextPlayer(this, mListener, &mQueue);
+ if (mTextDriver == NULL) {
+ mTextDriver = new TimedTextDriver(mListener);
}
- return mTextPlayer->setParameter(key, request);
+ return mTextDriver->addOutOfBandTextSource(request);
}
case KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS:
{
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index bc88015..6c95d4e 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -20,7 +20,6 @@
#include "include/MPEG4Extractor.h"
#include "include/SampleTable.h"
#include "include/ESDS.h"
-#include "timedtext/TimedTextPlayer.h"
#include <arpa/inet.h>
@@ -2430,4 +2429,3 @@
}
} // namespace android
-
diff --git a/media/libstagefright/OMXClient.cpp b/media/libstagefright/OMXClient.cpp
index 391add5..7a805aa 100644
--- a/media/libstagefright/OMXClient.cpp
+++ b/media/libstagefright/OMXClient.cpp
@@ -34,7 +34,7 @@
virtual IBinder *onAsBinder() { return NULL; }
- virtual bool livesLocally(pid_t pid);
+ virtual bool livesLocally(node_id node, pid_t pid);
virtual status_t listNodes(List<ComponentInfo> *list);
@@ -155,8 +155,8 @@
return isLocalNode_l(node) ? mLocalOMX : mRemoteOMX;
}
-bool MuxOMX::livesLocally(pid_t pid) {
- return true;
+bool MuxOMX::livesLocally(node_id node, pid_t pid) {
+ return getOMX(node)->livesLocally(node, pid);
}
status_t MuxOMX::listNodes(List<ComponentInfo> *list) {
@@ -326,7 +326,7 @@
mOMX = service->getOMX();
CHECK(mOMX.get() != NULL);
- if (!mOMX->livesLocally(getpid())) {
+ if (!mOMX->livesLocally(NULL /* node */, getpid())) {
ALOGI("Using client-side OMX mux.");
mOMX = new MuxOMX(mOMX);
}
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 7597f64..af4aa79 100755
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -18,8 +18,6 @@
#define LOG_TAG "OMXCodec"
#include <utils/Log.h>
-#include "include/AMRNBEncoder.h"
-#include "include/AMRWBEncoder.h"
#include "include/AVCEncoder.h"
#include "include/M4vH263Encoder.h"
@@ -70,8 +68,6 @@
#define FACTORY_REF(name) { #name, Make##name },
-FACTORY_CREATE_ENCODER(AMRNBEncoder)
-FACTORY_CREATE_ENCODER(AMRWBEncoder)
FACTORY_CREATE_ENCODER(AVCEncoder)
FACTORY_CREATE_ENCODER(M4vH263Encoder)
@@ -84,8 +80,6 @@
};
static const FactoryInfo kFactoryInfo[] = {
- FACTORY_REF(AMRNBEncoder)
- FACTORY_REF(AMRWBEncoder)
FACTORY_REF(AVCEncoder)
FACTORY_REF(M4vH263Encoder)
};
@@ -146,9 +140,9 @@
static const CodecInfo kEncoderInfo[] = {
{ MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.TI.AMR.encode" },
- { MEDIA_MIMETYPE_AUDIO_AMR_NB, "AMRNBEncoder" },
+ { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.google.amrnb.encoder" },
{ MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.encode" },
- { MEDIA_MIMETYPE_AUDIO_AMR_WB, "AMRWBEncoder" },
+ { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.google.amrwb.encoder" },
{ MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.encode" },
{ MEDIA_MIMETYPE_AUDIO_AAC, "OMX.google.aac.encoder" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.DUCATI1.VIDEO.MPEG4E" },
@@ -1479,7 +1473,7 @@
const sp<MediaSource> &source,
const sp<ANativeWindow> &nativeWindow)
: mOMX(omx),
- mOMXLivesLocally(omx->livesLocally(getpid())),
+ mOMXLivesLocally(omx->livesLocally(node, getpid())),
mNode(node),
mQuirks(quirks),
mFlags(flags),
diff --git a/media/libstagefright/codecs/amrnb/enc/Android.mk b/media/libstagefright/codecs/amrnb/enc/Android.mk
index b6aed81..94e8726 100644
--- a/media/libstagefright/codecs/amrnb/enc/Android.mk
+++ b/media/libstagefright/codecs/amrnb/enc/Android.mk
@@ -74,3 +74,30 @@
LOCAL_MODULE := libstagefright_amrnbenc
include $(BUILD_STATIC_LIBRARY)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ SoftAMRNBEncoder.cpp
+
+LOCAL_C_INCLUDES := \
+ frameworks/base/media/libstagefright/include \
+ frameworks/base/include/media/stagefright/openmax \
+ $(LOCAL_PATH)/src \
+ $(LOCAL_PATH)/include \
+ $(LOCAL_PATH)/../common/include \
+ $(LOCAL_PATH)/../common
+
+LOCAL_STATIC_LIBRARIES := \
+ libstagefright_amrnbenc
+
+LOCAL_SHARED_LIBRARIES := \
+ libstagefright_omx libstagefright_foundation libutils \
+ libstagefright_amrnb_common
+
+LOCAL_MODULE := libstagefright_soft_amrnbenc
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp
new file mode 100644
index 0000000..07f8b4f
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoftAMRNBEncoder"
+#include <utils/Log.h>
+
+#include "SoftAMRNBEncoder.h"
+
+#include "gsmamr_enc.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+namespace android {
+
+static const int32_t kSampleRate = 8000;
+
+template<class T>
+static void InitOMXParams(T *params) {
+ params->nSize = sizeof(T);
+ params->nVersion.s.nVersionMajor = 1;
+ params->nVersion.s.nVersionMinor = 0;
+ params->nVersion.s.nRevision = 0;
+ params->nVersion.s.nStep = 0;
+}
+
+SoftAMRNBEncoder::SoftAMRNBEncoder(
+ const char *name,
+ const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData,
+ OMX_COMPONENTTYPE **component)
+ : SimpleSoftOMXComponent(name, callbacks, appData, component),
+ mEncState(NULL),
+ mSidState(NULL),
+ mBitRate(0),
+ mMode(MR475),
+ mInputSize(0),
+ mInputTimeUs(-1ll),
+ mSawInputEOS(false),
+ mSignalledError(false) {
+ initPorts();
+ CHECK_EQ(initEncoder(), (status_t)OK);
+}
+
+SoftAMRNBEncoder::~SoftAMRNBEncoder() {
+ if (mEncState != NULL) {
+ AMREncodeExit(&mEncState, &mSidState);
+ mEncState = mSidState = NULL;
+ }
+}
+
+void SoftAMRNBEncoder::initPorts() {
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ InitOMXParams(&def);
+
+ def.nPortIndex = 0;
+ def.eDir = OMX_DirInput;
+ def.nBufferCountMin = kNumBuffers;
+ def.nBufferCountActual = def.nBufferCountMin;
+ def.nBufferSize = kNumSamplesPerFrame * sizeof(int16_t);
+ def.bEnabled = OMX_TRUE;
+ def.bPopulated = OMX_FALSE;
+ def.eDomain = OMX_PortDomainAudio;
+ def.bBuffersContiguous = OMX_FALSE;
+ def.nBufferAlignment = 1;
+
+ def.format.audio.cMIMEType = const_cast<char *>("audio/raw");
+ def.format.audio.pNativeRender = NULL;
+ def.format.audio.bFlagErrorConcealment = OMX_FALSE;
+ def.format.audio.eEncoding = OMX_AUDIO_CodingPCM;
+
+ addPort(def);
+
+ def.nPortIndex = 1;
+ def.eDir = OMX_DirOutput;
+ def.nBufferCountMin = kNumBuffers;
+ def.nBufferCountActual = def.nBufferCountMin;
+ def.nBufferSize = 8192;
+ def.bEnabled = OMX_TRUE;
+ def.bPopulated = OMX_FALSE;
+ def.eDomain = OMX_PortDomainAudio;
+ def.bBuffersContiguous = OMX_FALSE;
+ def.nBufferAlignment = 2;
+
+ def.format.audio.cMIMEType = const_cast<char *>("audio/3gpp");
+ def.format.audio.pNativeRender = NULL;
+ def.format.audio.bFlagErrorConcealment = OMX_FALSE;
+ def.format.audio.eEncoding = OMX_AUDIO_CodingAMR;
+
+ addPort(def);
+}
+
+status_t SoftAMRNBEncoder::initEncoder() {
+ if (AMREncodeInit(&mEncState, &mSidState, false /* dtx_enable */) != 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+OMX_ERRORTYPE SoftAMRNBEncoder::internalGetParameter(
+ OMX_INDEXTYPE index, OMX_PTR params) {
+ switch (index) {
+ case OMX_IndexParamAudioPortFormat:
+ {
+ OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams =
+ (OMX_AUDIO_PARAM_PORTFORMATTYPE *)params;
+
+ if (formatParams->nPortIndex > 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (formatParams->nIndex > 0) {
+ return OMX_ErrorNoMore;
+ }
+
+ formatParams->eEncoding =
+ (formatParams->nPortIndex == 0)
+ ? OMX_AUDIO_CodingPCM : OMX_AUDIO_CodingAMR;
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioAmr:
+ {
+ OMX_AUDIO_PARAM_AMRTYPE *amrParams =
+ (OMX_AUDIO_PARAM_AMRTYPE *)params;
+
+ if (amrParams->nPortIndex != 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ amrParams->nChannels = 1;
+ amrParams->nBitRate = mBitRate;
+ amrParams->eAMRBandMode = (OMX_AUDIO_AMRBANDMODETYPE)(mMode + 1);
+ amrParams->eAMRDTXMode = OMX_AUDIO_AMRDTXModeOff;
+ amrParams->eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioPcm:
+ {
+ OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams =
+ (OMX_AUDIO_PARAM_PCMMODETYPE *)params;
+
+ if (pcmParams->nPortIndex != 0) {
+ return OMX_ErrorUndefined;
+ }
+
+ pcmParams->eNumData = OMX_NumericalDataSigned;
+ pcmParams->eEndian = OMX_EndianBig;
+ pcmParams->bInterleaved = OMX_TRUE;
+ pcmParams->nBitPerSample = 16;
+ pcmParams->ePCMMode = OMX_AUDIO_PCMModeLinear;
+ pcmParams->eChannelMapping[0] = OMX_AUDIO_ChannelCF;
+
+ pcmParams->nChannels = 1;
+ pcmParams->nSamplingRate = kSampleRate;
+
+ return OMX_ErrorNone;
+ }
+
+ default:
+ return SimpleSoftOMXComponent::internalGetParameter(index, params);
+ }
+}
+
+OMX_ERRORTYPE SoftAMRNBEncoder::internalSetParameter(
+ OMX_INDEXTYPE index, const OMX_PTR params) {
+ switch (index) {
+ case OMX_IndexParamStandardComponentRole:
+ {
+ const OMX_PARAM_COMPONENTROLETYPE *roleParams =
+ (const OMX_PARAM_COMPONENTROLETYPE *)params;
+
+ if (strncmp((const char *)roleParams->cRole,
+ "audio_encoder.amrnb",
+ OMX_MAX_STRINGNAME_SIZE - 1)) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioPortFormat:
+ {
+ const OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams =
+ (const OMX_AUDIO_PARAM_PORTFORMATTYPE *)params;
+
+ if (formatParams->nPortIndex > 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (formatParams->nIndex > 0) {
+ return OMX_ErrorNoMore;
+ }
+
+ if ((formatParams->nPortIndex == 0
+ && formatParams->eEncoding != OMX_AUDIO_CodingPCM)
+ || (formatParams->nPortIndex == 1
+ && formatParams->eEncoding != OMX_AUDIO_CodingAMR)) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioAmr:
+ {
+ OMX_AUDIO_PARAM_AMRTYPE *amrParams =
+ (OMX_AUDIO_PARAM_AMRTYPE *)params;
+
+ if (amrParams->nPortIndex != 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (amrParams->nChannels != 1
+ || amrParams->eAMRDTXMode != OMX_AUDIO_AMRDTXModeOff
+ || amrParams->eAMRFrameFormat
+ != OMX_AUDIO_AMRFrameFormatFSF
+ || amrParams->eAMRBandMode < OMX_AUDIO_AMRBandModeNB0
+ || amrParams->eAMRBandMode > OMX_AUDIO_AMRBandModeNB7) {
+ return OMX_ErrorUndefined;
+ }
+
+ mBitRate = amrParams->nBitRate;
+ mMode = amrParams->eAMRBandMode - 1;
+
+ amrParams->eAMRDTXMode = OMX_AUDIO_AMRDTXModeOff;
+ amrParams->eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioPcm:
+ {
+ OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams =
+ (OMX_AUDIO_PARAM_PCMMODETYPE *)params;
+
+ if (pcmParams->nPortIndex != 0) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (pcmParams->nChannels != 1
+ || pcmParams->nSamplingRate != kSampleRate) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+
+ default:
+ return SimpleSoftOMXComponent::internalSetParameter(index, params);
+ }
+}
+
+void SoftAMRNBEncoder::onQueueFilled(OMX_U32 portIndex) {
+ if (mSignalledError) {
+ return;
+ }
+
+ List<BufferInfo *> &inQueue = getPortQueue(0);
+ List<BufferInfo *> &outQueue = getPortQueue(1);
+
+ size_t numBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t);
+
+ for (;;) {
+ // We do the following until we run out of buffers.
+
+ while (mInputSize < numBytesPerInputFrame) {
+ // As long as there's still input data to be read we
+ // will drain "kNumSamplesPerFrame" samples
+ // into the "mInputFrame" buffer and then encode those
+ // as a unit into an output buffer.
+
+ if (mSawInputEOS || inQueue.empty()) {
+ return;
+ }
+
+ BufferInfo *inInfo = *inQueue.begin();
+ OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader;
+
+ const void *inData = inHeader->pBuffer + inHeader->nOffset;
+
+ size_t copy = numBytesPerInputFrame - mInputSize;
+ if (copy > inHeader->nFilledLen) {
+ copy = inHeader->nFilledLen;
+ }
+
+ if (mInputSize == 0) {
+ mInputTimeUs = inHeader->nTimeStamp;
+ }
+
+ memcpy((uint8_t *)mInputFrame + mInputSize, inData, copy);
+ mInputSize += copy;
+
+ inHeader->nOffset += copy;
+ inHeader->nFilledLen -= copy;
+
+ // "Time" on the input buffer has in effect advanced by the
+ // number of audio frames we just advanced nOffset by.
+ inHeader->nTimeStamp +=
+ (copy * 1000000ll / kSampleRate) / sizeof(int16_t);
+
+ if (inHeader->nFilledLen == 0) {
+ if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
+ ALOGV("saw input EOS");
+ mSawInputEOS = true;
+
+ // Pad any remaining data with zeroes.
+ memset((uint8_t *)mInputFrame + mInputSize,
+ 0,
+ numBytesPerInputFrame - mInputSize);
+
+ mInputSize = numBytesPerInputFrame;
+ }
+
+ inQueue.erase(inQueue.begin());
+ inInfo->mOwnedByUs = false;
+ notifyEmptyBufferDone(inHeader);
+
+ inData = NULL;
+ inHeader = NULL;
+ inInfo = NULL;
+ }
+ }
+
+ // At this point we have all the input data necessary to encode
+ // a single frame, all we need is an output buffer to store the result
+ // in.
+
+ if (outQueue.empty()) {
+ return;
+ }
+
+ BufferInfo *outInfo = *outQueue.begin();
+ OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader;
+
+ uint8_t *outPtr = outHeader->pBuffer + outHeader->nOffset;
+ size_t outAvailable = outHeader->nAllocLen - outHeader->nOffset;
+
+ Frame_Type_3GPP frameType;
+ int res = AMREncode(
+ mEncState, mSidState, (Mode)mMode,
+ mInputFrame, outPtr, &frameType, AMR_TX_WMF);
+
+ CHECK_GE(res, 0);
+ CHECK_LE((size_t)res, outAvailable);
+
+ // Convert header byte from WMF to IETF format.
+ outPtr[0] = ((outPtr[0] << 3) | 4) & 0x7c;
+
+ outHeader->nFilledLen = res;
+ outHeader->nFlags = OMX_BUFFERFLAG_ENDOFFRAME;
+
+ if (mSawInputEOS) {
+ // We also tag this output buffer with EOS if it corresponds
+ // to the final input buffer.
+ outHeader->nFlags = OMX_BUFFERFLAG_EOS;
+ }
+
+ outHeader->nTimeStamp = mInputTimeUs;
+
+#if 0
+ ALOGI("sending %d bytes of data (time = %lld us, flags = 0x%08lx)",
+ nOutputBytes, mInputTimeUs, outHeader->nFlags);
+
+ hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen);
+#endif
+
+ outQueue.erase(outQueue.begin());
+ outInfo->mOwnedByUs = false;
+ notifyFillBufferDone(outHeader);
+
+ outHeader = NULL;
+ outInfo = NULL;
+
+ mInputSize = 0;
+ }
+}
+
+} // namespace android
+
+android::SoftOMXComponent *createSoftOMXComponent(
+ const char *name, const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData, OMX_COMPONENTTYPE **component) {
+ return new android::SoftAMRNBEncoder(name, callbacks, appData, component);
+}
diff --git a/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.h b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.h
new file mode 100644
index 0000000..50178c4
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef SOFT_AMRNB_ENCODER_H_
+
+#define SOFT_AMRNB_ENCODER_H_
+
+#include "SimpleSoftOMXComponent.h"
+
+namespace android {
+
+struct SoftAMRNBEncoder : public SimpleSoftOMXComponent {
+ SoftAMRNBEncoder(
+ const char *name,
+ const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData,
+ OMX_COMPONENTTYPE **component);
+
+protected:
+ virtual ~SoftAMRNBEncoder();
+
+ virtual OMX_ERRORTYPE internalGetParameter(
+ OMX_INDEXTYPE index, OMX_PTR params);
+
+ virtual OMX_ERRORTYPE internalSetParameter(
+ OMX_INDEXTYPE index, const OMX_PTR params);
+
+ virtual void onQueueFilled(OMX_U32 portIndex);
+
+private:
+ enum {
+ kNumBuffers = 4,
+ kNumSamplesPerFrame = 160,
+ };
+
+ void *mEncState;
+ void *mSidState;
+
+ OMX_U32 mBitRate;
+ int mMode;
+
+ size_t mInputSize;
+ int16_t mInputFrame[kNumSamplesPerFrame];
+ int64_t mInputTimeUs;
+
+ bool mSawInputEOS;
+ bool mSignalledError;
+
+ void initPorts();
+ status_t initEncoder();
+
+ status_t setAudioParams();
+
+ DISALLOW_EVIL_CONSTRUCTORS(SoftAMRNBEncoder);
+};
+
+} // namespace android
+
+#endif // SOFT_AMRNB_ENCODER_H_
diff --git a/media/libstagefright/codecs/amrwbenc/Android.mk b/media/libstagefright/codecs/amrwbenc/Android.mk
index ae43870..6ce6171 100644
--- a/media/libstagefright/codecs/amrwbenc/Android.mk
+++ b/media/libstagefright/codecs/amrwbenc/Android.mk
@@ -117,4 +117,26 @@
include $(BUILD_STATIC_LIBRARY)
+################################################################################
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ SoftAMRWBEncoder.cpp
+
+LOCAL_C_INCLUDES := \
+ frameworks/base/media/libstagefright/include \
+ frameworks/base/include/media/stagefright/openmax \
+ frameworks/base/media/libstagefright/codecs/common/include \
+
+LOCAL_STATIC_LIBRARIES := \
+ libstagefright_amrwbenc
+
+LOCAL_SHARED_LIBRARIES := \
+ libstagefright_omx libstagefright_foundation libutils \
+ libstagefright_enc_common
+
+LOCAL_MODULE := libstagefright_soft_amrwbenc
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.cpp b/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.cpp
new file mode 100644
index 0000000..9ccb49c
--- /dev/null
+++ b/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.cpp
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoftAMRWBEncoder"
+#include <utils/Log.h>
+
+#include "SoftAMRWBEncoder.h"
+
+#include "cmnMemory.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+namespace android {
+
+static const int32_t kSampleRate = 16000;
+
+template<class T>
+static void InitOMXParams(T *params) {
+ params->nSize = sizeof(T);
+ params->nVersion.s.nVersionMajor = 1;
+ params->nVersion.s.nVersionMinor = 0;
+ params->nVersion.s.nRevision = 0;
+ params->nVersion.s.nStep = 0;
+}
+
+SoftAMRWBEncoder::SoftAMRWBEncoder(
+ const char *name,
+ const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData,
+ OMX_COMPONENTTYPE **component)
+ : SimpleSoftOMXComponent(name, callbacks, appData, component),
+ mEncoderHandle(NULL),
+ mApiHandle(NULL),
+ mMemOperator(NULL),
+ mBitRate(0),
+ mMode(VOAMRWB_MD66),
+ mInputSize(0),
+ mInputTimeUs(-1ll),
+ mSawInputEOS(false),
+ mSignalledError(false) {
+ initPorts();
+ CHECK_EQ(initEncoder(), (status_t)OK);
+}
+
+SoftAMRWBEncoder::~SoftAMRWBEncoder() {
+ if (mEncoderHandle != NULL) {
+ CHECK_EQ(VO_ERR_NONE, mApiHandle->Uninit(mEncoderHandle));
+ mEncoderHandle = NULL;
+ }
+
+ delete mApiHandle;
+ mApiHandle = NULL;
+
+ delete mMemOperator;
+ mMemOperator = NULL;
+}
+
+void SoftAMRWBEncoder::initPorts() {
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ InitOMXParams(&def);
+
+ def.nPortIndex = 0;
+ def.eDir = OMX_DirInput;
+ def.nBufferCountMin = kNumBuffers;
+ def.nBufferCountActual = def.nBufferCountMin;
+ def.nBufferSize = kNumSamplesPerFrame * sizeof(int16_t);
+ def.bEnabled = OMX_TRUE;
+ def.bPopulated = OMX_FALSE;
+ def.eDomain = OMX_PortDomainAudio;
+ def.bBuffersContiguous = OMX_FALSE;
+ def.nBufferAlignment = 1;
+
+ def.format.audio.cMIMEType = const_cast<char *>("audio/raw");
+ def.format.audio.pNativeRender = NULL;
+ def.format.audio.bFlagErrorConcealment = OMX_FALSE;
+ def.format.audio.eEncoding = OMX_AUDIO_CodingPCM;
+
+ addPort(def);
+
+ def.nPortIndex = 1;
+ def.eDir = OMX_DirOutput;
+ def.nBufferCountMin = kNumBuffers;
+ def.nBufferCountActual = def.nBufferCountMin;
+ def.nBufferSize = 8192;
+ def.bEnabled = OMX_TRUE;
+ def.bPopulated = OMX_FALSE;
+ def.eDomain = OMX_PortDomainAudio;
+ def.bBuffersContiguous = OMX_FALSE;
+ def.nBufferAlignment = 2;
+
+ def.format.audio.cMIMEType = const_cast<char *>("audio/amr-wb");
+ def.format.audio.pNativeRender = NULL;
+ def.format.audio.bFlagErrorConcealment = OMX_FALSE;
+ def.format.audio.eEncoding = OMX_AUDIO_CodingAMR;
+
+ addPort(def);
+}
+
+status_t SoftAMRWBEncoder::initEncoder() {
+ mApiHandle = new VO_AUDIO_CODECAPI;
+
+ if (VO_ERR_NONE != voGetAMRWBEncAPI(mApiHandle)) {
+ ALOGE("Failed to get api handle");
+ return UNKNOWN_ERROR;
+ }
+
+ mMemOperator = new VO_MEM_OPERATOR;
+ mMemOperator->Alloc = cmnMemAlloc;
+ mMemOperator->Copy = cmnMemCopy;
+ mMemOperator->Free = cmnMemFree;
+ mMemOperator->Set = cmnMemSet;
+ mMemOperator->Check = cmnMemCheck;
+
+ VO_CODEC_INIT_USERDATA userData;
+ memset(&userData, 0, sizeof(userData));
+ userData.memflag = VO_IMF_USERMEMOPERATOR;
+ userData.memData = (VO_PTR) mMemOperator;
+
+ if (VO_ERR_NONE != mApiHandle->Init(
+ &mEncoderHandle, VO_AUDIO_CodingAMRWB, &userData)) {
+ ALOGE("Failed to init AMRWB encoder");
+ return UNKNOWN_ERROR;
+ }
+
+ VOAMRWBFRAMETYPE type = VOAMRWB_RFC3267;
+ if (VO_ERR_NONE != mApiHandle->SetParam(
+ mEncoderHandle, VO_PID_AMRWB_FRAMETYPE, &type)) {
+ ALOGE("Failed to set AMRWB encoder frame type to %d", type);
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+OMX_ERRORTYPE SoftAMRWBEncoder::internalGetParameter(
+ OMX_INDEXTYPE index, OMX_PTR params) {
+ switch (index) {
+ case OMX_IndexParamAudioPortFormat:
+ {
+ OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams =
+ (OMX_AUDIO_PARAM_PORTFORMATTYPE *)params;
+
+ if (formatParams->nPortIndex > 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (formatParams->nIndex > 0) {
+ return OMX_ErrorNoMore;
+ }
+
+ formatParams->eEncoding =
+ (formatParams->nPortIndex == 0)
+ ? OMX_AUDIO_CodingPCM : OMX_AUDIO_CodingAMR;
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioAmr:
+ {
+ OMX_AUDIO_PARAM_AMRTYPE *amrParams =
+ (OMX_AUDIO_PARAM_AMRTYPE *)params;
+
+ if (amrParams->nPortIndex != 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ amrParams->nChannels = 1;
+ amrParams->nBitRate = mBitRate;
+
+ amrParams->eAMRBandMode =
+ (OMX_AUDIO_AMRBANDMODETYPE)(mMode + OMX_AUDIO_AMRBandModeWB0);
+
+ amrParams->eAMRDTXMode = OMX_AUDIO_AMRDTXModeOff;
+ amrParams->eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioPcm:
+ {
+ OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams =
+ (OMX_AUDIO_PARAM_PCMMODETYPE *)params;
+
+ if (pcmParams->nPortIndex != 0) {
+ return OMX_ErrorUndefined;
+ }
+
+ pcmParams->eNumData = OMX_NumericalDataSigned;
+ pcmParams->eEndian = OMX_EndianBig;
+ pcmParams->bInterleaved = OMX_TRUE;
+ pcmParams->nBitPerSample = 16;
+ pcmParams->ePCMMode = OMX_AUDIO_PCMModeLinear;
+ pcmParams->eChannelMapping[0] = OMX_AUDIO_ChannelCF;
+
+ pcmParams->nChannels = 1;
+ pcmParams->nSamplingRate = kSampleRate;
+
+ return OMX_ErrorNone;
+ }
+
+ default:
+ return SimpleSoftOMXComponent::internalGetParameter(index, params);
+ }
+}
+
+OMX_ERRORTYPE SoftAMRWBEncoder::internalSetParameter(
+ OMX_INDEXTYPE index, const OMX_PTR params) {
+ switch (index) {
+ case OMX_IndexParamStandardComponentRole:
+ {
+ const OMX_PARAM_COMPONENTROLETYPE *roleParams =
+ (const OMX_PARAM_COMPONENTROLETYPE *)params;
+
+ if (strncmp((const char *)roleParams->cRole,
+ "audio_encoder.amrwb",
+ OMX_MAX_STRINGNAME_SIZE - 1)) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioPortFormat:
+ {
+ const OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams =
+ (const OMX_AUDIO_PARAM_PORTFORMATTYPE *)params;
+
+ if (formatParams->nPortIndex > 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (formatParams->nIndex > 0) {
+ return OMX_ErrorNoMore;
+ }
+
+ if ((formatParams->nPortIndex == 0
+ && formatParams->eEncoding != OMX_AUDIO_CodingPCM)
+ || (formatParams->nPortIndex == 1
+ && formatParams->eEncoding != OMX_AUDIO_CodingAMR)) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioAmr:
+ {
+ OMX_AUDIO_PARAM_AMRTYPE *amrParams =
+ (OMX_AUDIO_PARAM_AMRTYPE *)params;
+
+ if (amrParams->nPortIndex != 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (amrParams->nChannels != 1
+ || amrParams->eAMRDTXMode != OMX_AUDIO_AMRDTXModeOff
+ || amrParams->eAMRFrameFormat
+ != OMX_AUDIO_AMRFrameFormatFSF
+ || amrParams->eAMRBandMode < OMX_AUDIO_AMRBandModeWB0
+ || amrParams->eAMRBandMode > OMX_AUDIO_AMRBandModeWB8) {
+ return OMX_ErrorUndefined;
+ }
+
+ mBitRate = amrParams->nBitRate;
+
+ mMode = (VOAMRWBMODE)(
+ amrParams->eAMRBandMode - OMX_AUDIO_AMRBandModeWB0);
+
+ amrParams->eAMRDTXMode = OMX_AUDIO_AMRDTXModeOff;
+ amrParams->eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
+
+ if (VO_ERR_NONE !=
+ mApiHandle->SetParam(
+ mEncoderHandle, VO_PID_AMRWB_MODE, &mMode)) {
+ ALOGE("Failed to set AMRWB encoder mode to %d", mMode);
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioPcm:
+ {
+ OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams =
+ (OMX_AUDIO_PARAM_PCMMODETYPE *)params;
+
+ if (pcmParams->nPortIndex != 0) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (pcmParams->nChannels != 1
+ || pcmParams->nSamplingRate != (OMX_U32)kSampleRate) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+
+ default:
+ return SimpleSoftOMXComponent::internalSetParameter(index, params);
+ }
+}
+
+void SoftAMRWBEncoder::onQueueFilled(OMX_U32 portIndex) {
+ if (mSignalledError) {
+ return;
+ }
+
+ List<BufferInfo *> &inQueue = getPortQueue(0);
+ List<BufferInfo *> &outQueue = getPortQueue(1);
+
+ size_t numBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t);
+
+ for (;;) {
+ // We do the following until we run out of buffers.
+
+ while (mInputSize < numBytesPerInputFrame) {
+ // As long as there's still input data to be read we
+ // will drain "kNumSamplesPerFrame" samples
+ // into the "mInputFrame" buffer and then encode those
+ // as a unit into an output buffer.
+
+ if (mSawInputEOS || inQueue.empty()) {
+ return;
+ }
+
+ BufferInfo *inInfo = *inQueue.begin();
+ OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader;
+
+ const void *inData = inHeader->pBuffer + inHeader->nOffset;
+
+ size_t copy = numBytesPerInputFrame - mInputSize;
+ if (copy > inHeader->nFilledLen) {
+ copy = inHeader->nFilledLen;
+ }
+
+ if (mInputSize == 0) {
+ mInputTimeUs = inHeader->nTimeStamp;
+ }
+
+ memcpy((uint8_t *)mInputFrame + mInputSize, inData, copy);
+ mInputSize += copy;
+
+ inHeader->nOffset += copy;
+ inHeader->nFilledLen -= copy;
+
+ // "Time" on the input buffer has in effect advanced by the
+ // number of audio frames we just advanced nOffset by.
+ inHeader->nTimeStamp +=
+ (copy * 1000000ll / kSampleRate) / sizeof(int16_t);
+
+ if (inHeader->nFilledLen == 0) {
+ if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
+ ALOGV("saw input EOS");
+ mSawInputEOS = true;
+
+ // Pad any remaining data with zeroes.
+ memset((uint8_t *)mInputFrame + mInputSize,
+ 0,
+ numBytesPerInputFrame - mInputSize);
+
+ mInputSize = numBytesPerInputFrame;
+ }
+
+ inQueue.erase(inQueue.begin());
+ inInfo->mOwnedByUs = false;
+ notifyEmptyBufferDone(inHeader);
+
+ inData = NULL;
+ inHeader = NULL;
+ inInfo = NULL;
+ }
+ }
+
+ // At this point we have all the input data necessary to encode
+ // a single frame, all we need is an output buffer to store the result
+ // in.
+
+ if (outQueue.empty()) {
+ return;
+ }
+
+ BufferInfo *outInfo = *outQueue.begin();
+ OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader;
+
+ uint8_t *outPtr = outHeader->pBuffer + outHeader->nOffset;
+ size_t outAvailable = outHeader->nAllocLen - outHeader->nOffset;
+
+ VO_CODECBUFFER inputData;
+ memset(&inputData, 0, sizeof(inputData));
+ inputData.Buffer = (unsigned char *) mInputFrame;
+ inputData.Length = mInputSize;
+
+ CHECK_EQ(VO_ERR_NONE,
+ mApiHandle->SetInputData(mEncoderHandle, &inputData));
+
+ VO_CODECBUFFER outputData;
+ memset(&outputData, 0, sizeof(outputData));
+ VO_AUDIO_OUTPUTINFO outputInfo;
+ memset(&outputInfo, 0, sizeof(outputInfo));
+
+ outputData.Buffer = outPtr;
+ outputData.Length = outAvailable;
+ VO_U32 ret = mApiHandle->GetOutputData(
+ mEncoderHandle, &outputData, &outputInfo);
+ CHECK(ret == VO_ERR_NONE || ret == VO_ERR_INPUT_BUFFER_SMALL);
+
+ outHeader->nFilledLen = outputData.Length;
+ outHeader->nFlags = OMX_BUFFERFLAG_ENDOFFRAME;
+
+ if (mSawInputEOS) {
+ // We also tag this output buffer with EOS if it corresponds
+ // to the final input buffer.
+ outHeader->nFlags = OMX_BUFFERFLAG_EOS;
+ }
+
+ outHeader->nTimeStamp = mInputTimeUs;
+
+#if 0
+ ALOGI("sending %ld bytes of data (time = %lld us, flags = 0x%08lx)",
+ outHeader->nFilledLen, mInputTimeUs, outHeader->nFlags);
+
+ hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen);
+#endif
+
+ outQueue.erase(outQueue.begin());
+ outInfo->mOwnedByUs = false;
+ notifyFillBufferDone(outHeader);
+
+ outHeader = NULL;
+ outInfo = NULL;
+
+ mInputSize = 0;
+ }
+}
+
+} // namespace android
+
+android::SoftOMXComponent *createSoftOMXComponent(
+ const char *name, const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData, OMX_COMPONENTTYPE **component) {
+ return new android::SoftAMRWBEncoder(name, callbacks, appData, component);
+}
diff --git a/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.h b/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.h
new file mode 100644
index 0000000..d0c1dab
--- /dev/null
+++ b/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef SOFT_AMRWB_ENCODER_H_
+
+#define SOFT_AMRWB_ENCODER_H_
+
+#include "SimpleSoftOMXComponent.h"
+
+#include "voAMRWB.h"
+
+struct VO_AUDIO_CODECAPI;
+struct VO_MEM_OPERATOR;
+
+namespace android {
+
+struct SoftAMRWBEncoder : public SimpleSoftOMXComponent {
+ SoftAMRWBEncoder(
+ const char *name,
+ const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData,
+ OMX_COMPONENTTYPE **component);
+
+protected:
+ virtual ~SoftAMRWBEncoder();
+
+ virtual OMX_ERRORTYPE internalGetParameter(
+ OMX_INDEXTYPE index, OMX_PTR params);
+
+ virtual OMX_ERRORTYPE internalSetParameter(
+ OMX_INDEXTYPE index, const OMX_PTR params);
+
+ virtual void onQueueFilled(OMX_U32 portIndex);
+
+private:
+ enum {
+ kNumBuffers = 4,
+ kNumSamplesPerFrame = 320,
+ };
+
+ void *mEncoderHandle;
+ VO_AUDIO_CODECAPI *mApiHandle;
+ VO_MEM_OPERATOR *mMemOperator;
+
+ OMX_U32 mBitRate;
+ VOAMRWBMODE mMode;
+
+ size_t mInputSize;
+ int16_t mInputFrame[kNumSamplesPerFrame];
+ int64_t mInputTimeUs;
+
+ bool mSawInputEOS;
+ bool mSignalledError;
+
+ void initPorts();
+ status_t initEncoder();
+
+ DISALLOW_EVIL_CONSTRUCTORS(SoftAMRWBEncoder);
+};
+
+} // namespace android
+
+#endif // SOFT_AMRWB_ENCODER_H_
diff --git a/media/libstagefright/colorconversion/ColorConverter.cpp b/media/libstagefright/colorconversion/ColorConverter.cpp
index 5cc3f78..f3ef3de 100644
--- a/media/libstagefright/colorconversion/ColorConverter.cpp
+++ b/media/libstagefright/colorconversion/ColorConverter.cpp
@@ -144,8 +144,8 @@
return ERROR_UNSUPPORTED;
}
- uint32_t *dst_ptr = (uint32_t *)dst.mBits
- + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2;
+ uint16_t *dst_ptr = (uint16_t *)dst.mBits
+ + dst.mCropTop * dst.mWidth + dst.mCropLeft;
const uint8_t *src_ptr = (const uint8_t *)src.mBits
+ (src.mCropTop * dst.mWidth + src.mCropLeft) * 2;
@@ -182,11 +182,15 @@
| ((kAdjustedClip[g2] >> 2) << 5)
| (kAdjustedClip[b2] >> 3);
- dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+ if (x + 1 < src.cropWidth()) {
+ *(uint32_t *)(&dst_ptr[x]) = (rgb2 << 16) | rgb1;
+ } else {
+ dst_ptr[x] = rgb1;
+ }
}
src_ptr += src.mWidth * 2;
- dst_ptr += dst.mWidth / 2;
+ dst_ptr += dst.mWidth;
}
return OK;
@@ -290,15 +294,14 @@
const BitmapParams &src, const BitmapParams &dst) {
uint8_t *kAdjustedClip = initClip();
- if (!((dst.mWidth & 3) == 0
- && (src.mCropLeft & 1) == 0
+ if (!((src.mCropLeft & 1) == 0
&& src.cropWidth() == dst.cropWidth()
&& src.cropHeight() == dst.cropHeight())) {
return ERROR_UNSUPPORTED;
}
- uint32_t *dst_ptr = (uint32_t *)dst.mBits
- + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2;
+ uint16_t *dst_ptr = (uint16_t *)dst.mBits
+ + dst.mCropTop * dst.mWidth + dst.mCropLeft;
const uint8_t *src_y =
(const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft;
@@ -340,7 +343,11 @@
| ((kAdjustedClip[g2] >> 2) << 5)
| (kAdjustedClip[r2] >> 3);
- dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+ if (x + 1 < src.cropWidth()) {
+ *(uint32_t *)(&dst_ptr[x]) = (rgb2 << 16) | rgb1;
+ } else {
+ dst_ptr[x] = rgb1;
+ }
}
src_y += src.mWidth;
@@ -349,7 +356,7 @@
src_u += src.mWidth;
}
- dst_ptr += dst.mWidth / 2;
+ dst_ptr += dst.mWidth;
}
return OK;
@@ -361,15 +368,14 @@
uint8_t *kAdjustedClip = initClip();
- if (!((dst.mWidth & 3) == 0
- && (src.mCropLeft & 1) == 0
+ if (!((src.mCropLeft & 1) == 0
&& src.cropWidth() == dst.cropWidth()
&& src.cropHeight() == dst.cropHeight())) {
return ERROR_UNSUPPORTED;
}
- uint32_t *dst_ptr = (uint32_t *)dst.mBits
- + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2;
+ uint16_t *dst_ptr = (uint16_t *)dst.mBits
+ + dst.mCropTop * dst.mWidth + dst.mCropLeft;
const uint8_t *src_y =
(const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft;
@@ -411,7 +417,11 @@
| ((kAdjustedClip[g2] >> 2) << 5)
| (kAdjustedClip[r2] >> 3);
- dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+ if (x + 1 < src.cropWidth()) {
+ *(uint32_t *)(&dst_ptr[x]) = (rgb2 << 16) | rgb1;
+ } else {
+ dst_ptr[x] = rgb1;
+ }
}
src_y += src.mWidth;
@@ -420,7 +430,7 @@
src_u += src.mWidth;
}
- dst_ptr += dst.mWidth / 2;
+ dst_ptr += dst.mWidth;
}
return OK;
@@ -430,15 +440,14 @@
const BitmapParams &src, const BitmapParams &dst) {
uint8_t *kAdjustedClip = initClip();
- if (!((dst.mWidth & 3) == 0
- && (src.mCropLeft & 1) == 0
+ if (!((src.mCropLeft & 1) == 0
&& src.cropWidth() == dst.cropWidth()
&& src.cropHeight() == dst.cropHeight())) {
return ERROR_UNSUPPORTED;
}
- uint32_t *dst_ptr = (uint32_t *)dst.mBits
- + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2;
+ uint16_t *dst_ptr = (uint16_t *)dst.mBits
+ + dst.mCropTop * dst.mWidth + dst.mCropLeft;
const uint8_t *src_y = (const uint8_t *)src.mBits;
@@ -478,7 +487,11 @@
| ((kAdjustedClip[g2] >> 2) << 5)
| (kAdjustedClip[b2] >> 3);
- dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+ if (x + 1 < src.cropWidth()) {
+ *(uint32_t *)(&dst_ptr[x]) = (rgb2 << 16) | rgb1;
+ } else {
+ dst_ptr[x] = rgb1;
+ }
}
src_y += src.mWidth;
@@ -487,7 +500,7 @@
src_u += src.mWidth;
}
- dst_ptr += dst.mWidth / 2;
+ dst_ptr += dst.mWidth;
}
return OK;
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 82c6476..a7a3d47 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -41,7 +41,7 @@
class DrmManagerClinet;
class DecryptHandle;
-class TimedTextPlayer;
+class TimedTextDriver;
struct WVMExtractor;
struct AwesomeRenderer : public RefBase {
@@ -232,7 +232,7 @@
sp<DecryptHandle> mDecryptHandle;
int64_t mLastVideoTimeUs;
- TimedTextPlayer *mTextPlayer;
+ TimedTextDriver *mTextDriver;
mutable Mutex mTimedTextLock;
sp<WVMExtractor> mWVMExtractor;
@@ -326,4 +326,3 @@
} // namespace android
#endif // AWESOME_PLAYER_H_
-
diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h
index 53e764f..2c87b34 100644
--- a/media/libstagefright/include/OMX.h
+++ b/media/libstagefright/include/OMX.h
@@ -31,7 +31,7 @@
public:
OMX();
- virtual bool livesLocally(pid_t pid);
+ virtual bool livesLocally(node_id node, pid_t pid);
virtual status_t listNodes(List<ComponentInfo> *list);
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 694b12d8..ace883c 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -185,7 +185,7 @@
instance->onObserverDied(mMaster);
}
-bool OMX::livesLocally(pid_t pid) {
+bool OMX::livesLocally(node_id node, pid_t pid) {
return pid == getpid();
}
diff --git a/media/libstagefright/omx/SoftOMXPlugin.cpp b/media/libstagefright/omx/SoftOMXPlugin.cpp
index cf9e8c9..99ffe7d 100644
--- a/media/libstagefright/omx/SoftOMXPlugin.cpp
+++ b/media/libstagefright/omx/SoftOMXPlugin.cpp
@@ -37,7 +37,9 @@
{ "OMX.google.aac.decoder", "aacdec", "audio_decoder.aac" },
{ "OMX.google.aac.encoder", "aacenc", "audio_encoder.aac" },
{ "OMX.google.amrnb.decoder", "amrdec", "audio_decoder.amrnb" },
+ { "OMX.google.amrnb.encoder", "amrnbenc", "audio_encoder.amrnb" },
{ "OMX.google.amrwb.decoder", "amrdec", "audio_decoder.amrwb" },
+ { "OMX.google.amrwb.encoder", "amrwbenc", "audio_encoder.amrwb" },
{ "OMX.google.h264.decoder", "h264dec", "video_decoder.avc" },
{ "OMX.google.g711.alaw.decoder", "g711dec", "audio_decoder.g711alaw" },
{ "OMX.google.g711.mlaw.decoder", "g711dec", "audio_decoder.g711mlaw" },
diff --git a/media/libstagefright/omx/tests/Android.mk b/media/libstagefright/omx/tests/Android.mk
index bf69428..41c08be 100644
--- a/media/libstagefright/omx/tests/Android.mk
+++ b/media/libstagefright/omx/tests/Android.mk
@@ -7,11 +7,13 @@
LOCAL_SHARED_LIBRARIES := \
libstagefright libbinder libmedia libutils
-LOCAL_C_INCLUDES:= \
+LOCAL_C_INCLUDES := \
$(JNI_H_INCLUDE) \
frameworks/base/media/libstagefright \
$(TOP)/frameworks/base/include/media/stagefright/openmax
-LOCAL_MODULE:= omx_tests
+LOCAL_MODULE := omx_tests
+
+LOCAL_MODULE_TAGS := tests
include $(BUILD_EXECUTABLE)
diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk
index 59d0e15..8b23dee 100644
--- a/media/libstagefright/timedtext/Android.mk
+++ b/media/libstagefright/timedtext/Android.mk
@@ -3,7 +3,10 @@
LOCAL_SRC_FILES:= \
TextDescriptions.cpp \
- TimedTextParser.cpp \
+ TimedTextDriver.cpp \
+ TimedTextInBandSource.cpp \
+ TimedTextSource.cpp \
+ TimedTextSRTSource.cpp \
TimedTextPlayer.cpp
LOCAL_CFLAGS += -Wno-multichar
diff --git a/media/libstagefright/timedtext/TimedTextDriver.cpp b/media/libstagefright/timedtext/TimedTextDriver.cpp
new file mode 100644
index 0000000..9ec9415
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextDriver.cpp
@@ -0,0 +1,223 @@
+ /*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "TimedTextDriver"
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+
+#include "TimedTextDriver.h"
+
+#include "TextDescriptions.h"
+#include "TimedTextPlayer.h"
+#include "TimedTextSource.h"
+
+namespace android {
+
+TimedTextDriver::TimedTextDriver(
+ const wp<MediaPlayerBase> &listener)
+ : mLooper(new ALooper),
+ mListener(listener),
+ mState(UNINITIALIZED) {
+ mLooper->setName("TimedTextDriver");
+ mLooper->start();
+ mPlayer = new TimedTextPlayer(listener);
+ mLooper->registerHandler(mPlayer);
+}
+
+TimedTextDriver::~TimedTextDriver() {
+ mTextInBandVector.clear();
+ mTextOutOfBandVector.clear();
+ mLooper->stop();
+}
+
+status_t TimedTextDriver::setTimedTextTrackIndex_l(int32_t index) {
+ if (index >=
+ (int)(mTextInBandVector.size() + mTextOutOfBandVector.size())) {
+ return BAD_VALUE;
+ }
+
+ sp<TimedTextSource> source;
+ if (index < mTextInBandVector.size()) {
+ source = mTextInBandVector.itemAt(index);
+ } else {
+ source = mTextOutOfBandVector.itemAt(index - mTextInBandVector.size());
+ }
+ mPlayer->setDataSource(source);
+ return OK;
+}
+
+status_t TimedTextDriver::start() {
+ Mutex::Autolock autoLock(mLock);
+ switch (mState) {
+ case UNINITIALIZED:
+ return INVALID_OPERATION;
+ case STOPPED:
+ mPlayer->start();
+ break;
+ case PLAYING:
+ return OK;
+ case PAUSED:
+ mPlayer->resume();
+ break;
+ default:
+ TRESPASS();
+ }
+ mState = PLAYING;
+ return OK;
+}
+
+status_t TimedTextDriver::stop() {
+ return pause();
+}
+
+// TODO: Test if pause() works properly.
+// Scenario 1: start - pause - resume
+// Scenario 2: start - seek
+// Scenario 3: start - pause - seek - resume
+status_t TimedTextDriver::pause() {
+ Mutex::Autolock autoLock(mLock);
+ switch (mState) {
+ case UNINITIALIZED:
+ return INVALID_OPERATION;
+ case STOPPED:
+ return OK;
+ case PLAYING:
+ mPlayer->pause();
+ break;
+ case PAUSED:
+ return OK;
+ default:
+ TRESPASS();
+ }
+ mState = PAUSED;
+ return OK;
+}
+
+status_t TimedTextDriver::resume() {
+ return start();
+}
+
+status_t TimedTextDriver::seekToAsync(int64_t timeUs) {
+ mPlayer->seekToAsync(timeUs);
+ return OK;
+}
+
+status_t TimedTextDriver::setTimedTextTrackIndex(int32_t index) {
+ // TODO: This is current implementation for MediaPlayer::disableTimedText().
+ // Find better way for readability.
+ if (index < 0) {
+ mPlayer->pause();
+ return OK;
+ }
+
+ status_t ret = OK;
+ Mutex::Autolock autoLock(mLock);
+ switch (mState) {
+ case UNINITIALIZED:
+ ret = INVALID_OPERATION;
+ break;
+ case PAUSED:
+ ret = setTimedTextTrackIndex_l(index);
+ break;
+ case PLAYING:
+ mPlayer->pause();
+ ret = setTimedTextTrackIndex_l(index);
+ if (ret != OK) {
+ break;
+ }
+ mPlayer->start();
+ break;
+ case STOPPED:
+ // TODO: The only difference between STOPPED and PAUSED is this
+ // part. Revise the flow from "MediaPlayer::enableTimedText()" and
+ // remove one of the status, PAUSED and STOPPED, if possible.
+ ret = setTimedTextTrackIndex_l(index);
+ if (ret != OK) {
+ break;
+ }
+ mPlayer->start();
+ break;
+ defaut:
+ TRESPASS();
+ }
+ return ret;
+}
+
+status_t TimedTextDriver::addInBandTextSource(
+ const sp<MediaSource>& mediaSource) {
+ sp<TimedTextSource> source =
+ TimedTextSource::CreateTimedTextSource(mediaSource);
+ if (source == NULL) {
+ return ERROR_UNSUPPORTED;
+ }
+ Mutex::Autolock autoLock(mLock);
+ mTextInBandVector.add(source);
+ if (mState == UNINITIALIZED) {
+ mState = STOPPED;
+ }
+ return OK;
+}
+
+status_t TimedTextDriver::addOutOfBandTextSource(
+ const Parcel &request) {
+ // TODO: Define "TimedTextSource::CreateFromURI(uri)"
+ // and move below lines there..?
+
+ // String values written in Parcel are UTF-16 values.
+ const String16 uri16 = request.readString16();
+ String8 uri = String8(request.readString16());
+
+ uri.toLower();
+ // To support local subtitle file only for now
+ if (strncasecmp("file://", uri.string(), 7)) {
+ return ERROR_UNSUPPORTED;
+ }
+ sp<DataSource> dataSource =
+ DataSource::CreateFromURI(uri);
+ if (dataSource == NULL) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ sp<TimedTextSource> source;
+ if (uri.getPathExtension() == String8(".srt")) {
+ source = TimedTextSource::CreateTimedTextSource(
+ dataSource, TimedTextSource::OUT_OF_BAND_FILE_SRT);
+ }
+
+ if (source == NULL) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+
+ mTextOutOfBandVector.add(source);
+ if (mState == UNINITIALIZED) {
+ mState = STOPPED;
+ }
+ return OK;
+}
+
+} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextDriver.h b/media/libstagefright/timedtext/TimedTextDriver.h
new file mode 100644
index 0000000..efedb6e
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextDriver.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef TIMED_TEXT_DRIVER_H_
+#define TIMED_TEXT_DRIVER_H_
+
+#include <media/stagefright/foundation/ABase.h> // for DISALLOW_* macro
+#include <utils/Errors.h> // for status_t
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class ALooper;
+class MediaPlayerBase;
+class MediaSource;
+class Parcel;
+class TimedTextPlayer;
+class TimedTextSource;
+
+class TimedTextDriver {
+public:
+ TimedTextDriver(const wp<MediaPlayerBase> &listener);
+
+ ~TimedTextDriver();
+
+ // TODO: pause-resume pair seems equivalent to stop-start pair.
+ // Check if it is replaceable with stop-start.
+ status_t start();
+ status_t stop();
+ status_t pause();
+ status_t resume();
+
+ status_t seekToAsync(int64_t timeUs);
+
+ status_t addInBandTextSource(const sp<MediaSource>& source);
+ status_t addOutOfBandTextSource(const Parcel &request);
+
+ status_t setTimedTextTrackIndex(int32_t index);
+
+private:
+ Mutex mLock;
+
+ enum State {
+ UNINITIALIZED,
+ STOPPED,
+ PLAYING,
+ PAUSED,
+ };
+
+ sp<ALooper> mLooper;
+ sp<TimedTextPlayer> mPlayer;
+ wp<MediaPlayerBase> mListener;
+
+ // Variables to be guarded by mLock.
+ State mState;
+ Vector<sp<TimedTextSource> > mTextInBandVector;
+ Vector<sp<TimedTextSource> > mTextOutOfBandVector;
+ // -- End of variables to be guarded by mLock
+
+ status_t setTimedTextTrackIndex_l(int32_t index);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TimedTextDriver);
+};
+
+} // namespace android
+
+#endif // TIMED_TEXT_DRIVER_H_
diff --git a/media/libstagefright/timedtext/TimedTextInBandSource.cpp b/media/libstagefright/timedtext/TimedTextInBandSource.cpp
new file mode 100644
index 0000000..f2c4d54
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextInBandSource.cpp
@@ -0,0 +1,118 @@
+ /*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "TimedTextInBandSource"
+#include <utils/Log.h>
+
+#include <binder/Parcel.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h> // CHECK_XX macro
+#include <media/stagefright/MediaDefs.h> // for MEDIA_MIMETYPE_xxx
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+#include "TimedTextInBandSource.h"
+#include "TextDescriptions.h"
+
+namespace android {
+
+TimedTextInBandSource::TimedTextInBandSource(const sp<MediaSource>& mediaSource)
+ : mSource(mediaSource) {
+}
+
+TimedTextInBandSource::~TimedTextInBandSource() {
+}
+
+status_t TimedTextInBandSource::read(
+ int64_t *timeUs, Parcel *parcel, const MediaSource::ReadOptions *options) {
+ MediaBuffer *textBuffer = NULL;
+ status_t err = mSource->read(&textBuffer, options);
+ if (err != OK) {
+ return err;
+ }
+ CHECK(textBuffer != NULL);
+ textBuffer->meta_data()->findInt64(kKeyTime, timeUs);
+ // TODO: this is legacy code. when 'timeUs' can be <= 0?
+ if (*timeUs > 0) {
+ extractAndAppendLocalDescriptions(*timeUs, textBuffer, parcel);
+ }
+ textBuffer->release();
+ return OK;
+}
+
+// Each text sample consists of a string of text, optionally with sample
+// modifier description. The modifier description could specify a new
+// text style for the string of text. These descriptions are present only
+// if they are needed. This method is used to extract the modifier
+// description and append it at the end of the text.
+status_t TimedTextInBandSource::extractAndAppendLocalDescriptions(
+ int64_t timeUs, const MediaBuffer *textBuffer, Parcel *parcel) {
+ const void *data;
+ size_t size = 0;
+ int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS;
+
+ const char *mime;
+ CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+
+ if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) {
+ data = textBuffer->data();
+ size = textBuffer->size();
+
+ if (size > 0) {
+ parcel->freeData();
+ flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
+ return TextDescriptions::getParcelOfDescriptions(
+ (const uint8_t *)data, size, flag, timeUs / 1000, parcel);
+ }
+ return OK;
+ }
+ return ERROR_UNSUPPORTED;
+}
+
+// To extract and send the global text descriptions for all the text samples
+// in the text track or text file.
+// TODO: send error message to application via notifyListener()...?
+status_t TimedTextInBandSource::extractGlobalDescriptions(Parcel *parcel) {
+ const void *data;
+ size_t size = 0;
+ int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS;
+
+ const char *mime;
+ CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+
+ // support 3GPP only for now
+ if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) {
+ uint32_t type;
+ // get the 'tx3g' box content. This box contains the text descriptions
+ // used to render the text track
+ if (!mSource->getFormat()->findData(
+ kKeyTextFormatData, &type, &data, &size)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (size > 0) {
+ flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
+ return TextDescriptions::getParcelOfDescriptions(
+ (const uint8_t *)data, size, flag, 0, parcel);
+ }
+ return OK;
+ }
+ return ERROR_UNSUPPORTED;
+}
+
+} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextInBandSource.h b/media/libstagefright/timedtext/TimedTextInBandSource.h
new file mode 100644
index 0000000..26e5737
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextInBandSource.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef TIMED_TEXT_IN_BAND_SOURCE_H_
+#define TIMED_TEXT_IN_BAND_SOURCE_H_
+
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+
+#include "TimedTextSource.h"
+
+namespace android {
+
+class MediaBuffer;
+class Parcel;
+
+class TimedTextInBandSource : public TimedTextSource {
+ public:
+ TimedTextInBandSource(const sp<MediaSource>& mediaSource);
+ virtual status_t start() { return mSource->start(); }
+ virtual status_t stop() { return mSource->stop(); }
+ virtual status_t read(
+ int64_t *timeUs,
+ Parcel *parcel,
+ const MediaSource::ReadOptions *options = NULL);
+ virtual status_t extractGlobalDescriptions(Parcel *parcel);
+
+ protected:
+ virtual ~TimedTextInBandSource();
+
+ private:
+ sp<MediaSource> mSource;
+
+ status_t extractAndAppendLocalDescriptions(
+ int64_t timeUs, const MediaBuffer *textBuffer, Parcel *parcel);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TimedTextInBandSource);
+};
+
+} // namespace android
+
+#endif // TIMED_TEXT_IN_BAND_SOURCE_H_
diff --git a/media/libstagefright/timedtext/TimedTextParser.cpp b/media/libstagefright/timedtext/TimedTextParser.cpp
deleted file mode 100644
index caea0a4..0000000
--- a/media/libstagefright/timedtext/TimedTextParser.cpp
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "TimedTextParser.h"
-#include <media/stagefright/DataSource.h>
-
-namespace android {
-
-TimedTextParser::TimedTextParser()
- : mDataSource(NULL),
- mOffset(0),
- mIndex(0) {
-}
-
-TimedTextParser::~TimedTextParser() {
- reset();
-}
-
-status_t TimedTextParser::init(
- const sp<DataSource> &dataSource, FileType fileType) {
- mDataSource = dataSource;
- mFileType = fileType;
-
- status_t err;
- if ((err = scanFile()) != OK) {
- reset();
- return err;
- }
-
- return OK;
-}
-
-void TimedTextParser::reset() {
- mDataSource.clear();
- mTextVector.clear();
- mOffset = 0;
- mIndex = 0;
-}
-
-// scan the text file to get start/stop time and the
-// offset of each piece of text content
-status_t TimedTextParser::scanFile() {
- if (mFileType != OUT_OF_BAND_FILE_SRT) {
- return ERROR_UNSUPPORTED;
- }
-
- off64_t offset = 0;
- int64_t startTimeUs;
- bool endOfFile = false;
-
- while (!endOfFile) {
- TextInfo info;
- status_t err = getNextInSrtFileFormat(&offset, &startTimeUs, &info);
-
- if (err != OK) {
- if (err == ERROR_END_OF_STREAM) {
- endOfFile = true;
- } else {
- return err;
- }
- } else {
- mTextVector.add(startTimeUs, info);
- }
- }
-
- if (mTextVector.isEmpty()) {
- return ERROR_MALFORMED;
- }
- return OK;
-}
-
-// read one line started from *offset and store it into data.
-status_t TimedTextParser::readNextLine(off64_t *offset, AString *data) {
- char character;
-
- data->clear();
-
- while (true) {
- ssize_t err;
- if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) {
- if (err == 0) {
- return ERROR_END_OF_STREAM;
- }
- return ERROR_IO;
- }
-
- (*offset) ++;
-
- // a line could end with CR, LF or CR + LF
- if (character == 10) {
- break;
- } else if (character == 13) {
- if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) {
- if (err == 0) { // end of the stream
- return OK;
- }
- return ERROR_IO;
- }
-
- (*offset) ++;
-
- if (character != 10) {
- (*offset) --;
- }
- break;
- }
-
- data->append(character);
- }
-
- return OK;
-}
-
-/* SRT format:
- * Subtitle number
- * Start time --> End time
- * Text of subtitle (one or more lines)
- * Blank lines
- *
- * .srt file example:
- * 1
- * 00:00:20,000 --> 00:00:24,400
- * Altocumulus clouds occur between six thousand
- *
- * 2
- * 00:00:24,600 --> 00:00:27,800
- * and twenty thousand feet above ground level.
- */
-status_t TimedTextParser::getNextInSrtFileFormat(
- off64_t *offset, int64_t *startTimeUs, TextInfo *info) {
- AString data;
- status_t err;
-
- // To skip blank lines.
- do {
- if ((err = readNextLine(offset, &data)) != OK) {
- return err;
- }
- data.trim();
- } while(data.empty());
-
- // Just ignore the first non-blank line which is subtitle sequence number.
-
- if ((err = readNextLine(offset, &data)) != OK) {
- return err;
- }
- int hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
- // the start time format is: hours:minutes:seconds,milliseconds
- // 00:00:24,600 --> 00:00:27,800
- if (sscanf(data.c_str(), "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d",
- &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) {
- return ERROR_MALFORMED;
- }
-
- *startTimeUs = ((hour1 * 3600 + min1 * 60 + sec1) * 1000 + msec1) * 1000ll;
- info->endTimeUs = ((hour2 * 3600 + min2 * 60 + sec2) * 1000 + msec2) * 1000ll;
- if (info->endTimeUs <= *startTimeUs) {
- return ERROR_MALFORMED;
- }
-
- info->offset = *offset;
-
- bool needMoreData = true;
- while (needMoreData) {
- if ((err = readNextLine(offset, &data)) != OK) {
- if (err == ERROR_END_OF_STREAM) {
- needMoreData = false;
- } else {
- return err;
- }
- }
-
- if (needMoreData) {
- data.trim();
- if (data.empty()) {
- // it's an empty line used to separate two subtitles
- needMoreData = false;
- }
- }
- }
-
- info->textLen = *offset - info->offset;
-
- return OK;
-}
-
-status_t TimedTextParser::getText(
- AString *text, int64_t *startTimeUs, int64_t *endTimeUs,
- const MediaSource::ReadOptions *options) {
- Mutex::Autolock autoLock(mLock);
-
- text->clear();
-
- int64_t seekTimeUs;
- MediaSource::ReadOptions::SeekMode mode;
- if (options && options->getSeekTo(&seekTimeUs, &mode)) {
- int64_t lastEndTimeUs = mTextVector.valueAt(mTextVector.size() - 1).endTimeUs;
- int64_t firstStartTimeUs = mTextVector.keyAt(0);
-
- if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) {
- return ERROR_OUT_OF_RANGE;
- } else if (seekTimeUs < firstStartTimeUs) {
- mIndex = 0;
- } else {
- // binary search
- ssize_t low = 0;
- ssize_t high = mTextVector.size() - 1;
- ssize_t mid = 0;
- int64_t currTimeUs;
-
- while (low <= high) {
- mid = low + (high - low)/2;
- currTimeUs = mTextVector.keyAt(mid);
- const int diff = currTimeUs - seekTimeUs;
-
- if (diff == 0) {
- break;
- } else if (diff < 0) {
- low = mid + 1;
- } else {
- if ((high == mid + 1)
- && (seekTimeUs < mTextVector.keyAt(high))) {
- break;
- }
- high = mid - 1;
- }
- }
-
- mIndex = mid;
- }
- }
-
- TextInfo info = mTextVector.valueAt(mIndex);
- *startTimeUs = mTextVector.keyAt(mIndex);
- *endTimeUs = info.endTimeUs;
- mIndex ++;
-
- char *str = new char[info.textLen];
- if (mDataSource->readAt(info.offset, str, info.textLen) < info.textLen) {
- delete[] str;
- return ERROR_IO;
- }
-
- text->append(str, info.textLen);
- delete[] str;
- return OK;
-}
-
-} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextParser.h b/media/libstagefright/timedtext/TimedTextParser.h
deleted file mode 100644
index 44774c2..0000000
--- a/media/libstagefright/timedtext/TimedTextParser.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef TIMED_TEXT_PARSER_H_
-
-#define TIMED_TEXT_PARSER_H_
-
-#include <media/MediaPlayerInterface.h>
-#include <media/stagefright/foundation/ABase.h>
-#include <media/stagefright/foundation/AString.h>
-#include <media/stagefright/MediaSource.h>
-
-namespace android {
-
-class DataSource;
-
-class TimedTextParser : public RefBase {
-public:
- TimedTextParser();
- virtual ~TimedTextParser();
-
- enum FileType {
- OUT_OF_BAND_FILE_SRT = 1,
- };
-
- status_t getText(AString *text, int64_t *startTimeUs, int64_t *endTimeUs,
- const MediaSource::ReadOptions *options = NULL);
- status_t init(const sp<DataSource> &dataSource, FileType fileType);
- void reset();
-
-private:
- Mutex mLock;
-
- sp<DataSource> mDataSource;
- off64_t mOffset;
-
- struct TextInfo {
- int64_t endTimeUs;
- // the offset of the text in the original file
- off64_t offset;
- int textLen;
- };
-
- int mIndex;
- FileType mFileType;
-
- // the key indicated the start time of the text
- KeyedVector<int64_t, TextInfo> mTextVector;
-
- status_t getNextInSrtFileFormat(
- off64_t *offset, int64_t *startTimeUs, TextInfo *info);
- status_t readNextLine(off64_t *offset, AString *data);
-
- status_t scanFile();
-
- DISALLOW_EVIL_CONSTRUCTORS(TimedTextParser);
-};
-
-} // namespace android
-
-#endif // TIMED_TEXT_PARSER_H_
-
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp
index 3014b0b..8c2df88 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.cpp
+++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2012 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.
@@ -18,399 +18,164 @@
#define LOG_TAG "TimedTextPlayer"
#include <utils/Log.h>
-#include <binder/IPCThreadState.h>
-
+#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MediaSource.h>
-#include <media/stagefright/MetaData.h>
-#include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/FileSource.h>
-#include <media/stagefright/Utils.h>
+#include <media/MediaPlayerInterface.h>
-#include "include/AwesomePlayer.h"
#include "TimedTextPlayer.h"
-#include "TimedTextParser.h"
-#include "TextDescriptions.h"
+
+#include "TimedTextDriver.h"
+#include "TimedTextSource.h"
namespace android {
-struct TimedTextEvent : public TimedEventQueue::Event {
- TimedTextEvent(
- TimedTextPlayer *player,
- void (TimedTextPlayer::*method)())
- : mPlayer(player),
- mMethod(method) {
- }
+static const int64_t kAdjustmentProcessingTimeUs = 100000ll;
-protected:
- virtual ~TimedTextEvent() {}
-
- virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
- (mPlayer->*mMethod)();
- }
-
-private:
- TimedTextPlayer *mPlayer;
- void (TimedTextPlayer::*mMethod)();
-
- TimedTextEvent(const TimedTextEvent &);
- TimedTextEvent &operator=(const TimedTextEvent &);
-};
-
-TimedTextPlayer::TimedTextPlayer(
- AwesomePlayer *observer,
- const wp<MediaPlayerBase> &listener,
- TimedEventQueue *queue)
- : mSource(NULL),
- mOutOfBandSource(NULL),
- mSeekTimeUs(0),
- mStarted(false),
- mTextEventPending(false),
- mQueue(queue),
- mListener(listener),
- mObserver(observer),
- mTextBuffer(NULL),
- mTextParser(NULL),
- mTextType(kNoText) {
- mTextEvent = new TimedTextEvent(this, &TimedTextPlayer::onTextEvent);
+TimedTextPlayer::TimedTextPlayer(const wp<MediaPlayerBase> &listener)
+ : mListener(listener),
+ mSource(NULL),
+ mSendSubtitleGeneration(0) {
}
TimedTextPlayer::~TimedTextPlayer() {
- if (mStarted) {
- reset();
+ if (mSource != NULL) {
+ mSource->stop();
+ mSource.clear();
+ mSource = NULL;
}
-
- mTextTrackVector.clear();
- mTextOutOfBandVector.clear();
}
-status_t TimedTextPlayer::start(uint8_t index) {
- CHECK(!mStarted);
-
- if (index >=
- mTextTrackVector.size() + mTextOutOfBandVector.size()) {
- ALOGE("Incorrect text track index: %d", index);
- return BAD_VALUE;
- }
-
- status_t err;
- if (index < mTextTrackVector.size()) { // start an in-band text
- mSource = mTextTrackVector.itemAt(index);
-
- err = mSource->start();
-
- if (err != OK) {
- return err;
- }
- mTextType = kInBandText;
- } else { // start an out-of-band text
- OutOfBandText text =
- mTextOutOfBandVector.itemAt(index - mTextTrackVector.size());
-
- mOutOfBandSource = text.source;
- TimedTextParser::FileType fileType = text.type;
-
- if (mTextParser == NULL) {
- mTextParser = new TimedTextParser();
- }
-
- if ((err = mTextParser->init(mOutOfBandSource, fileType)) != OK) {
- return err;
- }
- mTextType = kOutOfBandText;
- }
-
- // send sample description format
- if ((err = extractAndSendGlobalDescriptions()) != OK) {
- return err;
- }
-
- int64_t positionUs;
- mObserver->getPosition(&positionUs);
- seekTo(positionUs);
-
- postTextEvent();
-
- mStarted = true;
-
- return OK;
+void TimedTextPlayer::start() {
+ sp<AMessage> msg = new AMessage(kWhatSeek, id());
+ msg->setInt64("seekTimeUs", -1);
+ msg->post();
}
void TimedTextPlayer::pause() {
- CHECK(mStarted);
-
- cancelTextEvent();
+ (new AMessage(kWhatPause, id()))->post();
}
void TimedTextPlayer::resume() {
- CHECK(mStarted);
-
- postTextEvent();
+ start();
}
-void TimedTextPlayer::reset() {
- CHECK(mStarted);
+void TimedTextPlayer::seekToAsync(int64_t timeUs) {
+ sp<AMessage> msg = new AMessage(kWhatSeek, id());
+ msg->setInt64("seekTimeUs", timeUs);
+ msg->post();
+}
- // send an empty text to clear the screen
- notifyListener(MEDIA_TIMED_TEXT);
+void TimedTextPlayer::setDataSource(sp<TimedTextSource> source) {
+ sp<AMessage> msg = new AMessage(kWhatSetSource, id());
+ msg->setObject("source", source);
+ msg->post();
+}
- cancelTextEvent();
-
- mSeeking = false;
- mStarted = false;
-
- if (mTextType == kInBandText) {
- if (mTextBuffer != NULL) {
- mTextBuffer->release();
- mTextBuffer = NULL;
+void TimedTextPlayer::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatPause: {
+ mSendSubtitleGeneration++;
+ break;
}
-
- if (mSource != NULL) {
- mSource->stop();
- mSource.clear();
- mSource = NULL;
+ case kWhatSeek: {
+ int64_t seekTimeUs = 0;
+ msg->findInt64("seekTimeUs", &seekTimeUs);
+ if (seekTimeUs < 0) {
+ sp<MediaPlayerBase> listener = mListener.promote();
+ if (listener != NULL) {
+ int32_t positionMs = 0;
+ listener->getCurrentPosition(&positionMs);
+ seekTimeUs = positionMs * 1000ll;
+ }
+ }
+ doSeekAndRead(seekTimeUs);
+ break;
}
- } else {
- if (mTextParser != NULL) {
- mTextParser.clear();
- mTextParser = NULL;
+ case kWhatSendSubtitle: {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+ if (generation != mSendSubtitleGeneration) {
+ // Drop obsolete msg.
+ break;
+ }
+ sp<RefBase> obj;
+ msg->findObject("subtitle", &obj);
+ if (obj != NULL) {
+ sp<ParcelEvent> parcelEvent;
+ parcelEvent = static_cast<ParcelEvent*>(obj.get());
+ notifyListener(MEDIA_TIMED_TEXT, &(parcelEvent->parcel));
+ } else {
+ notifyListener(MEDIA_TIMED_TEXT);
+ }
+ doRead();
+ break;
}
- if (mOutOfBandSource != NULL) {
- mOutOfBandSource.clear();
- mOutOfBandSource = NULL;
+ case kWhatSetSource: {
+ sp<RefBase> obj;
+ msg->findObject("source", &obj);
+ if (obj == NULL) break;
+ if (mSource != NULL) {
+ mSource->stop();
+ }
+ mSource = static_cast<TimedTextSource*>(obj.get());
+ mSource->start();
+ Parcel parcel;
+ if (mSource->extractGlobalDescriptions(&parcel) == OK &&
+ parcel.dataSize() > 0) {
+ notifyListener(MEDIA_TIMED_TEXT, &parcel);
+ } else {
+ notifyListener(MEDIA_TIMED_TEXT);
+ }
+ break;
}
}
}
-status_t TimedTextPlayer::seekTo(int64_t time_us) {
- Mutex::Autolock autoLock(mLock);
-
- mSeeking = true;
- mSeekTimeUs = time_us;
-
- postTextEvent();
-
- return OK;
-}
-
-status_t TimedTextPlayer::setTimedTextTrackIndex(int32_t index) {
- if (index >=
- (int)(mTextTrackVector.size() + mTextOutOfBandVector.size())) {
- return BAD_VALUE;
- }
-
- if (mStarted) {
- reset();
- }
-
- if (index >= 0) {
- return start(index);
- }
- return OK;
-}
-
-void TimedTextPlayer::onTextEvent() {
- Mutex::Autolock autoLock(mLock);
-
- if (!mTextEventPending) {
- return;
- }
- mTextEventPending = false;
-
- if (mData.dataSize() > 0) {
- notifyListener(MEDIA_TIMED_TEXT, &mData);
- mData.freeData();
- }
-
+void TimedTextPlayer::doSeekAndRead(int64_t seekTimeUs) {
MediaSource::ReadOptions options;
- if (mSeeking) {
- options.setSeekTo(mSeekTimeUs,
- MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
- mSeeking = false;
-
- notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen
- }
-
- int64_t positionUs, timeUs;
- mObserver->getPosition(&positionUs);
-
- if (mTextType == kInBandText) {
- if (mSource->read(&mTextBuffer, &options) != OK) {
- return;
- }
-
- mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs);
- } else {
- int64_t endTimeUs;
- if (mTextParser->getText(
- &mText, &timeUs, &endTimeUs, &options) != OK) {
- return;
- }
- }
-
- if (timeUs > 0) {
- extractAndAppendLocalDescriptions(timeUs);
- }
-
- if (mTextType == kInBandText) {
- if (mTextBuffer != NULL) {
- mTextBuffer->release();
- mTextBuffer = NULL;
- }
- } else {
- mText.clear();
- }
-
- //send the text now
- if (timeUs <= positionUs + 100000ll) {
- postTextEvent();
- } else {
- postTextEvent(timeUs - positionUs - 100000ll);
- }
+ options.setSeekTo(seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
+ doRead(&options);
}
-void TimedTextPlayer::postTextEvent(int64_t delayUs) {
- if (mTextEventPending) {
- return;
- }
-
- mTextEventPending = true;
- mQueue->postEventWithDelay(mTextEvent, delayUs < 0 ? 10000 : delayUs);
+void TimedTextPlayer::doRead(MediaSource::ReadOptions* options) {
+ int64_t timeUs = 0;
+ sp<ParcelEvent> parcelEvent = new ParcelEvent();
+ mSource->read(&timeUs, &(parcelEvent->parcel), options);
+ postTextEvent(parcelEvent, timeUs);
}
-void TimedTextPlayer::cancelTextEvent() {
- mQueue->cancelEvent(mTextEvent->eventID());
- mTextEventPending = false;
-}
+void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeUs) {
+ sp<MediaPlayerBase> listener = mListener.promote();
+ if (listener != NULL) {
+ int64_t positionUs, delayUs;
+ int32_t positionMs = 0;
+ listener->getCurrentPosition(&positionMs);
+ positionUs = positionMs * 1000;
-void TimedTextPlayer::addTextSource(sp<MediaSource> source) {
- Mutex::Autolock autoLock(mLock);
- mTextTrackVector.add(source);
-}
-
-status_t TimedTextPlayer::setParameter(int key, const Parcel &request) {
- Mutex::Autolock autoLock(mLock);
-
- if (key == KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE) {
- const String16 uri16 = request.readString16();
- String8 uri = String8(uri16);
- KeyedVector<String8, String8> headers;
-
- // To support local subtitle file only for now
- if (strncasecmp("file://", uri.string(), 7)) {
- return INVALID_OPERATION;
- }
- sp<DataSource> dataSource =
- DataSource::CreateFromURI(uri, &headers);
- status_t err = dataSource->initCheck();
-
- if (err != OK) {
- return err;
- }
-
- OutOfBandText text;
- text.source = dataSource;
- if (uri.getPathExtension() == String8(".srt")) {
- text.type = TimedTextParser::OUT_OF_BAND_FILE_SRT;
+ if (timeUs <= positionUs + kAdjustmentProcessingTimeUs) {
+ delayUs = 0;
} else {
- return ERROR_UNSUPPORTED;
+ delayUs = timeUs - positionUs - kAdjustmentProcessingTimeUs;
}
-
- mTextOutOfBandVector.add(text);
-
- return OK;
+ sp<AMessage> msg = new AMessage(kWhatSendSubtitle, id());
+ msg->setInt32("generation", mSendSubtitleGeneration);
+ if (parcel != NULL) {
+ msg->setObject("subtitle", parcel);
+ }
+ msg->post(delayUs);
}
- return INVALID_OPERATION;
}
void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) {
- if (mListener != NULL) {
- sp<MediaPlayerBase> listener = mListener.promote();
-
- if (listener != NULL) {
- if (parcel && (parcel->dataSize() > 0)) {
- listener->sendEvent(msg, 0, 0, parcel);
- } else { // send an empty timed text to clear the screen
- listener->sendEvent(msg);
- }
+ sp<MediaPlayerBase> listener = mListener.promote();
+ if (listener != NULL) {
+ if (parcel != NULL && (parcel->dataSize() > 0)) {
+ listener->sendEvent(msg, 0, 0, parcel);
+ } else { // send an empty timed text to clear the screen
+ listener->sendEvent(msg);
}
}
}
-// Each text sample consists of a string of text, optionally with sample
-// modifier description. The modifier description could specify a new
-// text style for the string of text. These descriptions are present only
-// if they are needed. This method is used to extract the modifier
-// description and append it at the end of the text.
-status_t TimedTextPlayer::extractAndAppendLocalDescriptions(int64_t timeUs) {
- const void *data;
- size_t size = 0;
- int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS;
-
- if (mTextType == kInBandText) {
- const char *mime;
- CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
-
- if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
- flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
- data = mTextBuffer->data();
- size = mTextBuffer->size();
- } else {
- // support 3GPP only for now
- return ERROR_UNSUPPORTED;
- }
- } else {
- data = mText.c_str();
- size = mText.size();
- flag |= TextDescriptions::OUT_OF_BAND_TEXT_SRT;
- }
-
- if ((size > 0) && (flag != TextDescriptions::LOCAL_DESCRIPTIONS)) {
- mData.freeData();
- return TextDescriptions::getParcelOfDescriptions(
- (const uint8_t *)data, size, flag, timeUs / 1000, &mData);
- }
-
- return OK;
-}
-
-// To extract and send the global text descriptions for all the text samples
-// in the text track or text file.
-status_t TimedTextPlayer::extractAndSendGlobalDescriptions() {
- const void *data;
- size_t size = 0;
- int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS;
-
- if (mTextType == kInBandText) {
- const char *mime;
- CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
-
- // support 3GPP only for now
- if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
- uint32_t type;
- // get the 'tx3g' box content. This box contains the text descriptions
- // used to render the text track
- if (!mSource->getFormat()->findData(
- kKeyTextFormatData, &type, &data, &size)) {
- return ERROR_MALFORMED;
- }
-
- flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
- }
- }
-
- if ((size > 0) && (flag != TextDescriptions::GLOBAL_DESCRIPTIONS)) {
- Parcel parcel;
- if (TextDescriptions::getParcelOfDescriptions(
- (const uint8_t *)data, size, flag, 0, &parcel) == OK) {
- if (parcel.dataSize() > 0) {
- notifyListener(MEDIA_TIMED_TEXT, &parcel);
- }
- }
- }
-
- return OK;
-}
-}
+} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h
index a744db5..837beeb 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.h
+++ b/media/libstagefright/timedtext/TimedTextPlayer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2012 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.
@@ -15,99 +15,61 @@
*/
#ifndef TIMEDTEXT_PLAYER_H_
-
#define TIMEDTEXT_PLAYER_H_
-#include <media/MediaPlayerInterface.h>
+#include <binder/Parcel.h>
#include <media/stagefright/foundation/ABase.h>
-#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/RefBase.h>
-#include "include/TimedEventQueue.h"
-#include "TimedTextParser.h"
+#include "TimedTextSource.h"
namespace android {
-class MediaSource;
-class AwesomePlayer;
-class MediaBuffer;
+class AMessage;
+class MediaPlayerBase;
+class TimedTextDriver;
+class TimedTextSource;
-class TimedTextPlayer {
+class TimedTextPlayer : public AHandler {
public:
- TimedTextPlayer(AwesomePlayer *observer,
- const wp<MediaPlayerBase> &listener,
- TimedEventQueue *queue);
+ TimedTextPlayer(const wp<MediaPlayerBase> &listener);
virtual ~TimedTextPlayer();
- // index: the index of the text track which will
- // be turned on
- status_t start(uint8_t index);
-
+ void start();
void pause();
-
void resume();
+ void seekToAsync(int64_t timeUs);
+ void setDataSource(sp<TimedTextSource> source);
- status_t seekTo(int64_t time_us);
-
- void addTextSource(sp<MediaSource> source);
-
- status_t setTimedTextTrackIndex(int32_t index);
- status_t setParameter(int key, const Parcel &request);
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
private:
- enum TextType {
- kNoText = 0,
- kInBandText = 1,
- kOutOfBandText = 2,
+ enum {
+ kWhatPause = 'paus',
+ kWhatSeek = 'seek',
+ kWhatSendSubtitle = 'send',
+ kWhatSetSource = 'ssrc',
};
- Mutex mLock;
-
- sp<MediaSource> mSource;
- sp<DataSource> mOutOfBandSource;
-
- bool mSeeking;
- int64_t mSeekTimeUs;
-
- bool mStarted;
-
- sp<TimedEventQueue::Event> mTextEvent;
- bool mTextEventPending;
-
- TimedEventQueue *mQueue;
+ // To add Parcel into an AMessage as an object, it should be 'RefBase'.
+ struct ParcelEvent : public RefBase {
+ Parcel parcel;
+ };
wp<MediaPlayerBase> mListener;
- AwesomePlayer *mObserver;
+ sp<TimedTextSource> mSource;
+ int32_t mSendSubtitleGeneration;
- MediaBuffer *mTextBuffer;
- Parcel mData;
-
- // for in-band timed text
- Vector<sp<MediaSource> > mTextTrackVector;
-
- // for out-of-band timed text
- struct OutOfBandText {
- TimedTextParser::FileType type;
- sp<DataSource> source;
- };
- Vector<OutOfBandText > mTextOutOfBandVector;
-
- sp<TimedTextParser> mTextParser;
- AString mText;
-
- TextType mTextType;
-
- void reset();
-
+ void doSeekAndRead(int64_t seekTimeUs);
+ void doRead(MediaSource::ReadOptions* options = NULL);
void onTextEvent();
- void postTextEvent(int64_t delayUs = -1);
- void cancelTextEvent();
-
+ void postTextEvent(const sp<ParcelEvent>& parcel = NULL, int64_t timeUs = -1);
void notifyListener(int msg, const Parcel *parcel = NULL);
- status_t extractAndAppendLocalDescriptions(int64_t timeUs);
- status_t extractAndSendGlobalDescriptions();
-
DISALLOW_EVIL_CONSTRUCTORS(TimedTextPlayer);
};
diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.cpp b/media/libstagefright/timedtext/TimedTextSRTSource.cpp
new file mode 100644
index 0000000..3752d34
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextSRTSource.cpp
@@ -0,0 +1,275 @@
+ /*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "TimedTextSRTSource"
+#include <utils/Log.h>
+
+#include <binder/Parcel.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+
+#include "TimedTextSRTSource.h"
+#include "TextDescriptions.h"
+
+namespace android {
+
+TimedTextSRTSource::TimedTextSRTSource(const sp<DataSource>& dataSource)
+ : mSource(dataSource),
+ mIndex(0) {
+}
+
+TimedTextSRTSource::~TimedTextSRTSource() {
+}
+
+status_t TimedTextSRTSource::start() {
+ status_t err = scanFile();
+ if (err != OK) {
+ reset();
+ }
+ return err;
+}
+
+void TimedTextSRTSource::reset() {
+ mTextVector.clear();
+ mIndex = 0;
+}
+
+status_t TimedTextSRTSource::stop() {
+ reset();
+ return OK;
+}
+
+status_t TimedTextSRTSource::read(
+ int64_t *timeUs,
+ Parcel *parcel,
+ const MediaSource::ReadOptions *options) {
+ int64_t endTimeUs;
+ AString text;
+ status_t err = getText(options, &text, timeUs, &endTimeUs);
+ if (err != OK) {
+ return err;
+ }
+
+ if (*timeUs > 0) {
+ extractAndAppendLocalDescriptions(*timeUs, text, parcel);
+ }
+ return OK;
+}
+
+status_t TimedTextSRTSource::scanFile() {
+ off64_t offset = 0;
+ int64_t startTimeUs;
+ bool endOfFile = false;
+
+ while (!endOfFile) {
+ TextInfo info;
+ status_t err = getNextSubtitleInfo(&offset, &startTimeUs, &info);
+ switch (err) {
+ case OK:
+ mTextVector.add(startTimeUs, info);
+ break;
+ case ERROR_END_OF_STREAM:
+ endOfFile = true;
+ break;
+ default:
+ return err;
+ }
+ }
+ if (mTextVector.isEmpty()) {
+ return ERROR_MALFORMED;
+ }
+ return OK;
+}
+
+/* SRT format:
+ * Subtitle number
+ * Start time --> End time
+ * Text of subtitle (one or more lines)
+ * Blank lines
+ *
+ * .srt file example:
+ * 1
+ * 00:00:20,000 --> 00:00:24,400
+ * Altocumulus clouds occr between six thousand
+ *
+ * 2
+ * 00:00:24,600 --> 00:00:27,800
+ * and twenty thousand feet above ground level.
+ */
+status_t TimedTextSRTSource::getNextSubtitleInfo(
+ off64_t *offset, int64_t *startTimeUs, TextInfo *info) {
+ AString data;
+ status_t err;
+
+ // To skip blank lines.
+ do {
+ if ((err = readNextLine(offset, &data)) != OK) {
+ return err;
+ }
+ data.trim();
+ } while (data.empty());
+
+ // Just ignore the first non-blank line which is subtitle sequence number.
+ if ((err = readNextLine(offset, &data)) != OK) {
+ return err;
+ }
+ int hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
+ // the start time format is: hours:minutes:seconds,milliseconds
+ // 00:00:24,600 --> 00:00:27,800
+ if (sscanf(data.c_str(), "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d",
+ &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) {
+ return ERROR_MALFORMED;
+ }
+
+ *startTimeUs = ((hour1 * 3600 + min1 * 60 + sec1) * 1000 + msec1) * 1000ll;
+ info->endTimeUs = ((hour2 * 3600 + min2 * 60 + sec2) * 1000 + msec2) * 1000ll;
+ if (info->endTimeUs <= *startTimeUs) {
+ return ERROR_MALFORMED;
+ }
+
+ info->offset = *offset;
+ bool needMoreData = true;
+ while (needMoreData) {
+ if ((err = readNextLine(offset, &data)) != OK) {
+ if (err == ERROR_END_OF_STREAM) {
+ needMoreData = false;
+ } else {
+ return err;
+ }
+ }
+
+ if (needMoreData) {
+ data.trim();
+ if (data.empty()) {
+ // it's an empty line used to separate two subtitles
+ needMoreData = false;
+ }
+ }
+ }
+ info->textLen = *offset - info->offset;
+ return OK;
+}
+
+status_t TimedTextSRTSource::readNextLine(off64_t *offset, AString *data) {
+ data->clear();
+ while (true) {
+ ssize_t readSize;
+ char character;
+ if ((readSize = mSource->readAt(*offset, &character, 1)) < 1) {
+ if (readSize == 0) {
+ return ERROR_END_OF_STREAM;
+ }
+ return ERROR_IO;
+ }
+
+ (*offset)++;
+
+ // a line could end with CR, LF or CR + LF
+ if (character == 10) {
+ break;
+ } else if (character == 13) {
+ if ((readSize = mSource->readAt(*offset, &character, 1)) < 1) {
+ if (readSize == 0) { // end of the stream
+ return OK;
+ }
+ return ERROR_IO;
+ }
+
+ (*offset)++;
+ if (character != 10) {
+ (*offset)--;
+ }
+ break;
+ }
+ data->append(character);
+ }
+ return OK;
+}
+
+status_t TimedTextSRTSource::getText(
+ const MediaSource::ReadOptions *options,
+ AString *text, int64_t *startTimeUs, int64_t *endTimeUs) {
+ text->clear();
+ int64_t seekTimeUs;
+ MediaSource::ReadOptions::SeekMode mode;
+ if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) {
+ int64_t lastEndTimeUs =
+ mTextVector.valueAt(mTextVector.size() - 1).endTimeUs;
+ int64_t firstStartTimeUs = mTextVector.keyAt(0);
+ if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) {
+ return ERROR_OUT_OF_RANGE;
+ } else if (seekTimeUs < firstStartTimeUs) {
+ mIndex = 0;
+ } else {
+ // binary search
+ ssize_t low = 0;
+ ssize_t high = mTextVector.size() - 1;
+ ssize_t mid = 0;
+ int64_t currTimeUs;
+
+ while (low <= high) {
+ mid = low + (high - low)/2;
+ currTimeUs = mTextVector.keyAt(mid);
+ const int diff = currTimeUs - seekTimeUs;
+
+ if (diff == 0) {
+ break;
+ } else if (diff < 0) {
+ low = mid + 1;
+ } else {
+ if ((high == mid + 1)
+ && (seekTimeUs < mTextVector.keyAt(high))) {
+ break;
+ }
+ high = mid - 1;
+ }
+ }
+ mIndex = mid;
+ }
+ }
+ const TextInfo &info = mTextVector.valueAt(mIndex);
+ *startTimeUs = mTextVector.keyAt(mIndex);
+ *endTimeUs = info.endTimeUs;
+ mIndex++;
+
+ char *str = new char[info.textLen];
+ if (mSource->readAt(info.offset, str, info.textLen) < info.textLen) {
+ delete[] str;
+ return ERROR_IO;
+ }
+ text->append(str, info.textLen);
+ delete[] str;
+ return OK;
+}
+
+status_t TimedTextSRTSource::extractAndAppendLocalDescriptions(
+ int64_t timeUs, const AString &text, Parcel *parcel) {
+ const void *data = text.c_str();
+ size_t size = text.size();
+ int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS |
+ TextDescriptions::OUT_OF_BAND_TEXT_SRT;
+
+ if (size > 0) {
+ return TextDescriptions::getParcelOfDescriptions(
+ (const uint8_t *)data, size, flag, timeUs / 1000, parcel);
+ }
+ return OK;
+}
+
+} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.h b/media/libstagefright/timedtext/TimedTextSRTSource.h
new file mode 100644
index 0000000..a0734d9
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextSRTSource.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef TIMED_TEXT_SRT_SOURCE_H_
+#define TIMED_TEXT_SRT_SOURCE_H_
+
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/Compat.h> // off64_t
+
+#include "TimedTextSource.h"
+
+namespace android {
+
+class AString;
+class DataSource;
+class MediaBuffer;
+class Parcel;
+
+class TimedTextSRTSource : public TimedTextSource {
+ public:
+ TimedTextSRTSource(const sp<DataSource>& dataSource);
+ virtual status_t start();
+ virtual status_t stop();
+ virtual status_t read(
+ int64_t *timeUs,
+ Parcel *parcel,
+ const MediaSource::ReadOptions *options = NULL);
+
+ protected:
+ virtual ~TimedTextSRTSource();
+
+ private:
+ sp<DataSource> mSource;
+
+ struct TextInfo {
+ int64_t endTimeUs;
+ // The offset of the text in the original file.
+ off64_t offset;
+ int textLen;
+ };
+
+ int mIndex;
+ KeyedVector<int64_t, TextInfo> mTextVector;
+
+ void reset();
+ status_t scanFile();
+ status_t getNextSubtitleInfo(
+ off64_t *offset, int64_t *startTimeUs, TextInfo *info);
+ status_t readNextLine(off64_t *offset, AString *data);
+ status_t getText(
+ const MediaSource::ReadOptions *options,
+ AString *text, int64_t *startTimeUs, int64_t *endTimeUs);
+ status_t extractAndAppendLocalDescriptions(
+ int64_t timeUs, const AString &text, Parcel *parcel);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TimedTextSRTSource);
+};
+
+} // namespace android
+
+#endif // TIMED_TEXT_SRT_SOURCE_H_
diff --git a/media/libstagefright/timedtext/TimedTextSource.cpp b/media/libstagefright/timedtext/TimedTextSource.cpp
new file mode 100644
index 0000000..9efe67c
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextSource.cpp
@@ -0,0 +1,53 @@
+ /*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "TimedTextSource"
+#include <utils/Log.h>
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaSource.h>
+
+#include "TimedTextSource.h"
+
+#include "TimedTextInBandSource.h"
+#include "TimedTextSRTSource.h"
+
+namespace android {
+
+// static
+sp<TimedTextSource> TimedTextSource::CreateTimedTextSource(
+ const sp<MediaSource>& mediaSource) {
+ return new TimedTextInBandSource(mediaSource);
+}
+
+// static
+sp<TimedTextSource> TimedTextSource::CreateTimedTextSource(
+ const sp<DataSource>& dataSource, FileType filetype) {
+ switch(filetype) {
+ case OUT_OF_BAND_FILE_SRT:
+ return new TimedTextSRTSource(dataSource);
+ case OUT_OF_BAND_FILE_SMI:
+ // TODO: Implement for SMI.
+ ALOGE("Supporting SMI is not implemented yet");
+ break;
+ default:
+ ALOGE("Undefined subtitle format. : %d", filetype);
+ }
+ return NULL;
+}
+
+} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextSource.h b/media/libstagefright/timedtext/TimedTextSource.h
new file mode 100644
index 0000000..06bae71
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextSource.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef TIMED_TEXT_SOURCE_H_
+#define TIMED_TEXT_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h> // for DISALLOW_XXX macro.
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h> // for MediaSource::ReadOptions
+#include <utils/RefBase.h>
+
+namespace android {
+
+class DataSource;
+class Parcel;
+
+class TimedTextSource : public RefBase {
+ public:
+ enum FileType {
+ OUT_OF_BAND_FILE_SRT = 1,
+ OUT_OF_BAND_FILE_SMI = 2,
+ };
+ static sp<TimedTextSource> CreateTimedTextSource(
+ const sp<MediaSource>& source);
+ static sp<TimedTextSource> CreateTimedTextSource(
+ const sp<DataSource>& source, FileType filetype);
+ TimedTextSource() {}
+ virtual status_t start() = 0;
+ virtual status_t stop() = 0;
+ // Returns subtitle parcel and its start time.
+ virtual status_t read(
+ int64_t *timeUs,
+ Parcel *parcel,
+ const MediaSource::ReadOptions *options = NULL) = 0;
+ virtual status_t extractGlobalDescriptions(Parcel *parcel) {
+ return INVALID_OPERATION;
+ }
+
+ protected:
+ virtual ~TimedTextSource() { }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(TimedTextSource);
+};
+
+} // namespace android
+
+#endif // TIMED_TEXT_SOURCE_H_
diff --git a/opengl/libs/GLES_trace/src/gltrace_fixup.cpp b/opengl/libs/GLES_trace/src/gltrace_fixup.cpp
index daba3ff..871b5dc 100644
--- a/opengl/libs/GLES_trace/src/gltrace_fixup.cpp
+++ b/opengl/libs/GLES_trace/src/gltrace_fixup.cpp
@@ -358,6 +358,14 @@
fixup_addFBContents(context, glmsg, CURRENTLY_BOUND_FB);
}
break;
+ case GLMessage::glPushGroupMarkerEXT:
+ /* void PushGroupMarkerEXT(sizei length, const char *marker); */
+ fixup_CStringPtr(1, glmsg);
+ break;
+ case GLMessage::glInsertEventMarkerEXT:
+ /* void InsertEventMarkerEXT(sizei length, const char *marker); */
+ fixup_CStringPtr(1, glmsg);
+ break;
default:
break;
}
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 2d856ad..21b9bd6 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1740,7 +1740,7 @@
// FIXME - Current mixer implementation only supports stereo output: Always
// Allocate a stereo buffer even if HW output is mono.
- if (mMixBuffer != NULL) delete[] mMixBuffer;
+ delete[] mMixBuffer;
mMixBuffer = new int16_t[mFrameCount * 2];
memset(mMixBuffer, 0, mFrameCount * 2 * sizeof(int16_t));
@@ -2461,6 +2461,8 @@
}
if (status == NO_ERROR && reconfig) {
delete mAudioMixer;
+ // for safety in case readOutputParameters() accesses mAudioMixer (it doesn't)
+ mAudioMixer = NULL;
readOutputParameters();
mAudioMixer = new AudioMixer(mFrameCount, mSampleRate);
for (size_t i = 0; i < mTracks.size() ; i++) {
@@ -4282,10 +4284,8 @@
AudioFlinger::RecordThread::~RecordThread()
{
delete[] mRsmpInBuffer;
- if (mResampler != NULL) {
- delete mResampler;
- delete[] mRsmpOutBuffer;
- }
+ delete mResampler;
+ delete[] mRsmpOutBuffer;
}
void AudioFlinger::RecordThread::onFirstRef()
@@ -4829,9 +4829,11 @@
void AudioFlinger::RecordThread::readInputParameters()
{
- if (mRsmpInBuffer) delete mRsmpInBuffer;
- if (mRsmpOutBuffer) delete mRsmpOutBuffer;
- if (mResampler) delete mResampler;
+ delete mRsmpInBuffer;
+ // mRsmpInBuffer is always assigned a new[] below
+ delete mRsmpOutBuffer;
+ mRsmpOutBuffer = NULL;
+ delete mResampler;
mResampler = NULL;
mSampleRate = mInput->stream->common.get_sample_rate(&mInput->stream->common);
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index a8102e5..a01c6a8 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -1127,9 +1127,7 @@
}
}
- if (buff != NULL) {
- delete [] buff;
- }
+ delete [] buff;
}
#endif
diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp
index 2df1385..7695d2b 100644
--- a/services/audioflinger/AudioPolicyService.cpp
+++ b/services/audioflinger/AudioPolicyService.cpp
@@ -649,7 +649,7 @@
release_wake_lock(mName.string());
}
mAudioCommands.clear();
- if (mpToneGenerator != NULL) delete mpToneGenerator;
+ delete mpToneGenerator;
}
void AudioPolicyService::AudioCommandThread::onFirstRef()
@@ -682,8 +682,7 @@
ToneData *data = (ToneData *)command->mParam;
ALOGV("AudioCommandThread() processing start tone %d on stream %d",
data->mType, data->mStream);
- if (mpToneGenerator != NULL)
- delete mpToneGenerator;
+ delete mpToneGenerator;
mpToneGenerator = new ToneGenerator(data->mStream, 1.0);
mpToneGenerator->startTone(data->mType);
delete data;
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 1f8cf63..296c95e 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -831,7 +831,7 @@
ALOGV("Opening device: %s", devicePath);
- int fd = open(devicePath, O_RDWR);
+ int fd = open(devicePath, O_RDWR | O_CLOEXEC);
if(fd < 0) {
ALOGE("could not open %s, %s\n", devicePath, strerror(errno));
return -1;
@@ -1063,14 +1063,20 @@
return -1;
}
+ // Enable wake-lock behavior on kernels that support it.
+ // TODO: Only need this for devices that can really wake the system.
+ bool usingSuspendBlock = ioctl(fd, EVIOCSSUSPENDBLOCK, 1) == 0;
+
ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, "
- "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s",
+ "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, "
+ "usingSuspendBlock=%s",
deviceId, fd, devicePath, device->identifier.name.string(),
device->classes,
device->configurationFile.string(),
device->keyMap.keyLayoutFile.string(),
device->keyMap.keyCharacterMapFile.string(),
- toString(mBuiltInKeyboardId == deviceId));
+ toString(mBuiltInKeyboardId == deviceId),
+ toString(usingSuspendBlock));
mDevices.add(deviceId, device);
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 23fa94a..8bda755 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -46,7 +46,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.view.IWindow;
-import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -96,6 +95,10 @@
private static final int DO_SET_SERVICE_INFO = 10;
+ public static final int ACTIVE_WINDOW_ID = -1;
+
+ public static final long ROOT_NODE_ID = -1;
+
private static int sNextWindowId;
final HandlerCaller mCaller;
@@ -467,7 +470,8 @@
}
}
- public void registerEventListener(IEventListener listener) {
+ public void registerUiTestAutomationService(IEventListener listener,
+ AccessibilityServiceInfo accessibilityServiceInfo) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
FUNCTION_REGISTER_EVENT_LISTENER);
ComponentName componentName = new ComponentName("foo.bar",
@@ -490,13 +494,23 @@
}
}
// Hook the automation service up.
- AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
- accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
- accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
Service service = new Service(componentName, accessibilityServiceInfo, true);
service.onServiceConnected(componentName, listener.asBinder());
}
+ public void unregisterUiTestAutomationService(IEventListener listener) {
+ synchronized (mLock) {
+ final int serviceCount = mServices.size();
+ for (int i = 0; i < serviceCount; i++) {
+ Service service = mServices.get(i);
+ if (service.mServiceInterface == listener && service.mIsAutomation) {
+ // Automation service is not bound, so pretend it died to perform clean up.
+ service.binderDied();
+ }
+ }
+ }
+ }
+
/**
* Removes an AccessibilityInteractionConnection.
*
@@ -1070,10 +1084,11 @@
}
}
- public float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId,
- int interactionId, IAccessibilityInteractionConnectionCallback callback,
- long interrogatingTid)
+ public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, int viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
+ final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
IAccessibilityInteractionConnection connection = null;
synchronized (mLock) {
mSecurityPolicy.enforceCanRetrieveWindowContent(this);
@@ -1081,12 +1096,8 @@
if (!permissionGranted) {
return 0;
} else {
- connection = getConnectionToRetrievalAllowingWindowLocked();
+ connection = getConnectionLocked(resolvedWindowId);
if (connection == null) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "No interaction connection to a retrieve "
- + "allowing window.");
- }
return 0;
}
}
@@ -1094,44 +1105,33 @@
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
- connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, callback,
- interrogatingPid, interrogatingTid);
+ connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId,
+ interactionId, callback, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
- Slog.e(LOG_TAG, "Error finding node.");
+ Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId().");
}
} finally {
Binder.restoreCallingIdentity(identityToken);
}
- return getCompatibilityScale(mSecurityPolicy.getRetrievalAllowingWindowLocked());
+ return getCompatibilityScale(resolvedWindowId);
}
- public float findAccessibilityNodeInfosByTextInActiveWindow(
- String text, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long threadId)
- throws RemoteException {
- return findAccessibilityNodeInfosByText(text,
- mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID, interactionId, callback,
- threadId);
- }
-
- public float findAccessibilityNodeInfosByText(String text,
- int accessibilityWindowId, long accessibilityNodeId, int interactionId,
+ public float findAccessibilityNodeInfosByText(int accessibilityWindowId,
+ long accessibilityNodeId, String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
+ final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
IAccessibilityInteractionConnection connection = null;
synchronized (mLock) {
mSecurityPolicy.enforceCanRetrieveWindowContent(this);
final boolean permissionGranted =
- mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
if (!permissionGranted) {
return 0;
} else {
- connection = getConnectionToRetrievalAllowingWindowLocked();
+ connection = getConnectionLocked(resolvedWindowId);
if (connection == null) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "No interaction connection to focused window.");
- }
return 0;
}
}
@@ -1139,40 +1139,35 @@
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
- connection.findAccessibilityNodeInfosByText(text, accessibilityNodeId,
+ connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,
interactionId, callback, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
- Slog.e(LOG_TAG, "Error finding node.");
+ Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
}
} finally {
Binder.restoreCallingIdentity(identityToken);
}
- return getCompatibilityScale(accessibilityWindowId);
+ return getCompatibilityScale(resolvedWindowId);
}
public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
+ final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
IAccessibilityInteractionConnection connection = null;
synchronized (mLock) {
mSecurityPolicy.enforceCanRetrieveWindowContent(this);
final boolean permissionGranted =
- mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
if (!permissionGranted) {
return 0;
} else {
- AccessibilityConnectionWrapper wrapper =
- mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId);
- if (wrapper == null) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "No interaction connection to window: "
- + accessibilityWindowId);
- }
+ connection = getConnectionLocked(resolvedWindowId);
+ if (connection == null) {
return 0;
}
- connection = wrapper.mConnection;
}
}
final int interrogatingPid = Binder.getCallingPid();
@@ -1182,35 +1177,29 @@
interactionId, callback, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
- Slog.e(LOG_TAG, "Error requesting node with accessibilityNodeId: "
- + accessibilityNodeId);
+ Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
}
} finally {
Binder.restoreCallingIdentity(identityToken);
}
- return getCompatibilityScale(accessibilityWindowId);
+ return getCompatibilityScale(resolvedWindowId);
}
public boolean performAccessibilityAction(int accessibilityWindowId,
long accessibilityNodeId, int action, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) {
+ final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
IAccessibilityInteractionConnection connection = null;
synchronized (mLock) {
final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this,
- accessibilityWindowId, action);
+ resolvedWindowId, action);
if (!permissionGranted) {
return false;
} else {
- AccessibilityConnectionWrapper wrapper =
- mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId);
- if (wrapper == null) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "No interaction connection to window: "
- + accessibilityWindowId);
- }
+ connection = getConnectionLocked(resolvedWindowId);
+ if (connection == null) {
return false;
}
- connection = wrapper.mConnection;
}
}
final int interrogatingPid = Binder.getCallingPid();
@@ -1220,8 +1209,7 @@
callback, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
- Slog.e(LOG_TAG, "Error requesting node with accessibilityNodeId: "
- + accessibilityNodeId);
+ Slog.e(LOG_TAG, "Error calling performAccessibilityAction()");
}
} finally {
Binder.restoreCallingIdentity(identityToken);
@@ -1265,14 +1253,26 @@
}
}
- private IAccessibilityInteractionConnection getConnectionToRetrievalAllowingWindowLocked() {
- final int windowId = mSecurityPolicy.getRetrievalAllowingWindowLocked();
+ private IAccessibilityInteractionConnection getConnectionLocked(int windowId) {
if (DEBUG) {
Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
}
- AccessibilityConnectionWrapper wrapper =
- mWindowIdToInteractionConnectionWrapperMap.get(windowId);
- return (wrapper != null) ? wrapper.mConnection : null;
+ AccessibilityConnectionWrapper wrapper = mWindowIdToInteractionConnectionWrapperMap.get(
+ windowId);
+ if (wrapper != null && wrapper.mConnection != null) {
+ return wrapper.mConnection;
+ }
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "No interaction connection to window: " + windowId);
+ }
+ return null;
+ }
+
+ private int resolveAccessibilityWindowId(int accessibilityWindowId) {
+ if (accessibilityWindowId == ACTIVE_WINDOW_ID) {
+ return mSecurityPolicy.mRetrievalAlowingWindowId;
+ }
+ return accessibilityWindowId;
}
private float getCompatibilityScale(int windowId) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index db0a736..3a6a3de 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -158,6 +158,7 @@
static final boolean DEBUG_OOM_ADJ = localLOGV || false;
static final boolean DEBUG_TRANSITION = localLOGV || false;
static final boolean DEBUG_BROADCAST = localLOGV || false;
+ static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false;
static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
static final boolean DEBUG_SERVICE = localLOGV || false;
static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false;
@@ -221,7 +222,8 @@
static final int CPU_MIN_CHECK_DURATION = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000;
// How long we allow a receiver to run before giving up on it.
- static final int BROADCAST_TIMEOUT = 10*1000;
+ static final int BROADCAST_FG_TIMEOUT = 10*1000;
+ static final int BROADCAST_BG_TIMEOUT = 60*1000;
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
@@ -276,33 +278,823 @@
= new ArrayList<PendingActivityLaunch>();
/**
- * List of all active broadcasts that are to be executed immediately
- * (without waiting for another broadcast to finish). Currently this only
- * contains broadcasts to registered receivers, to avoid spinning up
- * a bunch of processes to execute IntentReceiver components.
+ * BROADCASTS
+ *
+ * We keep two broadcast queues and associated bookkeeping, one for those at
+ * foreground priority, and one for normal (background-priority) broadcasts.
*/
- final ArrayList<BroadcastRecord> mParallelBroadcasts
- = new ArrayList<BroadcastRecord>();
+ public class BroadcastQueue {
+ static final String TAG = "BroadcastQueue";
- /**
- * List of all active broadcasts that are to be executed one at a time.
- * The object at the top of the list is the currently activity broadcasts;
- * those after it are waiting for the top to finish..
- */
- final ArrayList<BroadcastRecord> mOrderedBroadcasts
- = new ArrayList<BroadcastRecord>();
+ static final int MAX_BROADCAST_HISTORY = 25;
- /**
- * Historical data of past broadcasts, for debugging.
- */
- static final int MAX_BROADCAST_HISTORY = 25;
- final BroadcastRecord[] mBroadcastHistory
- = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+ /**
+ * Recognizable moniker for this queue
+ */
+ String mQueueName;
- /**
- * Set when we current have a BROADCAST_INTENT_MSG in flight.
- */
- boolean mBroadcastsScheduled = false;
+ /**
+ * Timeout period for this queue's broadcasts
+ */
+ long mTimeoutPeriod;
+
+ /**
+ * Lists of all active broadcasts that are to be executed immediately
+ * (without waiting for another broadcast to finish). Currently this only
+ * contains broadcasts to registered receivers, to avoid spinning up
+ * a bunch of processes to execute IntentReceiver components. Background-
+ * and foreground-priority broadcasts are queued separately.
+ */
+ final ArrayList<BroadcastRecord> mParallelBroadcasts
+ = new ArrayList<BroadcastRecord>();
+ /**
+ * List of all active broadcasts that are to be executed one at a time.
+ * The object at the top of the list is the currently activity broadcasts;
+ * those after it are waiting for the top to finish. As with parallel
+ * broadcasts, separate background- and foreground-priority queues are
+ * maintained.
+ */
+ final ArrayList<BroadcastRecord> mOrderedBroadcasts
+ = new ArrayList<BroadcastRecord>();
+
+ /**
+ * Historical data of past broadcasts, for debugging.
+ */
+ final BroadcastRecord[] mBroadcastHistory
+ = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+
+ /**
+ * Set when we current have a BROADCAST_INTENT_MSG in flight.
+ */
+ boolean mBroadcastsScheduled = false;
+
+ /**
+ * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
+ */
+ boolean mPendingBroadcastTimeoutMessage;
+
+ /**
+ * Intent broadcasts that we have tried to start, but are
+ * waiting for the application's process to be created. We only
+ * need one per scheduling class (instead of a list) because we always
+ * process broadcasts one at a time, so no others can be started while
+ * waiting for this one.
+ */
+ BroadcastRecord mPendingBroadcast = null;
+
+ /**
+ * The receiver index that is pending, to restart the broadcast if needed.
+ */
+ int mPendingBroadcastRecvIndex;
+
+ BroadcastQueue(String name, long timeoutPeriod) {
+ mQueueName = name;
+ mTimeoutPeriod = timeoutPeriod;
+ }
+
+ public boolean isPendingBroadcastProcessLocked(int pid) {
+ return mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid;
+ }
+
+ public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
+ mParallelBroadcasts.add(r);
+ }
+
+ public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
+ mOrderedBroadcasts.add(r);
+ }
+
+ public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) {
+ for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {
+ if (r.intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "***** DROPPING PARALLEL ["
+ + mQueueName + "]: " + r.intent);
+ mParallelBroadcasts.set(i, r);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public final boolean replaceOrderedBroadcastLocked(BroadcastRecord r) {
+ for (int i=mOrderedBroadcasts.size()-1; i>0; i--) {
+ if (r.intent.filterEquals(mOrderedBroadcasts.get(i).intent)) {
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "***** DROPPING ORDERED ["
+ + mQueueName + "]: " + r.intent);
+ mOrderedBroadcasts.set(i, r);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
+ boolean didSomething = false;
+ final BroadcastRecord br = mPendingBroadcast;
+ if (br != null && br.curApp.pid == app.pid) {
+ try {
+ mPendingBroadcast = null;
+ processCurBroadcastLocked(br, app);
+ didSomething = true;
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception in new application when starting receiver "
+ + br.curComponent.flattenToShortString(), e);
+ logBroadcastReceiverDiscardLocked(br);
+ finishReceiverLocked(br, br.resultCode, br.resultData,
+ br.resultExtras, br.resultAbort, true);
+ scheduleBroadcastsLocked();
+ // We need to reset the state if we fails to start the receiver.
+ br.state = BroadcastRecord.IDLE;
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+ return didSomething;
+ }
+
+ public void skipPendingBroadcastLocked(int pid) {
+ final BroadcastRecord br = mPendingBroadcast;
+ if (br != null && br.curApp.pid == pid) {
+ br.state = BroadcastRecord.IDLE;
+ br.nextReceiver = mPendingBroadcastRecvIndex;
+ mPendingBroadcast = null;
+ scheduleBroadcastsLocked();
+ }
+ }
+
+ public void skipCurrentReceiverLocked(ProcessRecord app) {
+ boolean reschedule = false;
+ BroadcastRecord r = app.curReceiver;
+ if (r != null) {
+ // The current broadcast is waiting for this app's receiver
+ // to be finished. Looks like that's not going to happen, so
+ // let the broadcast continue.
+ logBroadcastReceiverDiscardLocked(r);
+ finishReceiverLocked(r, r.resultCode, r.resultData,
+ r.resultExtras, r.resultAbort, true);
+ reschedule = true;
+ }
+
+ r = mPendingBroadcast;
+ if (r != null && r.curApp == app) {
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "[" + mQueueName + "] skip & discard pending app " + r);
+ logBroadcastReceiverDiscardLocked(r);
+ finishReceiverLocked(r, r.resultCode, r.resultData,
+ r.resultExtras, r.resultAbort, true);
+ reschedule = true;
+ }
+ if (reschedule) {
+ scheduleBroadcastsLocked();
+ }
+ }
+
+ public void scheduleBroadcastsLocked() {
+ if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts ["
+ + mQueueName + "]: current="
+ + mBroadcastsScheduled);
+
+ if (mBroadcastsScheduled) {
+ return;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
+ mBroadcastsScheduled = true;
+ }
+
+ public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) {
+ if (mOrderedBroadcasts.size() > 0) {
+ final BroadcastRecord r = mOrderedBroadcasts.get(0);
+ if (r != null && r.receiver == receiver) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
+ String resultData, Bundle resultExtras, boolean resultAbort,
+ boolean explicit) {
+ int state = r.state;
+ r.state = BroadcastRecord.IDLE;
+ if (state == BroadcastRecord.IDLE) {
+ if (explicit) {
+ Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE");
+ }
+ }
+ r.receiver = null;
+ r.intent.setComponent(null);
+ if (r.curApp != null) {
+ r.curApp.curReceiver = null;
+ }
+ if (r.curFilter != null) {
+ r.curFilter.receiverList.curBroadcast = null;
+ }
+ r.curFilter = null;
+ r.curApp = null;
+ r.curComponent = null;
+ r.curReceiver = null;
+ mPendingBroadcast = null;
+
+ r.resultCode = resultCode;
+ r.resultData = resultData;
+ r.resultExtras = resultExtras;
+ r.resultAbort = resultAbort;
+
+ // We will process the next receiver right now if this is finishing
+ // an app receiver (which is always asynchronous) or after we have
+ // come back from calling a receiver.
+ return state == BroadcastRecord.APP_RECEIVE
+ || state == BroadcastRecord.CALL_DONE_RECEIVE;
+ }
+
+ private final void processNextBroadcast(boolean fromMsg) {
+ synchronized(ActivityManagerService.this) {
+ BroadcastRecord r;
+
+ if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast ["
+ + mQueueName + "]: "
+ + mParallelBroadcasts.size() + " broadcasts, "
+ + mOrderedBroadcasts.size() + " ordered broadcasts");
+
+ updateCpuStats();
+
+ if (fromMsg) {
+ mBroadcastsScheduled = false;
+ }
+
+ // First, deliver any non-serialized broadcasts right away.
+ while (mParallelBroadcasts.size() > 0) {
+ r = mParallelBroadcasts.remove(0);
+ r.dispatchTime = SystemClock.uptimeMillis();
+ r.dispatchClockTime = System.currentTimeMillis();
+ final int N = r.receivers.size();
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast ["
+ + mQueueName + "] " + r);
+ for (int i=0; i<N; i++) {
+ Object target = r.receivers.get(i);
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "Delivering non-ordered on [" + mQueueName + "] to registered "
+ + target + ": " + r);
+ deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
+ }
+ addBroadcastToHistoryLocked(r);
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast ["
+ + mQueueName + "] " + r);
+ }
+
+ // Now take care of the next serialized one...
+
+ // If we are waiting for a process to come up to handle the next
+ // broadcast, then do nothing at this point. Just in case, we
+ // check that the process we're waiting for still exists.
+ if (mPendingBroadcast != null) {
+ if (DEBUG_BROADCAST_LIGHT) {
+ Slog.v(TAG, "processNextBroadcast ["
+ + mQueueName + "]: waiting for "
+ + mPendingBroadcast.curApp);
+ }
+
+ boolean isDead;
+ synchronized (mPidsSelfLocked) {
+ isDead = (mPidsSelfLocked.get(mPendingBroadcast.curApp.pid) == null);
+ }
+ if (!isDead) {
+ // It's still alive, so keep waiting
+ return;
+ } else {
+ Slog.w(TAG, "pending app ["
+ + mQueueName + "]" + mPendingBroadcast.curApp
+ + " died before responding to broadcast");
+ mPendingBroadcast.state = BroadcastRecord.IDLE;
+ mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
+ mPendingBroadcast = null;
+ }
+ }
+
+ boolean looped = false;
+
+ do {
+ if (mOrderedBroadcasts.size() == 0) {
+ // No more broadcasts pending, so all done!
+ scheduleAppGcsLocked();
+ if (looped) {
+ // If we had finished the last ordered broadcast, then
+ // make sure all processes have correct oom and sched
+ // adjustments.
+ updateOomAdjLocked();
+ }
+ return;
+ }
+ r = mOrderedBroadcasts.get(0);
+ boolean forceReceive = false;
+
+ // Ensure that even if something goes awry with the timeout
+ // detection, we catch "hung" broadcasts here, discard them,
+ // and continue to make progress.
+ //
+ // This is only done if the system is ready so that PRE_BOOT_COMPLETED
+ // receivers don't get executed with timeouts. They're intended for
+ // one time heavy lifting after system upgrades and can take
+ // significant amounts of time.
+ int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+ if (mProcessesReady && r.dispatchTime > 0) {
+ long now = SystemClock.uptimeMillis();
+ if ((numReceivers > 0) &&
+ (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
+ Slog.w(TAG, "Hung broadcast ["
+ + mQueueName + "] discarded after timeout failure:"
+ + " now=" + now
+ + " dispatchTime=" + r.dispatchTime
+ + " startTime=" + r.receiverTime
+ + " intent=" + r.intent
+ + " numReceivers=" + numReceivers
+ + " nextReceiver=" + r.nextReceiver
+ + " state=" + r.state);
+ broadcastTimeoutLocked(false); // forcibly finish this broadcast
+ forceReceive = true;
+ r.state = BroadcastRecord.IDLE;
+ }
+ }
+
+ if (r.state != BroadcastRecord.IDLE) {
+ if (DEBUG_BROADCAST) Slog.d(TAG,
+ "processNextBroadcast("
+ + mQueueName + ") called when not idle (state="
+ + r.state + ")");
+ return;
+ }
+
+ if (r.receivers == null || r.nextReceiver >= numReceivers
+ || r.resultAbort || forceReceive) {
+ // No more receivers for this broadcast! Send the final
+ // result if requested...
+ if (r.resultTo != null) {
+ try {
+ if (DEBUG_BROADCAST) {
+ int seq = r.intent.getIntExtra("seq", -1);
+ Slog.i(TAG, "Finishing broadcast ["
+ + mQueueName + "] " + r.intent.getAction()
+ + " seq=" + seq + " app=" + r.callerApp);
+ }
+ performReceiveLocked(r.callerApp, r.resultTo,
+ new Intent(r.intent), r.resultCode,
+ r.resultData, r.resultExtras, false, false);
+ // Set this to null so that the reference
+ // (local and remote) isnt kept in the mBroadcastHistory.
+ r.resultTo = null;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failure ["
+ + mQueueName + "] sending broadcast result of "
+ + r.intent, e);
+ }
+ }
+
+ if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG");
+ cancelBroadcastTimeoutLocked();
+
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast "
+ + r);
+
+ // ... and on to the next...
+ addBroadcastToHistoryLocked(r);
+ mOrderedBroadcasts.remove(0);
+ r = null;
+ looped = true;
+ continue;
+ }
+ } while (r == null);
+
+ // Get the next receiver...
+ int recIdx = r.nextReceiver++;
+
+ // Keep track of when this receiver started, and make sure there
+ // is a timeout message pending to kill it if need be.
+ r.receiverTime = SystemClock.uptimeMillis();
+ if (recIdx == 0) {
+ r.dispatchTime = r.receiverTime;
+ r.dispatchClockTime = System.currentTimeMillis();
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast ["
+ + mQueueName + "] " + r);
+ }
+ if (! mPendingBroadcastTimeoutMessage) {
+ long timeoutTime = r.receiverTime + mTimeoutPeriod;
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "Submitting BROADCAST_TIMEOUT_MSG ["
+ + mQueueName + "] for " + r + " at " + timeoutTime);
+ setBroadcastTimeoutLocked(timeoutTime);
+ }
+
+ Object nextReceiver = r.receivers.get(recIdx);
+ if (nextReceiver instanceof BroadcastFilter) {
+ // Simple case: this is a registered receiver who gets
+ // a direct call.
+ BroadcastFilter filter = (BroadcastFilter)nextReceiver;
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "Delivering ordered ["
+ + mQueueName + "] to registered "
+ + filter + ": " + r);
+ deliverToRegisteredReceiverLocked(r, filter, r.ordered);
+ if (r.receiver == null || !r.ordered) {
+ // The receiver has already finished, so schedule to
+ // process the next one.
+ if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing ["
+ + mQueueName + "]: ordered="
+ + r.ordered + " receiver=" + r.receiver);
+ r.state = BroadcastRecord.IDLE;
+ scheduleBroadcastsLocked();
+ }
+ return;
+ }
+
+ // Hard case: need to instantiate the receiver, possibly
+ // starting its application process to host it.
+
+ ResolveInfo info =
+ (ResolveInfo)nextReceiver;
+
+ boolean skip = false;
+ int perm = checkComponentPermission(info.activityInfo.permission,
+ r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
+ info.activityInfo.exported);
+ if (perm != PackageManager.PERMISSION_GRANTED) {
+ if (!info.activityInfo.exported) {
+ Slog.w(TAG, "Permission Denial: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid=" + r.callingPid
+ + ", uid=" + r.callingUid + ")"
+ + " is not exported from uid " + info.activityInfo.applicationInfo.uid
+ + " due to receiver " + info.activityInfo.packageName
+ + "/" + info.activityInfo.name);
+ } else {
+ Slog.w(TAG, "Permission Denial: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid=" + r.callingPid
+ + ", uid=" + r.callingUid + ")"
+ + " requires " + info.activityInfo.permission
+ + " due to receiver " + info.activityInfo.packageName
+ + "/" + info.activityInfo.name);
+ }
+ skip = true;
+ }
+ if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
+ r.requiredPermission != null) {
+ try {
+ perm = AppGlobals.getPackageManager().
+ checkPermission(r.requiredPermission,
+ info.activityInfo.applicationInfo.packageName);
+ } catch (RemoteException e) {
+ perm = PackageManager.PERMISSION_DENIED;
+ }
+ if (perm != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Permission Denial: receiving "
+ + r.intent + " to "
+ + info.activityInfo.applicationInfo.packageName
+ + " requires " + r.requiredPermission
+ + " due to sender " + r.callerPackage
+ + " (uid " + r.callingUid + ")");
+ skip = true;
+ }
+ }
+ if (r.curApp != null && r.curApp.crashing) {
+ // If the target process is crashing, just skip it.
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "Skipping deliver ordered ["
+ + mQueueName + "] " + r + " to " + r.curApp
+ + ": process crashing");
+ skip = true;
+ }
+
+ if (skip) {
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "Skipping delivery of ordered ["
+ + mQueueName + "] " + r + " for whatever reason");
+ r.receiver = null;
+ r.curFilter = null;
+ r.state = BroadcastRecord.IDLE;
+ scheduleBroadcastsLocked();
+ return;
+ }
+
+ r.state = BroadcastRecord.APP_RECEIVE;
+ String targetProcess = info.activityInfo.processName;
+ r.curComponent = new ComponentName(
+ info.activityInfo.applicationInfo.packageName,
+ info.activityInfo.name);
+ r.curReceiver = info.activityInfo;
+
+ // Broadcast is being executed, its package can't be stopped.
+ try {
+ AppGlobals.getPackageManager().setPackageStoppedState(
+ r.curComponent.getPackageName(), false);
+ } catch (RemoteException e) {
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Failed trying to unstop package "
+ + r.curComponent.getPackageName() + ": " + e);
+ }
+
+ // Is this receiver's application already running?
+ ProcessRecord app = getProcessRecordLocked(targetProcess,
+ info.activityInfo.applicationInfo.uid);
+ if (app != null && app.thread != null) {
+ try {
+ app.addPackage(info.activityInfo.packageName);
+ processCurBroadcastLocked(r, app);
+ return;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception when sending broadcast to "
+ + r.curComponent, e);
+ }
+
+ // If a dead object exception was thrown -- fall through to
+ // restart the application.
+ }
+
+ // Not running -- get it started, to be executed when the app comes up.
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "Need to start app ["
+ + mQueueName + "] " + targetProcess + " for broadcast " + r);
+ if ((r.curApp=startProcessLocked(targetProcess,
+ info.activityInfo.applicationInfo, true,
+ r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
+ "broadcast", r.curComponent,
+ (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0))
+ == null) {
+ // Ah, this recipient is unavailable. Finish it if necessary,
+ // and mark the broadcast record as ready for the next.
+ Slog.w(TAG, "Unable to launch app "
+ + info.activityInfo.applicationInfo.packageName + "/"
+ + info.activityInfo.applicationInfo.uid + " for broadcast "
+ + r.intent + ": process is bad");
+ logBroadcastReceiverDiscardLocked(r);
+ finishReceiverLocked(r, r.resultCode, r.resultData,
+ r.resultExtras, r.resultAbort, true);
+ scheduleBroadcastsLocked();
+ r.state = BroadcastRecord.IDLE;
+ return;
+ }
+
+ mPendingBroadcast = r;
+ mPendingBroadcastRecvIndex = recIdx;
+ }
+ }
+
+ final void setBroadcastTimeoutLocked(long timeoutTime) {
+ if (! mPendingBroadcastTimeoutMessage) {
+ Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
+ mHandler.sendMessageAtTime(msg, timeoutTime);
+ mPendingBroadcastTimeoutMessage = true;
+ }
+ }
+
+ final void cancelBroadcastTimeoutLocked() {
+ if (mPendingBroadcastTimeoutMessage) {
+ mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
+ mPendingBroadcastTimeoutMessage = false;
+ }
+ }
+
+ final void broadcastTimeoutLocked(boolean fromMsg) {
+ if (fromMsg) {
+ mPendingBroadcastTimeoutMessage = false;
+ }
+
+ if (mOrderedBroadcasts.size() == 0) {
+ return;
+ }
+
+ long now = SystemClock.uptimeMillis();
+ BroadcastRecord r = mOrderedBroadcasts.get(0);
+ if (fromMsg) {
+ if (mDidDexOpt) {
+ // Delay timeouts until dexopt finishes.
+ mDidDexOpt = false;
+ long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
+ setBroadcastTimeoutLocked(timeoutTime);
+ return;
+ }
+ if (! mProcessesReady) {
+ // Only process broadcast timeouts if the system is ready. That way
+ // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended
+ // to do heavy lifting for system up.
+ return;
+ }
+
+ long timeoutTime = r.receiverTime + mTimeoutPeriod;
+ if (timeoutTime > now) {
+ // We can observe premature timeouts because we do not cancel and reset the
+ // broadcast timeout message after each receiver finishes. Instead, we set up
+ // an initial timeout then kick it down the road a little further as needed
+ // when it expires.
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "Premature timeout ["
+ + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
+ + timeoutTime);
+ setBroadcastTimeoutLocked(timeoutTime);
+ return;
+ }
+ }
+
+ Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver
+ + ", started " + (now - r.receiverTime) + "ms ago");
+ r.receiverTime = now;
+ r.anrCount++;
+
+ // Current receiver has passed its expiration date.
+ if (r.nextReceiver <= 0) {
+ Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0");
+ return;
+ }
+
+ ProcessRecord app = null;
+ String anrMessage = null;
+
+ Object curReceiver = r.receivers.get(r.nextReceiver-1);
+ Slog.w(TAG, "Receiver during timeout: " + curReceiver);
+ logBroadcastReceiverDiscardLocked(r);
+ if (curReceiver instanceof BroadcastFilter) {
+ BroadcastFilter bf = (BroadcastFilter)curReceiver;
+ if (bf.receiverList.pid != 0
+ && bf.receiverList.pid != MY_PID) {
+ synchronized (ActivityManagerService.this.mPidsSelfLocked) {
+ app = ActivityManagerService.this.mPidsSelfLocked.get(
+ bf.receiverList.pid);
+ }
+ }
+ } else {
+ app = r.curApp;
+ }
+
+ if (app != null) {
+ anrMessage = "Broadcast of " + r.intent.toString();
+ }
+
+ if (mPendingBroadcast == r) {
+ mPendingBroadcast = null;
+ }
+
+ // Move on to the next receiver.
+ finishReceiverLocked(r, r.resultCode, r.resultData,
+ r.resultExtras, r.resultAbort, true);
+ scheduleBroadcastsLocked();
+
+ if (anrMessage != null) {
+ // Post the ANR to the handler since we do not want to process ANRs while
+ // potentially holding our lock.
+ mHandler.post(new AppNotResponding(app, anrMessage));
+ }
+ }
+
+ private final void addBroadcastToHistoryLocked(BroadcastRecord r) {
+ if (r.callingUid < 0) {
+ // This was from a registerReceiver() call; ignore it.
+ return;
+ }
+ System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1,
+ MAX_BROADCAST_HISTORY-1);
+ r.finishTime = SystemClock.uptimeMillis();
+ mBroadcastHistory[0] = r;
+ }
+
+ final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
+ if (r.nextReceiver > 0) {
+ Object curReceiver = r.receivers.get(r.nextReceiver-1);
+ if (curReceiver instanceof BroadcastFilter) {
+ BroadcastFilter bf = (BroadcastFilter) curReceiver;
+ EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
+ System.identityHashCode(r),
+ r.intent.getAction(),
+ r.nextReceiver - 1,
+ System.identityHashCode(bf));
+ } else {
+ EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
+ System.identityHashCode(r),
+ r.intent.getAction(),
+ r.nextReceiver - 1,
+ ((ResolveInfo)curReceiver).toString());
+ }
+ } else {
+ Slog.w(TAG, "Discarding broadcast before first receiver is invoked: "
+ + r);
+ EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
+ System.identityHashCode(r),
+ r.intent.getAction(),
+ r.nextReceiver,
+ "NONE");
+ }
+ }
+
+ final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
+ if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0
+ || mPendingBroadcast != null) {
+ boolean printed = false;
+ for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {
+ BroadcastRecord br = mParallelBroadcasts.get(i);
+ if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
+ continue;
+ }
+ if (!printed) {
+ if (needSep) {
+ pw.println();
+ needSep = false;
+ }
+ printed = true;
+ pw.println(" Active broadcasts [" + mQueueName + "]:");
+ }
+ pw.println(" Broadcast #" + i + ":");
+ br.dump(pw, " ");
+ }
+ printed = false;
+ needSep = true;
+ for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) {
+ BroadcastRecord br = mOrderedBroadcasts.get(i);
+ if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
+ continue;
+ }
+ if (!printed) {
+ if (needSep) {
+ pw.println();
+ }
+ needSep = true;
+ pw.println(" Active ordered broadcasts [" + mQueueName + "]:");
+ }
+ pw.println(" Ordered Broadcast #" + i + ":");
+ mOrderedBroadcasts.get(i).dump(pw, " ");
+ }
+ if (dumpPackage == null || (mPendingBroadcast != null
+ && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
+ if (needSep) {
+ pw.println();
+ }
+ pw.println(" Pending broadcast [" + mQueueName + "]:");
+ if (mPendingBroadcast != null) {
+ mPendingBroadcast.dump(pw, " ");
+ } else {
+ pw.println(" (null)");
+ }
+ needSep = true;
+ }
+ }
+
+ boolean printed = false;
+ for (int i=0; i<MAX_BROADCAST_HISTORY; i++) {
+ BroadcastRecord r = mBroadcastHistory[i];
+ if (r == null) {
+ break;
+ }
+ if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) {
+ continue;
+ }
+ if (!printed) {
+ if (needSep) {
+ pw.println();
+ }
+ needSep = true;
+ pw.println(" Historical broadcasts [" + mQueueName + "]:");
+ printed = true;
+ }
+ if (dumpAll) {
+ pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":");
+ r.dump(pw, " ");
+ } else {
+ if (i >= 50) {
+ pw.println(" ...");
+ break;
+ }
+ pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r);
+ }
+ }
+
+ return needSep;
+ }
+ }
+
+ final BroadcastQueue mFgBroadcastQueue = new BroadcastQueue("foreground", BROADCAST_FG_TIMEOUT);
+ final BroadcastQueue mBgBroadcastQueue = new BroadcastQueue("background", BROADCAST_BG_TIMEOUT);
+ // Convenient for easy iteration over the queues. Foreground is first
+ // so that dispatch of foreground broadcasts gets precedence.
+ final BroadcastQueue[] mBroadcastQueues = { mFgBroadcastQueue, mBgBroadcastQueue };
+
+ BroadcastQueue broadcastQueueForIntent(Intent intent) {
+ final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
+ if (DEBUG_BACKGROUND_BROADCAST) {
+ Slog.i(TAG, "Broadcast intent " + intent + " on "
+ + (isFg ? "foreground" : "background")
+ + " queue");
+ }
+ return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
+ }
+
+ BroadcastRecord broadcastRecordForReceiverLocked(IBinder receiver) {
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ BroadcastRecord r = queue.getMatchingOrderedReceiver(receiver);
+ if (r != null) {
+ return r;
+ }
+ }
+ return null;
+ }
/**
* Activity we have told the window manager to have key focus.
@@ -456,25 +1248,6 @@
private final StringBuilder mStrictModeBuffer = new StringBuilder();
/**
- * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
- */
- private boolean mPendingBroadcastTimeoutMessage;
-
- /**
- * Intent broadcast that we have tried to start, but are
- * waiting for its application's process to be created. We only
- * need one (instead of a list) because we always process broadcasts
- * one at a time, so no others can be started while waiting for this
- * one.
- */
- BroadcastRecord mPendingBroadcast = null;
-
- /**
- * The receiver index that is pending, to restart the broadcast if needed.
- */
- int mPendingBroadcastRecvIndex;
-
- /**
* Keeps track of all IIntentReceivers that have been registered for
* broadcasts. Hash keys are the receiver IBinder, hash value is
* a ReceiverList.
@@ -910,7 +1683,8 @@
Intent intent = new Intent("android.intent.action.ANR");
if (!mProcessesReady) {
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
}
broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null,
@@ -987,11 +1761,13 @@
case BROADCAST_INTENT_MSG: {
if (DEBUG_BROADCAST) Slog.v(
TAG, "Received BROADCAST_INTENT_MSG");
- processNextBroadcast(true);
+ BroadcastQueue queue = (BroadcastQueue) msg.obj;
+ queue.processNextBroadcast(true);
} break;
case BROADCAST_TIMEOUT_MSG: {
+ final BroadcastQueue queue = (BroadcastQueue) msg.obj;
synchronized (ActivityManagerService.this) {
- broadcastTimeoutLocked(true);
+ queue.broadcastTimeoutLocked(true);
}
} break;
case SERVICE_TIMEOUT_MSG: {
@@ -3708,12 +4484,9 @@
// Can't happen; the backup manager is local
}
}
- if (mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid) {
+ if (isPendingBroadcastProcessLocked(pid)) {
Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping");
- mPendingBroadcast.state = BroadcastRecord.IDLE;
- mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
- mPendingBroadcast = null;
- scheduleBroadcastsLocked();
+ skipPendingBroadcastLocked(pid);
}
} else {
Slog.w(TAG, "Spurious process start timeout - pid not known for " + app);
@@ -3912,23 +4685,13 @@
}
}
- // Check if the next broadcast receiver is in this process...
- BroadcastRecord br = mPendingBroadcast;
- if (!badApp && br != null && br.curApp == app) {
+ // Check if a next-broadcast receiver is in this process...
+ if (!badApp && isPendingBroadcastProcessLocked(pid)) {
try {
- mPendingBroadcast = null;
- processCurBroadcastLocked(br, app);
- didSomething = true;
+ didSomething = sendPendingBroadcastsLocked(app);
} catch (Exception e) {
- Slog.w(TAG, "Exception in new application when starting receiver "
- + br.curComponent.flattenToShortString(), e);
+ // If the app died trying to launch the receiver we declare it 'bad'
badApp = true;
- logBroadcastReceiverDiscardLocked(br);
- finishReceiverLocked(br.receiver, br.resultCode, br.resultData,
- br.resultExtras, br.resultAbort, true);
- scheduleBroadcastsLocked();
- // We need to reset the state if we fails to start the receiver.
- br.state = BroadcastRecord.IDLE;
}
}
@@ -7227,28 +7990,8 @@
}
void skipCurrentReceiverLocked(ProcessRecord app) {
- boolean reschedule = false;
- BroadcastRecord r = app.curReceiver;
- if (r != null) {
- // The current broadcast is waiting for this app's receiver
- // to be finished. Looks like that's not going to happen, so
- // let the broadcast continue.
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r.receiver, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, true);
- reschedule = true;
- }
- r = mPendingBroadcast;
- if (r != null && r.curApp == app) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "skip & discard pending app " + r);
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r.receiver, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, true);
- reschedule = true;
- }
- if (reschedule) {
- scheduleBroadcastsLocked();
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ queue.skipCurrentReceiverLocked(app);
}
}
@@ -8971,84 +9714,11 @@
needSep = true;
}
}
-
- if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0
- || mPendingBroadcast != null) {
- boolean printed = false;
- for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {
- BroadcastRecord br = mParallelBroadcasts.get(i);
- if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
- continue;
- }
- if (!printed) {
- if (needSep) {
- pw.println();
- }
- needSep = true;
- pw.println(" Active broadcasts:");
- }
- pw.println(" Broadcast #" + i + ":");
- br.dump(pw, " ");
- }
- printed = false;
- for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) {
- BroadcastRecord br = mOrderedBroadcasts.get(i);
- if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
- continue;
- }
- if (!printed) {
- if (needSep) {
- pw.println();
- }
- needSep = true;
- pw.println(" Active ordered broadcasts:");
- }
- pw.println(" Ordered Broadcast #" + i + ":");
- mOrderedBroadcasts.get(i).dump(pw, " ");
- }
- if (dumpPackage == null || (mPendingBroadcast != null
- && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
- if (needSep) {
- pw.println();
- }
- pw.println(" Pending broadcast:");
- if (mPendingBroadcast != null) {
- mPendingBroadcast.dump(pw, " ");
- } else {
- pw.println(" (null)");
- }
- needSep = true;
- }
+
+ for (BroadcastQueue q : mBroadcastQueues) {
+ needSep = q.dumpLocked(fd, pw, args, opti, dumpAll, dumpPackage, needSep);
}
- boolean printed = false;
- for (int i=0; i<MAX_BROADCAST_HISTORY; i++) {
- BroadcastRecord r = mBroadcastHistory[i];
- if (r == null) {
- break;
- }
- if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) {
- continue;
- }
- if (!printed) {
- if (needSep) {
- pw.println();
- }
- needSep = true;
- pw.println(" Historical broadcasts:");
- printed = true;
- }
- if (dumpAll) {
- pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":");
- r.dump(pw, " ");
- } else {
- if (i >= 50) {
- pw.println(" ...");
- break;
- }
- pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r);
- }
- }
needSep = true;
if (mStickyBroadcasts != null && dumpPackage == null) {
@@ -9085,7 +9755,10 @@
if (dumpAll) {
pw.println();
- pw.println(" mBroadcastsScheduled=" + mBroadcastsScheduled);
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ pw.println(" mBroadcastsScheduled [" + queue.mQueueName + "]="
+ + queue.mBroadcastsScheduled);
+ }
pw.println(" mHandler:");
mHandler.dump(new PrintWriterPrinter(pw), " ");
needSep = true;
@@ -12072,15 +12745,25 @@
return cur;
}
- private final void scheduleBroadcastsLocked() {
- if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts: current="
- + mBroadcastsScheduled);
+ boolean isPendingBroadcastProcessLocked(int pid) {
+ return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid)
+ || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid);
+ }
- if (mBroadcastsScheduled) {
- return;
+ void skipPendingBroadcastLocked(int pid) {
+ Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping");
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ queue.skipPendingBroadcastLocked(pid);
+ }
+ }
+
+ // The app just attached; send any pending broadcasts that it should receive
+ boolean sendPendingBroadcastsLocked(ProcessRecord app) {
+ boolean didSomething = false;
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ didSomething |= queue.sendPendingBroadcastsLocked(app);
}
- mHandler.sendEmptyMessage(BROADCAST_INTENT_MSG);
- mBroadcastsScheduled = true;
+ return didSomething;
}
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
@@ -12162,13 +12845,12 @@
int N = allSticky.size();
for (int i=0; i<N; i++) {
Intent intent = (Intent)allSticky.get(i);
- BroadcastRecord r = new BroadcastRecord(intent, null,
+ BroadcastQueue queue = broadcastQueueForIntent(intent);
+ BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, -1, -1, null, receivers, null, 0, null, null,
false, true, true);
- if (mParallelBroadcasts.size() == 0) {
- scheduleBroadcastsLocked();
- }
- mParallelBroadcasts.add(r);
+ queue.enqueueParallelBroadcastLocked(r);
+ queue.scheduleBroadcastsLocked();
}
}
@@ -12179,38 +12861,46 @@
public void unregisterReceiver(IIntentReceiver receiver) {
if (DEBUG_BROADCAST) Slog.v(TAG, "Unregister receiver: " + receiver);
- boolean doNext = false;
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ boolean doTrim = false;
- synchronized(this) {
- ReceiverList rl
+ synchronized(this) {
+ ReceiverList rl
= (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
- if (rl != null) {
- if (rl.curBroadcast != null) {
- BroadcastRecord r = rl.curBroadcast;
- doNext = finishReceiverLocked(
- receiver.asBinder(), r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, true);
- }
+ if (rl != null) {
+ if (rl.curBroadcast != null) {
+ BroadcastRecord r = rl.curBroadcast;
+ final boolean doNext = finishReceiverLocked(
+ receiver.asBinder(), r.resultCode, r.resultData,
+ r.resultExtras, r.resultAbort, true);
+ if (doNext) {
+ doTrim = true;
+ r.queue.processNextBroadcast(false);
+ }
+ }
- if (rl.app != null) {
- rl.app.receivers.remove(rl);
- }
- removeReceiverLocked(rl);
- if (rl.linkedToDeath) {
- rl.linkedToDeath = false;
- rl.receiver.asBinder().unlinkToDeath(rl, 0);
+ if (rl.app != null) {
+ rl.app.receivers.remove(rl);
+ }
+ removeReceiverLocked(rl);
+ if (rl.linkedToDeath) {
+ rl.linkedToDeath = false;
+ rl.receiver.asBinder().unlinkToDeath(rl, 0);
+ }
}
}
- }
- if (!doNext) {
- return;
+ // If we actually concluded any broadcasts, we might now be able
+ // to trim the recipients' apps from our working set
+ if (doTrim) {
+ trimApplications();
+ return;
+ }
+
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
-
- final long origId = Binder.clearCallingIdentity();
- processNextBroadcast(false);
- trimApplications();
- Binder.restoreCallingIdentity(origId);
}
void removeReceiverLocked(ReceiverList rl) {
@@ -12440,28 +13130,17 @@
// If we are not serializing this broadcast, then send the
// registered receivers separately so they don't wait for the
// components to be launched.
- BroadcastRecord r = new BroadcastRecord(intent, callerApp,
+ final BroadcastQueue queue = broadcastQueueForIntent(intent);
+ BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
registeredReceivers, resultTo, resultCode, resultData, map,
ordered, sticky, false);
if (DEBUG_BROADCAST) Slog.v(
- TAG, "Enqueueing parallel broadcast " + r
- + ": prev had " + mParallelBroadcasts.size());
- boolean replaced = false;
- if (replacePending) {
- for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {
- if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "***** DROPPING PARALLEL: " + intent);
- mParallelBroadcasts.set(i, r);
- replaced = true;
- break;
- }
- }
- }
+ TAG, "Enqueueing parallel broadcast " + r);
+ final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
if (!replaced) {
- mParallelBroadcasts.add(r);
- scheduleBroadcastsLocked();
+ queue.enqueueParallelBroadcastLocked(r);
+ queue.scheduleBroadcastsLocked();
}
registeredReceivers = null;
NR = 0;
@@ -12541,32 +13220,22 @@
if ((receivers != null && receivers.size() > 0)
|| resultTo != null) {
- BroadcastRecord r = new BroadcastRecord(intent, callerApp,
+ BroadcastQueue queue = broadcastQueueForIntent(intent);
+ BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
receivers, resultTo, resultCode, resultData, map, ordered,
sticky, false);
if (DEBUG_BROADCAST) Slog.v(
TAG, "Enqueueing ordered broadcast " + r
- + ": prev had " + mOrderedBroadcasts.size());
+ + ": prev had " + queue.mOrderedBroadcasts.size());
if (DEBUG_BROADCAST) {
int seq = r.intent.getIntExtra("seq", -1);
Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq);
}
- boolean replaced = false;
- if (replacePending) {
- for (int i=mOrderedBroadcasts.size()-1; i>0; i--) {
- if (intent.filterEquals(mOrderedBroadcasts.get(i).intent)) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "***** DROPPING ORDERED: " + intent);
- mOrderedBroadcasts.set(i, r);
- replaced = true;
- break;
- }
- }
- }
+ boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
if (!replaced) {
- mOrderedBroadcasts.add(r);
- scheduleBroadcastsLocked();
+ queue.enqueueOrderedBroadcastLocked(r);
+ queue.scheduleBroadcastsLocked();
}
}
@@ -12673,54 +13342,14 @@
private final boolean finishReceiverLocked(IBinder receiver, int resultCode,
String resultData, Bundle resultExtras, boolean resultAbort,
boolean explicit) {
- if (mOrderedBroadcasts.size() == 0) {
- if (explicit) {
- Slog.w(TAG, "finishReceiver called but no pending broadcasts");
- }
+ final BroadcastRecord r = broadcastRecordForReceiverLocked(receiver);
+ if (r == null) {
+ Slog.w(TAG, "finishReceiver called but not found on queue");
return false;
}
- BroadcastRecord r = mOrderedBroadcasts.get(0);
- if (r.receiver == null) {
- if (explicit) {
- Slog.w(TAG, "finishReceiver called but none active");
- }
- return false;
- }
- if (r.receiver != receiver) {
- Slog.w(TAG, "finishReceiver called but active receiver is different");
- return false;
- }
- int state = r.state;
- r.state = BroadcastRecord.IDLE;
- if (state == BroadcastRecord.IDLE) {
- if (explicit) {
- Slog.w(TAG, "finishReceiver called but state is IDLE");
- }
- }
- r.receiver = null;
- r.intent.setComponent(null);
- if (r.curApp != null) {
- r.curApp.curReceiver = null;
- }
- if (r.curFilter != null) {
- r.curFilter.receiverList.curBroadcast = null;
- }
- r.curFilter = null;
- r.curApp = null;
- r.curComponent = null;
- r.curReceiver = null;
- mPendingBroadcast = null;
- r.resultCode = resultCode;
- r.resultData = resultData;
- r.resultExtras = resultExtras;
- r.resultAbort = resultAbort;
-
- // We will process the next receiver right now if this is finishing
- // an app receiver (which is always asynchronous) or after we have
- // come back from calling a receiver.
- return state == BroadcastRecord.APP_RECEIVE
- || state == BroadcastRecord.CALL_DONE_RECEIVE;
+ return r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort,
+ explicit);
}
public void finishReceiver(IBinder who, int resultCode, String resultData,
@@ -12732,153 +13361,25 @@
throw new IllegalArgumentException("File descriptors passed in Bundle");
}
- boolean doNext;
-
final long origId = Binder.clearCallingIdentity();
+ try {
+ boolean doNext = false;
+ BroadcastRecord r = null;
- synchronized(this) {
- doNext = finishReceiverLocked(
- who, resultCode, resultData, resultExtras, resultAbort, true);
- }
-
- if (doNext) {
- processNextBroadcast(false);
- }
- trimApplications();
-
- Binder.restoreCallingIdentity(origId);
- }
-
- private final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
- if (r.nextReceiver > 0) {
- Object curReceiver = r.receivers.get(r.nextReceiver-1);
- if (curReceiver instanceof BroadcastFilter) {
- BroadcastFilter bf = (BroadcastFilter) curReceiver;
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
- System.identityHashCode(r),
- r.intent.getAction(),
- r.nextReceiver - 1,
- System.identityHashCode(bf));
- } else {
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- System.identityHashCode(r),
- r.intent.getAction(),
- r.nextReceiver - 1,
- ((ResolveInfo)curReceiver).toString());
- }
- } else {
- Slog.w(TAG, "Discarding broadcast before first receiver is invoked: "
- + r);
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- System.identityHashCode(r),
- r.intent.getAction(),
- r.nextReceiver,
- "NONE");
- }
- }
-
- private final void setBroadcastTimeoutLocked(long timeoutTime) {
- if (! mPendingBroadcastTimeoutMessage) {
- Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG);
- mHandler.sendMessageAtTime(msg, timeoutTime);
- mPendingBroadcastTimeoutMessage = true;
- }
- }
-
- private final void cancelBroadcastTimeoutLocked() {
- if (mPendingBroadcastTimeoutMessage) {
- mHandler.removeMessages(BROADCAST_TIMEOUT_MSG);
- mPendingBroadcastTimeoutMessage = false;
- }
- }
-
- private final void broadcastTimeoutLocked(boolean fromMsg) {
- if (fromMsg) {
- mPendingBroadcastTimeoutMessage = false;
- }
-
- if (mOrderedBroadcasts.size() == 0) {
- return;
- }
-
- long now = SystemClock.uptimeMillis();
- BroadcastRecord r = mOrderedBroadcasts.get(0);
- if (fromMsg) {
- if (mDidDexOpt) {
- // Delay timeouts until dexopt finishes.
- mDidDexOpt = false;
- long timeoutTime = SystemClock.uptimeMillis() + BROADCAST_TIMEOUT;
- setBroadcastTimeoutLocked(timeoutTime);
- return;
- }
- if (! mProcessesReady) {
- // Only process broadcast timeouts if the system is ready. That way
- // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended
- // to do heavy lifting for system up.
- return;
- }
-
- long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT;
- if (timeoutTime > now) {
- // We can observe premature timeouts because we do not cancel and reset the
- // broadcast timeout message after each receiver finishes. Instead, we set up
- // an initial timeout then kick it down the road a little further as needed
- // when it expires.
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "Premature timeout @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
- + timeoutTime);
- setBroadcastTimeoutLocked(timeoutTime);
- return;
- }
- }
-
- Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver
- + ", started " + (now - r.receiverTime) + "ms ago");
- r.receiverTime = now;
- r.anrCount++;
-
- // Current receiver has passed its expiration date.
- if (r.nextReceiver <= 0) {
- Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0");
- return;
- }
-
- ProcessRecord app = null;
- String anrMessage = null;
-
- Object curReceiver = r.receivers.get(r.nextReceiver-1);
- Slog.w(TAG, "Receiver during timeout: " + curReceiver);
- logBroadcastReceiverDiscardLocked(r);
- if (curReceiver instanceof BroadcastFilter) {
- BroadcastFilter bf = (BroadcastFilter)curReceiver;
- if (bf.receiverList.pid != 0
- && bf.receiverList.pid != MY_PID) {
- synchronized (this.mPidsSelfLocked) {
- app = this.mPidsSelfLocked.get(
- bf.receiverList.pid);
+ synchronized(this) {
+ r = broadcastRecordForReceiverLocked(who);
+ if (r != null) {
+ doNext = r.queue.finishReceiverLocked(r, resultCode,
+ resultData, resultExtras, resultAbort, true);
}
}
- } else {
- app = r.curApp;
- }
-
- if (app != null) {
- anrMessage = "Broadcast of " + r.intent.toString();
- }
- if (mPendingBroadcast == r) {
- mPendingBroadcast = null;
- }
-
- // Move on to the next receiver.
- finishReceiverLocked(r.receiver, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, true);
- scheduleBroadcastsLocked();
-
- if (anrMessage != null) {
- // Post the ANR to the handler since we do not want to process ANRs while
- // potentially holding our lock.
- mHandler.post(new AppNotResponding(app, anrMessage));
+ if (doNext) {
+ r.queue.processNextBroadcast(false);
+ }
+ trimApplications();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
}
@@ -13013,333 +13514,6 @@
}
}
- private final void addBroadcastToHistoryLocked(BroadcastRecord r) {
- if (r.callingUid < 0) {
- // This was from a registerReceiver() call; ignore it.
- return;
- }
- System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1,
- MAX_BROADCAST_HISTORY-1);
- r.finishTime = SystemClock.uptimeMillis();
- mBroadcastHistory[0] = r;
- }
-
- private final void processNextBroadcast(boolean fromMsg) {
- synchronized(this) {
- BroadcastRecord r;
-
- if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast: "
- + mParallelBroadcasts.size() + " broadcasts, "
- + mOrderedBroadcasts.size() + " ordered broadcasts");
-
- updateCpuStats();
-
- if (fromMsg) {
- mBroadcastsScheduled = false;
- }
-
- // First, deliver any non-serialized broadcasts right away.
- while (mParallelBroadcasts.size() > 0) {
- r = mParallelBroadcasts.remove(0);
- r.dispatchTime = SystemClock.uptimeMillis();
- r.dispatchClockTime = System.currentTimeMillis();
- final int N = r.receivers.size();
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast "
- + r);
- for (int i=0; i<N; i++) {
- Object target = r.receivers.get(i);
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "Delivering non-ordered to registered "
- + target + ": " + r);
- deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
- }
- addBroadcastToHistoryLocked(r);
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast "
- + r);
- }
-
- // Now take care of the next serialized one...
-
- // If we are waiting for a process to come up to handle the next
- // broadcast, then do nothing at this point. Just in case, we
- // check that the process we're waiting for still exists.
- if (mPendingBroadcast != null) {
- if (DEBUG_BROADCAST_LIGHT) {
- Slog.v(TAG, "processNextBroadcast: waiting for "
- + mPendingBroadcast.curApp);
- }
-
- boolean isDead;
- synchronized (mPidsSelfLocked) {
- isDead = (mPidsSelfLocked.get(mPendingBroadcast.curApp.pid) == null);
- }
- if (!isDead) {
- // It's still alive, so keep waiting
- return;
- } else {
- Slog.w(TAG, "pending app " + mPendingBroadcast.curApp
- + " died before responding to broadcast");
- mPendingBroadcast.state = BroadcastRecord.IDLE;
- mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
- mPendingBroadcast = null;
- }
- }
-
- boolean looped = false;
-
- do {
- if (mOrderedBroadcasts.size() == 0) {
- // No more broadcasts pending, so all done!
- scheduleAppGcsLocked();
- if (looped) {
- // If we had finished the last ordered broadcast, then
- // make sure all processes have correct oom and sched
- // adjustments.
- updateOomAdjLocked();
- }
- return;
- }
- r = mOrderedBroadcasts.get(0);
- boolean forceReceive = false;
-
- // Ensure that even if something goes awry with the timeout
- // detection, we catch "hung" broadcasts here, discard them,
- // and continue to make progress.
- //
- // This is only done if the system is ready so that PRE_BOOT_COMPLETED
- // receivers don't get executed with timeouts. They're intended for
- // one time heavy lifting after system upgrades and can take
- // significant amounts of time.
- int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
- if (mProcessesReady && r.dispatchTime > 0) {
- long now = SystemClock.uptimeMillis();
- if ((numReceivers > 0) &&
- (now > r.dispatchTime + (2*BROADCAST_TIMEOUT*numReceivers))) {
- Slog.w(TAG, "Hung broadcast discarded after timeout failure:"
- + " now=" + now
- + " dispatchTime=" + r.dispatchTime
- + " startTime=" + r.receiverTime
- + " intent=" + r.intent
- + " numReceivers=" + numReceivers
- + " nextReceiver=" + r.nextReceiver
- + " state=" + r.state);
- broadcastTimeoutLocked(false); // forcibly finish this broadcast
- forceReceive = true;
- r.state = BroadcastRecord.IDLE;
- }
- }
-
- if (r.state != BroadcastRecord.IDLE) {
- if (DEBUG_BROADCAST) Slog.d(TAG,
- "processNextBroadcast() called when not idle (state="
- + r.state + ")");
- return;
- }
-
- if (r.receivers == null || r.nextReceiver >= numReceivers
- || r.resultAbort || forceReceive) {
- // No more receivers for this broadcast! Send the final
- // result if requested...
- if (r.resultTo != null) {
- try {
- if (DEBUG_BROADCAST) {
- int seq = r.intent.getIntExtra("seq", -1);
- Slog.i(TAG, "Finishing broadcast " + r.intent.getAction()
- + " seq=" + seq + " app=" + r.callerApp);
- }
- performReceiveLocked(r.callerApp, r.resultTo,
- new Intent(r.intent), r.resultCode,
- r.resultData, r.resultExtras, false, false);
- // Set this to null so that the reference
- // (local and remote) isnt kept in the mBroadcastHistory.
- r.resultTo = null;
- } catch (RemoteException e) {
- Slog.w(TAG, "Failure sending broadcast result of " + r.intent, e);
- }
- }
-
- if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG");
- cancelBroadcastTimeoutLocked();
-
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast "
- + r);
-
- // ... and on to the next...
- addBroadcastToHistoryLocked(r);
- mOrderedBroadcasts.remove(0);
- r = null;
- looped = true;
- continue;
- }
- } while (r == null);
-
- // Get the next receiver...
- int recIdx = r.nextReceiver++;
-
- // Keep track of when this receiver started, and make sure there
- // is a timeout message pending to kill it if need be.
- r.receiverTime = SystemClock.uptimeMillis();
- if (recIdx == 0) {
- r.dispatchTime = r.receiverTime;
- r.dispatchClockTime = System.currentTimeMillis();
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast "
- + r);
- }
- if (! mPendingBroadcastTimeoutMessage) {
- long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT;
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "Submitting BROADCAST_TIMEOUT_MSG for " + r + " at " + timeoutTime);
- setBroadcastTimeoutLocked(timeoutTime);
- }
-
- Object nextReceiver = r.receivers.get(recIdx);
- if (nextReceiver instanceof BroadcastFilter) {
- // Simple case: this is a registered receiver who gets
- // a direct call.
- BroadcastFilter filter = (BroadcastFilter)nextReceiver;
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "Delivering ordered to registered "
- + filter + ": " + r);
- deliverToRegisteredReceiverLocked(r, filter, r.ordered);
- if (r.receiver == null || !r.ordered) {
- // The receiver has already finished, so schedule to
- // process the next one.
- if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing: ordered="
- + r.ordered + " receiver=" + r.receiver);
- r.state = BroadcastRecord.IDLE;
- scheduleBroadcastsLocked();
- }
- return;
- }
-
- // Hard case: need to instantiate the receiver, possibly
- // starting its application process to host it.
-
- ResolveInfo info =
- (ResolveInfo)nextReceiver;
-
- boolean skip = false;
- int perm = checkComponentPermission(info.activityInfo.permission,
- r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
- info.activityInfo.exported);
- if (perm != PackageManager.PERMISSION_GRANTED) {
- if (!info.activityInfo.exported) {
- Slog.w(TAG, "Permission Denial: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid=" + r.callingPid
- + ", uid=" + r.callingUid + ")"
- + " is not exported from uid " + info.activityInfo.applicationInfo.uid
- + " due to receiver " + info.activityInfo.packageName
- + "/" + info.activityInfo.name);
- } else {
- Slog.w(TAG, "Permission Denial: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid=" + r.callingPid
- + ", uid=" + r.callingUid + ")"
- + " requires " + info.activityInfo.permission
- + " due to receiver " + info.activityInfo.packageName
- + "/" + info.activityInfo.name);
- }
- skip = true;
- }
- if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
- r.requiredPermission != null) {
- try {
- perm = AppGlobals.getPackageManager().
- checkPermission(r.requiredPermission,
- info.activityInfo.applicationInfo.packageName);
- } catch (RemoteException e) {
- perm = PackageManager.PERMISSION_DENIED;
- }
- if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: receiving "
- + r.intent + " to "
- + info.activityInfo.applicationInfo.packageName
- + " requires " + r.requiredPermission
- + " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- skip = true;
- }
- }
- if (r.curApp != null && r.curApp.crashing) {
- // If the target process is crashing, just skip it.
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "Skipping deliver ordered " + r + " to " + r.curApp
- + ": process crashing");
- skip = true;
- }
-
- if (skip) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "Skipping delivery of ordered " + r + " for whatever reason");
- r.receiver = null;
- r.curFilter = null;
- r.state = BroadcastRecord.IDLE;
- scheduleBroadcastsLocked();
- return;
- }
-
- r.state = BroadcastRecord.APP_RECEIVE;
- String targetProcess = info.activityInfo.processName;
- r.curComponent = new ComponentName(
- info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
- r.curReceiver = info.activityInfo;
-
- // Broadcast is being executed, its package can't be stopped.
- try {
- AppGlobals.getPackageManager().setPackageStoppedState(
- r.curComponent.getPackageName(), false);
- } catch (RemoteException e) {
- } catch (IllegalArgumentException e) {
- Slog.w(TAG, "Failed trying to unstop package "
- + r.curComponent.getPackageName() + ": " + e);
- }
-
- // Is this receiver's application already running?
- ProcessRecord app = getProcessRecordLocked(targetProcess,
- info.activityInfo.applicationInfo.uid);
- if (app != null && app.thread != null) {
- try {
- app.addPackage(info.activityInfo.packageName);
- processCurBroadcastLocked(r, app);
- return;
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception when sending broadcast to "
- + r.curComponent, e);
- }
-
- // If a dead object exception was thrown -- fall through to
- // restart the application.
- }
-
- // Not running -- get it started, to be executed when the app comes up.
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "Need to start app " + targetProcess + " for broadcast " + r);
- if ((r.curApp=startProcessLocked(targetProcess,
- info.activityInfo.applicationInfo, true,
- r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
- "broadcast", r.curComponent,
- (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0))
- == null) {
- // Ah, this recipient is unavailable. Finish it if necessary,
- // and mark the broadcast record as ready for the next.
- Slog.w(TAG, "Unable to launch app "
- + info.activityInfo.applicationInfo.packageName + "/"
- + info.activityInfo.applicationInfo.uid + " for broadcast "
- + r.intent + ": process is bad");
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r.receiver, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, true);
- scheduleBroadcastsLocked();
- r.state = BroadcastRecord.IDLE;
- return;
- }
-
- mPendingBroadcast = r;
- mPendingBroadcastRecvIndex = recIdx;
- }
- }
// =========================================================
// INSTRUMENTATION
@@ -13663,6 +13837,28 @@
// LIFETIME MANAGEMENT
// =========================================================
+ // Returns which broadcast queue the app is the current [or imminent] receiver
+ // on, or 'null' if the app is not an active broadcast recipient.
+ private BroadcastQueue isReceivingBroadcast(ProcessRecord app) {
+ BroadcastRecord r = app.curReceiver;
+ if (r != null) {
+ return r.queue;
+ }
+
+ // It's not the current receiver, but it might be starting up to become one
+ synchronized (this) {
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ r = queue.mPendingBroadcast;
+ if (r != null && r.curApp == app) {
+ // found it; report which queue it's in
+ return queue;
+ }
+ }
+ }
+
+ return null;
+ }
+
private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj,
ProcessRecord TOP_APP, boolean recursed, boolean doingAll) {
if (mAdjSeq == app.adjSeq) {
@@ -13728,6 +13924,7 @@
// important to least, and assign an appropriate OOM adjustment.
int adj;
int schedGroup;
+ BroadcastQueue queue;
if (app == TOP_APP) {
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
@@ -13739,12 +13936,14 @@
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "instrumentation";
- } else if (app.curReceiver != null ||
- (mPendingBroadcast != null && mPendingBroadcast.curApp == app)) {
+ } else if ((queue = isReceivingBroadcast(app)) != null) {
// An app that is currently receiving a broadcast also
- // counts as being in the foreground.
+ // counts as being in the foreground for OOM killer purposes.
+ // It's placed in a sched group based on the nature of the
+ // broadcast as reflected by which queue it's active in.
adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = Process.THREAD_GROUP_DEFAULT;
+ schedGroup = (queue == mFgBroadcastQueue)
+ ? Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE;
app.adjType = "broadcast";
} else if (app.executingServices.size() > 0) {
// An app that is currently executing a service callback also
@@ -14185,8 +14384,13 @@
* Returns true if things are idle enough to perform GCs.
*/
private final boolean canGcNowLocked() {
- return mParallelBroadcasts.size() == 0
- && mOrderedBroadcasts.size() == 0
+ boolean processingBroadcasts = false;
+ for (BroadcastQueue q : mBroadcastQueues) {
+ if (q.mParallelBroadcasts.size() != 0 || q.mOrderedBroadcasts.size() != 0) {
+ processingBroadcasts = true;
+ }
+ }
+ return !processingBroadcasts
&& (mSleeping || (mMainStack.mResumedActivity != null &&
mMainStack.mResumedActivity.idle));
}
diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index bcb0134..6738e4f 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -59,6 +59,7 @@
IBinder receiver; // who is currently running, null if none.
int state;
int anrCount; // has this broadcast record hit any ANRs?
+ ActivityManagerService.BroadcastQueue queue; // the outbound queue handling this broadcast
static final int IDLE = 0;
static final int APP_RECEIVE = 1;
@@ -161,11 +162,13 @@
}
}
- BroadcastRecord(Intent _intent, ProcessRecord _callerApp, String _callerPackage,
+ BroadcastRecord(ActivityManagerService.BroadcastQueue _queue,
+ Intent _intent, ProcessRecord _callerApp, String _callerPackage,
int _callingPid, int _callingUid, String _requiredPermission,
List _receivers, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized,
boolean _sticky, boolean _initialSticky) {
+ queue = _queue;
intent = _intent;
callerApp = _callerApp;
callerPackage = _callerPackage;
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index c9b79e8..414ed1e 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -121,6 +121,7 @@
private static final int MSG_PERFORM_POLL = 1;
private static final int MSG_UPDATE_IFACES = 2;
+ private static final int MSG_REGISTER_GLOBAL_ALERT = 3;
/** Flags to control detail level of poll event. */
private static final int FLAG_PERSIST_NETWORK = 0x1;
@@ -510,7 +511,13 @@
@Override
public void forceUpdate() {
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
- performPoll(FLAG_PERSIST_ALL);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ performPoll(FLAG_PERSIST_ALL);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
/**
@@ -594,7 +601,7 @@
mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget();
// re-arm global alert for next update
- registerGlobalAlert();
+ mHandler.obtainMessage(MSG_REGISTER_GLOBAL_ALERT).sendToTarget();
}
}
};
@@ -945,6 +952,10 @@
updateIfaces();
return true;
}
+ case MSG_REGISTER_GLOBAL_ALERT: {
+ registerGlobalAlert();
+ return true;
+ }
default: {
return false;
}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 19d94a1..620d74c 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -95,6 +95,7 @@
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.TypedValue;
+import android.view.Choreographer;
import android.view.Display;
import android.view.Gravity;
import android.view.IApplicationToken;
@@ -141,7 +142,8 @@
/** {@hide} */
public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
+ implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs,
+ Choreographer.OnAnimateListener {
static final String TAG = "WindowManager";
static final boolean DEBUG = false;
static final boolean DEBUG_ADD_REMOVE = false;
@@ -456,7 +458,7 @@
int mDeferredRotationPauseCount;
boolean mLayoutNeeded = true;
- boolean mAnimationPending = false;
+ boolean mTraversalScheduled = false;
boolean mDisplayFrozen = false;
boolean mWaitingForConfig = false;
boolean mWindowsFreezingScreen = false;
@@ -503,7 +505,9 @@
final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
- H mH = new H();
+ final H mH = new H();
+
+ final Choreographer mChoreographer = Choreographer.getInstance();
WindowState mCurrentFocus = null;
WindowState mLastFocus = null;
@@ -559,6 +563,7 @@
float mWindowAnimationScale = 1.0f;
float mTransitionAnimationScale = 1.0f;
+ float mAnimatorDurationScale = 1.0f;
final InputManager mInputManager;
@@ -691,6 +696,7 @@
Looper.prepare();
WindowManagerService s = new WindowManagerService(mContext, mPM,
mHaveInputMethods, mAllowBootMessages);
+ s.mChoreographer.addOnAnimateListener(s);
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_DISPLAY);
android.os.Process.setCanSelfBackground(false);
@@ -774,6 +780,8 @@
Settings.System.WINDOW_ANIMATION_SCALE, mWindowAnimationScale);
mTransitionAnimationScale = Settings.System.getFloat(context.getContentResolver(),
Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale);
+ mAnimatorDurationScale = Settings.System.getFloat(context.getContentResolver(),
+ Settings.System.ANIMATOR_DURATION_SCALE, mTransitionAnimationScale);
// Track changes to DevicePolicyManager state so we can enable/disable keyguard.
IntentFilter filter = new IntentFilter();
@@ -4657,6 +4665,7 @@
switch (which) {
case 0: mWindowAnimationScale = fixScale(scale); break;
case 1: mTransitionAnimationScale = fixScale(scale); break;
+ case 2: mAnimatorDurationScale = fixScale(scale); break;
}
// Persist setting
@@ -4676,6 +4685,9 @@
if (scales.length >= 2) {
mTransitionAnimationScale = fixScale(scales[1]);
}
+ if (scales.length >= 3) {
+ mAnimatorDurationScale = fixScale(scales[2]);
+ }
}
// Persist setting
@@ -4686,12 +4698,14 @@
switch (which) {
case 0: return mWindowAnimationScale;
case 1: return mTransitionAnimationScale;
+ case 2: return mAnimatorDurationScale;
}
return 0;
}
public float[] getAnimationScales() {
- return new float[] { mWindowAnimationScale, mTransitionAnimationScale };
+ return new float[] { mWindowAnimationScale, mTransitionAnimationScale,
+ mAnimatorDurationScale };
}
public int getSwitchState(int sw) {
@@ -5390,7 +5404,7 @@
if (mScreenRotationAnimation.setRotation(rotation, mFxSession,
MAX_ANIMATION_DURATION, mTransitionAnimationScale,
mCurDisplayWidth, mCurDisplayHeight)) {
- requestAnimationLocked(0);
+ mChoreographer.scheduleAnimation();
}
}
Surface.setOrientation(0, rotation);
@@ -6513,7 +6527,7 @@
final class H extends Handler {
public static final int REPORT_FOCUS_CHANGE = 2;
public static final int REPORT_LOSING_FOCUS = 3;
- public static final int ANIMATE = 4;
+ public static final int DO_TRAVERSAL = 4;
public static final int ADD_STARTING = 5;
public static final int REMOVE_STARTING = 6;
public static final int FINISHED_STARTING = 7;
@@ -6607,9 +6621,9 @@
}
} break;
- case ANIMATE: {
+ case DO_TRAVERSAL: {
synchronized(mWindowMap) {
- mAnimationPending = false;
+ mTraversalScheduled = false;
performLayoutAndPlaceSurfacesLocked();
}
} break;
@@ -6825,12 +6839,14 @@
Settings.System.WINDOW_ANIMATION_SCALE, mWindowAnimationScale);
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale);
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.ANIMATOR_DURATION_SCALE, mAnimatorDurationScale);
break;
}
case FORCE_GC: {
synchronized(mWindowMap) {
- if (mAnimationPending) {
+ if (mChoreographer.isAnimationScheduled()) {
// If we are animating, don't do the gc now but
// delay a bit so we don't interrupt the animation.
mH.sendMessageDelayed(mH.obtainMessage(H.FORCE_GC),
@@ -7389,7 +7405,7 @@
} else {
mInLayout = false;
if (mLayoutNeeded) {
- requestAnimationLocked(0);
+ requestTraversalLocked();
}
}
if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) {
@@ -8822,10 +8838,9 @@
needRelayout = adjustWallpaperWindowsLocked() != 0;
}
if (needRelayout) {
- requestAnimationLocked(0);
+ requestTraversalLocked();
} else if (animating) {
- final int refreshTimeUs = (int)(1000 / mDisplay.getRefreshRate());
- requestAnimationLocked(currentTime + refreshTimeUs - SystemClock.uptimeMillis());
+ mChoreographer.scheduleAnimation();
}
// Finally update all input windows now that the window changes have stabilized.
@@ -8944,10 +8959,17 @@
}
}
- void requestAnimationLocked(long delay) {
- if (!mAnimationPending) {
- mAnimationPending = true;
- mH.sendMessageDelayed(mH.obtainMessage(H.ANIMATE), delay);
+ void requestTraversalLocked() {
+ if (!mTraversalScheduled) {
+ mTraversalScheduled = true;
+ mH.sendEmptyMessage(H.DO_TRAVERSAL);
+ }
+ }
+
+ @Override
+ public void onAnimate() {
+ synchronized(mWindowMap) {
+ performLayoutAndPlaceSurfacesLocked();
}
}
@@ -9267,7 +9289,7 @@
if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation");
if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) {
- requestAnimationLocked(0);
+ mChoreographer.scheduleAnimation();
} else {
mScreenRotationAnimation = null;
updateRotation = true;
@@ -9759,9 +9781,10 @@
pw.print(" mLastWindowForcedOrientation"); pw.print(mLastWindowForcedOrientation);
pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation);
pw.print(" mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
- pw.print(" mAnimationPending="); pw.print(mAnimationPending);
+ pw.print(" mTraversalScheduled="); pw.print(mTraversalScheduled);
pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale);
pw.print(" mTransitionWindowAnimationScale="); pw.println(mTransitionAnimationScale);
+ pw.print(" mAnimatorDurationScale="); pw.println(mAnimatorDurationScale);
pw.print(" mNextAppTransition=0x");
pw.print(Integer.toHexString(mNextAppTransition));
pw.print(" mAppTransitionReady="); pw.println(mAppTransitionReady);
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 1067cad..6868cf6 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -1593,7 +1593,7 @@
mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true);
}
if (requestAnim) {
- mService.requestAnimationLocked(0);
+ mService.mChoreographer.scheduleAnimation();
}
return true;
}
@@ -1634,7 +1634,7 @@
}
}
if (requestAnim) {
- mService.requestAnimationLocked(0);
+ mService.mChoreographer.scheduleAnimation();
}
return true;
}
diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index 732af53..fc211e5 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -36,7 +36,6 @@
endif
ifeq ($(TARGET_BOARD_PLATFORM), s5pc110)
LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY -DNEVER_DEFAULT_TO_ASYNC_MODE
- LOCAL_CFLAGS += -DREFRESH_RATE=56
endif
LOCAL_SHARED_LIBRARIES := \
diff --git a/services/surfaceflinger/EventThread.cpp b/services/surfaceflinger/EventThread.cpp
index 92d4266..af0da0b 100644
--- a/services/surfaceflinger/EventThread.cpp
+++ b/services/surfaceflinger/EventThread.cpp
@@ -151,9 +151,9 @@
mLastVSyncTimestamp = timestamp;
// now see if we still need to report this VSYNC event
- bool reportVsync = false;
- size_t count = mDisplayEventConnections.size();
+ const size_t count = mDisplayEventConnections.size();
for (size_t i=0 ; i<count ; i++) {
+ bool reportVsync = false;
const ConnectionInfo& info(
mDisplayEventConnections.valueAt(i));
if (info.count >= 1) {
@@ -174,11 +174,7 @@
displayEventConnections.add(mDisplayEventConnections.keyAt(i));
}
}
-
- if (reportVsync) {
- break;
- }
- } while (true);
+ } while (!displayEventConnections.size());
// dispatch vsync events to listeners...
vsync.header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
diff --git a/services/surfaceflinger/tests/vsync/vsync.cpp b/services/surfaceflinger/tests/vsync/vsync.cpp
index 4f79080..b0d54c4 100644
--- a/services/surfaceflinger/tests/vsync/vsync.cpp
+++ b/services/surfaceflinger/tests/vsync/vsync.cpp
@@ -55,6 +55,8 @@
loop->addFd(myDisplayEvent.getFd(), 0, ALOOPER_EVENT_INPUT, receiver,
&myDisplayEvent);
+ myDisplayEvent.setVsyncRate(1);
+
do {
//printf("about to poll...\n");
int32_t ret = loop->pollOnce(-1);
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 05e198f..1a4b574 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -47,6 +47,9 @@
"none", "poor", "moderate", "good", "great"
};
+ /** @hide */
+ public static final int INVALID_SNR = 0x7FFFFFFF;
+
private int mGsmSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5
private int mGsmBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5
private int mCdmaDbm; // This value is the RSSI value
@@ -96,7 +99,7 @@
mLteSignalStrength = -1;
mLteRsrp = -1;
mLteRsrq = -1;
- mLteRssnr = -1;
+ mLteRssnr = INVALID_SNR;
mLteCqi = -1;
isGsm = true;
}
@@ -136,7 +139,8 @@
int evdoDbm, int evdoEcio, int evdoSnr,
boolean gsm) {
this(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
- evdoDbm, evdoEcio, evdoSnr, -1, -1, -1, -1, -1, gsm);
+ evdoDbm, evdoEcio, evdoSnr, -1, -1,
+ -1, INVALID_SNR, -1, gsm);
}
/**
@@ -292,7 +296,7 @@
if ((mLteSignalStrength == -1)
&& (mLteRsrp == -1)
&& (mLteRsrq == -1)
- && (mLteRssnr == -1)
+ && (mLteRssnr == INVALID_SNR)
&& (mLteCqi == -1)) {
level = getGsmLevel();
} else {
@@ -327,7 +331,7 @@
if ((mLteSignalStrength == -1)
&& (mLteRsrp == -1)
&& (mLteRsrq == -1)
- && (mLteRssnr == -1)
+ && (mLteRssnr == INVALID_SNR)
&& (mLteCqi == -1)) {
asuLevel = getGsmAsuLevel();
} else {
@@ -363,7 +367,7 @@
if ((mLteSignalStrength == -1)
&& (mLteRsrp == -1)
&& (mLteRsrq == -1)
- && (mLteRssnr == -1)
+ && (mLteRssnr == INVALID_SNR)
&& (mLteCqi == -1)) {
dBm = getGsmDbm();
} else {
@@ -566,6 +570,7 @@
*/
public int getLteLevel() {
int levelLteRsrp = 0;
+ int levelLteRssnr = 0;
if (mLteRsrp == -1) levelLteRsrp = 0;
else if (mLteRsrp >= -95) levelLteRsrp = SIGNAL_STRENGTH_GREAT;
@@ -573,8 +578,23 @@
else if (mLteRsrp >= -115) levelLteRsrp = SIGNAL_STRENGTH_MODERATE;
else levelLteRsrp = SIGNAL_STRENGTH_POOR;
- if (DBG) log("Lte level: "+levelLteRsrp);
- return levelLteRsrp;
+ if (mLteRssnr == INVALID_SNR) levelLteRssnr = 0;
+ else if (mLteRssnr >= 45) levelLteRssnr = SIGNAL_STRENGTH_GREAT;
+ else if (mLteRssnr >= 10) levelLteRssnr = SIGNAL_STRENGTH_GOOD;
+ else if (mLteRssnr >= -30) levelLteRssnr = SIGNAL_STRENGTH_MODERATE;
+ else levelLteRssnr = SIGNAL_STRENGTH_POOR;
+
+ int level;
+ if (mLteRsrp == -1)
+ level = levelLteRssnr;
+ else if (mLteRssnr == INVALID_SNR)
+ level = levelLteRsrp;
+ else
+ level = (levelLteRssnr < levelLteRsrp) ? levelLteRssnr : levelLteRsrp;
+
+ if (DBG) log("Lte rsrp level: "+levelLteRsrp
+ + " snr level: " + levelLteRssnr + " level: " + level);
+ return level;
}
/**
diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java
index ee39850..7900a9d 100644
--- a/telephony/java/com/android/internal/telephony/CommandsInterface.java
+++ b/telephony/java/com/android/internal/telephony/CommandsInterface.java
@@ -93,11 +93,6 @@
static final int USSD_MODE_NOTIFY = 0;
static final int USSD_MODE_REQUEST = 1;
- // SIM Refresh results, passed up from RIL.
- static final int SIM_REFRESH_FILE_UPDATED = 0; // Single file updated
- static final int SIM_REFRESH_INIT = 1; // SIM initialized; reload all
- static final int SIM_REFRESH_RESET = 2; // SIM reset; may be locked
-
// GSM SMS fail cause for acknowledgeLastIncomingSMS. From TS 23.040, 9.2.3.22.
static final int GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED = 0xD3;
static final int GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY = 0xD4;
diff --git a/telephony/java/com/android/internal/telephony/IccCard.java b/telephony/java/com/android/internal/telephony/IccCard.java
index f4308a0..a9ef762 100644
--- a/telephony/java/com/android/internal/telephony/IccCard.java
+++ b/telephony/java/com/android/internal/telephony/IccCard.java
@@ -48,7 +48,7 @@
protected String mLogTag;
protected boolean mDbg;
- private IccCardStatus mIccCardStatus = null;
+ protected IccCardStatus mIccCardStatus = null;
protected State mState = null;
private final Object mStateMonitor = new Object();
@@ -911,4 +911,24 @@
private void log(String msg) {
Log.d(mLogTag, "[IccCard] " + msg);
}
+
+ protected abstract int getCurrentApplicationIndex();
+
+ public String getAid() {
+ String aid = "";
+ int appIndex = getCurrentApplicationIndex();
+
+ if (appIndex >= 0 && appIndex < IccCardStatus.CARD_MAX_APPS) {
+ IccCardApplication app = mIccCardStatus.getApplication(appIndex);
+ if (app != null) {
+ aid = app.aid;
+ } else {
+ Log.e(mLogTag, "[IccCard] getAid: no current application index=" + appIndex);
+ }
+ } else {
+ Log.e(mLogTag, "[IccCard] getAid: Invalid Subscription Application index=" + appIndex);
+ }
+
+ return aid;
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/IccRefreshResponse.java b/telephony/java/com/android/internal/telephony/IccRefreshResponse.java
new file mode 100644
index 0000000..6806703
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IccRefreshResponse.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+/**
+ * See also RIL_SimRefresh in include/telephony/ril.h
+ *
+ * {@hide}
+ */
+
+public class IccRefreshResponse {
+
+ public static final int REFRESH_RESULT_FILE_UPDATE = 0; /* Single file was updated */
+ public static final int REFRESH_RESULT_INIT = 1; /* The Icc has been initialized */
+ public static final int REFRESH_RESULT_RESET = 2; /* The Icc was reset */
+
+ public int refreshResult; /* Sim Refresh result */
+ public int efId; /* EFID */
+ public String aid; /* null terminated string, e.g.,
+ from 0xA0, 0x00 -> 0x41,
+ 0x30, 0x30, 0x30 */
+ /* Example: a0000000871002f310ffff89080000ff */
+
+ @Override
+ public String toString() {
+ return "{" + refreshResult + ", " + aid +", " + efId + "}";
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index 718a4fd..156eb32 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -52,6 +52,7 @@
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
import com.android.internal.telephony.cdma.CdmaInformationRecords;
+import com.android.internal.telephony.IccRefreshResponse;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
@@ -2419,7 +2420,7 @@
case RIL_UNSOL_STK_EVENT_NOTIFY: ret = responseString(p); break;
case RIL_UNSOL_STK_CALL_SETUP: ret = responseInts(p); break;
case RIL_UNSOL_SIM_SMS_STORAGE_FULL: ret = responseVoid(p); break;
- case RIL_UNSOL_SIM_REFRESH: ret = responseInts(p); break;
+ case RIL_UNSOL_SIM_REFRESH: ret = responseSimRefresh(p); break;
case RIL_UNSOL_CALL_RING: ret = responseCallRing(p); break;
case RIL_UNSOL_RESTRICTED_STATE_CHANGED: ret = responseInts(p); break;
case RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED: ret = responseVoid(p); break;
@@ -2976,6 +2977,16 @@
}
private Object
+ responseSimRefresh(Parcel p) {
+ IccRefreshResponse response = new IccRefreshResponse();
+
+ response.refreshResult = p.readInt();
+ response.efId = p.readInt();
+ response.aid = p.readString();
+ return response;
+ }
+
+ private Object
responseCallList(Parcel p) {
int num;
int voiceSettings;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
index 6b73cc5..7da23d0 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
@@ -124,7 +124,9 @@
@Override
protected void setSignalStrengthDefaultValues() {
- mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, false);
+ // TODO Make a constructor only has boolean gsm as parameter
+ mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, SignalStrength.INVALID_SNR, -1, false);
}
@Override
@@ -429,8 +431,13 @@
setSignalStrengthDefaultValues();
} else {
int[] ints = (int[])ar.result;
- int lteCqi = 99, lteRsrp = -1;
- int lteRssi = 99;
+
+ int lteRssi = -1;
+ int lteRsrp = -1;
+ int lteRsrq = -1;
+ int lteRssnr = SignalStrength.INVALID_SNR;
+ int lteCqi = -1;
+
int offset = 2;
int cdmaDbm = (ints[offset] > 0) ? -ints[offset] : -120;
int cdmaEcio = (ints[offset + 1] > 0) ? -ints[offset + 1] : -160;
@@ -438,10 +445,13 @@
int evdoEcio = (ints[offset + 3] > 0) ? -ints[offset + 3] : -1;
int evdoSnr = ((ints[offset + 4] > 0) && (ints[offset + 4] <= 8)) ? ints[offset + 4]
: -1;
+
if (networkType == ServiceState.RADIO_TECHNOLOGY_LTE) {
- lteRssi = (ints[offset + 5] >= 0) ? ints[offset + 5] : 99;
- lteRsrp = (ints[offset + 6] < 0) ? ints[offset + 6] : -1;
- lteCqi = (ints[offset + 7] >= 0) ? ints[offset + 7] : 99;
+ lteRssi = ints[offset+5];
+ lteRsrp = ints[offset+6];
+ lteRsrq = ints[offset+7];
+ lteRssnr = ints[offset+8];
+ lteCqi = ints[offset+9];
}
if (networkType != ServiceState.RADIO_TECHNOLOGY_LTE) {
@@ -449,7 +459,7 @@
evdoSnr, false);
} else {
mSignalStrength = new SignalStrength(99, -1, cdmaDbm, cdmaEcio, evdoRssi, evdoEcio,
- evdoSnr, lteRssi, lteRsrp, -1, -1, lteCqi, true);
+ evdoSnr, lteRssi, lteRsrp, lteRsrq, lteRssnr, lteCqi, true);
}
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimCard.java b/telephony/java/com/android/internal/telephony/cdma/RuimCard.java
index 02eb86d..674fada 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimCard.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimCard.java
@@ -44,5 +44,13 @@
public String getServiceProviderName () {
return mPhone.mIccRecords.getServiceProviderName();
}
+
+ @Override
+ protected int getCurrentApplicationIndex() {
+ if (mIccCardStatus == null) {
+ return -1;
+ }
+ return mIccCardStatus.getCdmaSubscriptionAppIndex();
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java
index b057e46..17a200e 100755
--- a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java
@@ -29,6 +29,7 @@
import com.android.internal.telephony.AdnRecordCache;
import com.android.internal.telephony.AdnRecordLoader;
import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccRefreshResponse;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.cdma.RuimCard;
import com.android.internal.telephony.MccTable;
@@ -300,7 +301,7 @@
isRecordLoadResponse = false;
ar = (AsyncResult)msg.obj;
if (ar.exception == null) {
- handleRuimRefresh((int[])(ar.result));
+ handleRuimRefresh((IccRefreshResponse)ar.result);
}
break;
@@ -409,24 +410,30 @@
((CDMAPhone) phone).notifyMessageWaitingIndicator();
}
- private void handleRuimRefresh(int[] result) {
- if (result == null || result.length == 0) {
- if (DBG) log("handleRuimRefresh without input");
+ private void handleRuimRefresh(IccRefreshResponse refreshResponse) {
+ if (refreshResponse == null) {
+ if (DBG) log("handleRuimRefresh received without input");
return;
}
- switch ((result[0])) {
- case CommandsInterface.SIM_REFRESH_FILE_UPDATED:
+ if (refreshResponse.aid != null &&
+ !refreshResponse.aid.equals(phone.getIccCard().getAid())) {
+ // This is for different app. Ignore.
+ return;
+ }
+
+ switch (refreshResponse.refreshResult) {
+ case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE:
if (DBG) log("handleRuimRefresh with SIM_REFRESH_FILE_UPDATED");
adnCache.reset();
fetchRuimRecords();
break;
- case CommandsInterface.SIM_REFRESH_INIT:
+ case IccRefreshResponse.REFRESH_RESULT_INIT:
if (DBG) log("handleRuimRefresh with SIM_REFRESH_INIT");
// need to reload all files (that we care about)
fetchRuimRecords();
break;
- case CommandsInterface.SIM_REFRESH_RESET:
+ case IccRefreshResponse.REFRESH_RESULT_RESET:
if (DBG) log("handleRuimRefresh with SIM_REFRESH_RESET");
phone.mCM.setRadioPower(false, null);
/* Note: no need to call setRadioPower(true). Assuming the desired
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 92e16ce..0ebeabe 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -675,7 +675,9 @@
}
private void setSignalStrengthDefaultValues() {
- mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, true);
+ // TODO Make a constructor only has boolean gsm as parameter
+ mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, SignalStrength.INVALID_SNR, -1, true);
}
/**
@@ -1013,7 +1015,7 @@
int lteSignalStrength = -1;
int lteRsrp = -1;
int lteRsrq = -1;
- int lteRssnr = -1;
+ int lteRssnr = SignalStrength.INVALID_SNR;
int lteCqi = -1;
if (ar.exception != null) {
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
index 7c423c7..b2fa051 100755
--- a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
@@ -39,6 +39,7 @@
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.IccRefreshResponse;
import java.util.ArrayList;
@@ -1049,7 +1050,7 @@
ar = (AsyncResult)msg.obj;
if (DBG) log("Sim REFRESH with exception: " + ar.exception);
if (ar.exception == null) {
- handleSimRefresh((int[])(ar.result));
+ handleSimRefresh((IccRefreshResponse)ar.result);
}
break;
case EVENT_GET_CFIS_DONE:
@@ -1130,27 +1131,31 @@
}
}
- private void handleSimRefresh(int[] result) {
- if (result == null || result.length == 0) {
- if (DBG) log("handleSimRefresh without input");
+ private void handleSimRefresh(IccRefreshResponse refreshResponse){
+ if (refreshResponse == null) {
+ if (DBG) log("handleSimRefresh received without input");
return;
}
- switch ((result[0])) {
- case CommandsInterface.SIM_REFRESH_FILE_UPDATED:
- if (DBG) log("handleSimRefresh with SIM_REFRESH_FILE_UPDATED");
- // result[1] contains the EFID of the updated file.
- int efid = result[1];
- handleFileUpdate(efid);
+ if (refreshResponse.aid != null &&
+ !refreshResponse.aid.equals(phone.getIccCard().getAid())) {
+ // This is for different app. Ignore.
+ return;
+ }
+
+ switch (refreshResponse.refreshResult) {
+ case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE:
+ if (DBG) log("handleSimRefresh with SIM_FILE_UPDATED");
+ handleFileUpdate(refreshResponse.efId);
break;
- case CommandsInterface.SIM_REFRESH_INIT:
- if (DBG) log("handleSimRefresh with SIM_REFRESH_INIT");
+ case IccRefreshResponse.REFRESH_RESULT_INIT:
+ if (DBG) log("handleSimRefresh with SIM_REFRESH_INIT");
// need to reload all files (that we care about)
adnCache.reset();
fetchSimRecords();
break;
- case CommandsInterface.SIM_REFRESH_RESET:
- if (DBG) log("handleSimRefresh with SIM_REFRESH_RESET");
+ case IccRefreshResponse.REFRESH_RESULT_RESET:
+ if (DBG) log("handleSimRefresh with SIM_REFRESH_RESET");
phone.mCM.setRadioPower(false, null);
/* Note: no need to call setRadioPower(true). Assuming the desired
* radio power state is still ON (as tracked by ServiceStateTracker),
@@ -1162,7 +1167,7 @@
break;
default:
// unknown refresh operation
- if (DBG) log("handleSimRefresh with unknown operation");
+ if (DBG) log("handleSimRefresh with unknown operation");
break;
}
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimCard.java b/telephony/java/com/android/internal/telephony/gsm/SimCard.java
index e34e10a..0e68e07 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimCard.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimCard.java
@@ -39,4 +39,11 @@
return mPhone.mIccRecords.getServiceProviderName();
}
+ @Override
+ protected int getCurrentApplicationIndex() {
+ if (mIccCardStatus == null) {
+ return -1;
+ }
+ return mIccCardStatus.getGsmUmtsSubscriptionAppIndex();
+ }
}
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
index d2e573c..6f0175e 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
@@ -64,7 +64,7 @@
assertEquals(result1, number.toString());
assertEquals(result1.length(), Selection.getSelectionEnd(number));
// Remove last 5 chars
- final String result2 = "(650) 123";
+ final String result2 = "650-123";
textWatcher.beforeTextChanged(number, number.length() - 4, 4, 0);
number.delete(number.length() - 5, number.length());
Selection.setSelection(number, number.length());
@@ -75,26 +75,26 @@
}
public void testInsertChars() {
- final String init = "(650) 23";
- final String expected1 = "(650) 123";
+ final String init = "650-23";
+ final String expected1 = "650-123";
TextWatcher textWatcher = getTextWatcher();
// Insert one char
SpannableStringBuilder number = new SpannableStringBuilder(init);
- textWatcher.beforeTextChanged(number, 4, 0, 1);
- number.insert(4, "1"); // (6501) 23
- Selection.setSelection(number, 5); // make the cursor at right of 1
- textWatcher.onTextChanged(number, 4, 0, 1);
+ textWatcher.beforeTextChanged(number, 3, 0, 1);
+ number.insert(3, "1"); // 6501-23
+ Selection.setSelection(number, 4); // make the cursor at right of 1
+ textWatcher.onTextChanged(number, 3, 0, 1);
textWatcher.afterTextChanged(number);
assertEquals(expected1, number.toString());
// the cursor should still at the right of '1'
- assertEquals(7, Selection.getSelectionEnd(number));
+ assertEquals(5, Selection.getSelectionEnd(number));
// Insert multiple chars
final String expected2 = "(650) 145-6723";
- textWatcher.beforeTextChanged(number, 7, 0, 4);
- number.insert(7, "4567"); // change to (650) 1456723
- Selection.setSelection(number, 11); // the cursor is at the right of '7'.
+ textWatcher.beforeTextChanged(number, 5, 0, 4);
+ number.insert(5, "4567"); // change to 650-1456723
+ Selection.setSelection(number, 9); // the cursor is at the right of '7'.
textWatcher.onTextChanged(number, 7, 0, 4);
textWatcher.afterTextChanged(number);
assertEquals(expected2, number.toString());
@@ -168,7 +168,7 @@
textWatcher.onTextChanged(number, 0, len, 0);
textWatcher.afterTextChanged(number);
- final String expected2 = "(650) 123-4";
+ final String expected2 = "650-1234";
number = new SpannableStringBuilder(init);
textWatcher.beforeTextChanged(number, 9, 0, 1);
number.insert(9, "4"); // (650) 1234
diff --git a/tests/BiDiTests/res/layout/basic.xml b/tests/BiDiTests/res/layout/basic.xml
index f503658..7d4d4db 100644
--- a/tests/BiDiTests/res/layout/basic.xml
+++ b/tests/BiDiTests/res/layout/basic.xml
@@ -19,6 +19,10 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent">
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -131,4 +135,6 @@
</LinearLayout>
+ </ScrollView>
+
</FrameLayout>
diff --git a/tests/BiDiTests/res/values/strings.xml b/tests/BiDiTests/res/values/strings.xml
index 1d4fc84..233cd0d 100644
--- a/tests/BiDiTests/res/values/strings.xml
+++ b/tests/BiDiTests/res/values/strings.xml
@@ -24,11 +24,11 @@
<string name="button_before_text">Start</string>
<string name="button_requestlayout_text">Request Layout</string>
<string name="button_alert_dialog_text">AlertDialog</string>
- <string name="textview_text">This is a text for a TextView</string>
- <string name="textview_ltr_text">This is a text for a LTR TextView</string>
- <string name="textview_rtl_text">This is a text for a RTL TextView</string>
- <string name="textview_default_text">This is a text for a default TextView</string>
- <string name="textview_password_default_text">This is a text for a password TextView</string>
+ <string name="textview_text">TextView</string>
+ <string name="textview_ltr_text">LTR TextView</string>
+ <string name="textview_rtl_text">RTL TextView</string>
+ <string name="textview_default_text">Default TextView</string>
+ <string name="textview_password_default_text">Password TextView</string>
<string name="edittext_text">mmmmmmmmmmmmmmmmmmmmmmmm</string>
<string name="normal_text">Normal String</string>
<string name="normal_long_text">mmmmmmmmmmmmmmmmmmmmmmmm</string>
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 5bbcce3..643cb8d 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -30,6 +30,8 @@
android:label="HwUi"
android:hardwareAccelerated="true">
+ <meta-data android:name="android.graphics.renderThread" android:value="true" />
+
<activity
android:name="PaintDrawFilterActivity"
android:label="_DrawFilter">
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingTest.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingTest.java
index f96e68b..912d863 100644
--- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingTest.java
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingTest.java
@@ -84,7 +84,7 @@
Log.v(TAG, "RenderScript framew time core: " + t + " ms");
}
long avgValue = sum/ITERATION;
- rsWriter.write("Averge frame time: " + avgValue + " ms\n");
+ rsWriter.write("Average frame time: " + avgValue + " ms\n");
Log.v(TAG, "Average frame time: " + avgValue + " ms");
rsWriter.close();
} catch (IOException e) {
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
index 5ab2c58..2293678 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
@@ -65,6 +65,7 @@
unitTests = new ArrayList<UnitTest>();
unitTests.add(new UT_primitives(this, mRes, mCtx));
+ unitTests.add(new UT_constant(this, mRes, mCtx));
unitTests.add(new UT_vector(this, mRes, mCtx));
unitTests.add(new UT_rsdebug(this, mRes, mCtx));
unitTests.add(new UT_rstime(this, mRes, mCtx));
@@ -73,6 +74,7 @@
unitTests.add(new UT_refcount(this, mRes, mCtx));
unitTests.add(new UT_foreach(this, mRes, mCtx));
unitTests.add(new UT_atomic(this, mRes, mCtx));
+ unitTests.add(new UT_struct(this, mRes, mCtx));
unitTests.add(new UT_math(this, mRes, mCtx));
unitTests.add(new UT_fp_mad(this, mRes, mCtx));
/*
@@ -92,7 +94,7 @@
for (int i = 0; i < uta.length; i++) {
ScriptField_ListAllocs_s.Item listElem = new ScriptField_ListAllocs_s.Item();
listElem.text = Allocation.createFromString(mRS, uta[i].name, Allocation.USAGE_SCRIPT);
- listElem.result = uta[i].result;
+ listElem.result = uta[i].getResult();
mListAllocs.set(listElem, i, false);
uta[i].setItem(listElem);
}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_constant.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_constant.java
new file mode 100644
index 0000000..adda5a3
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_constant.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 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.rs.test;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.renderscript.*;
+
+public class UT_constant extends UnitTest {
+ private Resources mRes;
+
+ protected UT_constant(RSTestCore rstc, Resources res, Context ctx) {
+ super(rstc, "Const", ctx);
+ mRes = res;
+ }
+
+ private void Assert(boolean b) {
+ if (!b) {
+ failTest();
+ }
+ }
+
+ public void run() {
+ Assert(ScriptC_constant.const_floatTest == 1.99f);
+ Assert(ScriptC_constant.const_doubleTest == 2.05);
+ Assert(ScriptC_constant.const_charTest == -8);
+ Assert(ScriptC_constant.const_shortTest == -16);
+ Assert(ScriptC_constant.const_intTest == -32);
+ Assert(ScriptC_constant.const_longTest == 17179869184l);
+ Assert(ScriptC_constant.const_longlongTest == 68719476736l);
+
+ Assert(ScriptC_constant.const_ucharTest == 8);
+ Assert(ScriptC_constant.const_ushortTest == 16);
+ Assert(ScriptC_constant.const_uintTest == 32);
+ Assert(ScriptC_constant.const_ulongTest == 4611686018427387904L);
+ Assert(ScriptC_constant.const_int64_tTest == -17179869184l);
+ Assert(ScriptC_constant.const_uint64_tTest == 117179869184l);
+
+ Assert(ScriptC_constant.const_boolTest == true);
+
+ passTest();
+ }
+}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_primitives.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_primitives.java
index b7a65a5..18829c2 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_primitives.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_primitives.java
@@ -92,8 +92,7 @@
ScriptC_primitives s = new ScriptC_primitives(pRS, mRes, R.raw.primitives);
pRS.setMessageHandler(mRsMessage);
if (!initializeGlobals(s)) {
- // initializeGlobals failed
- result = -1;
+ failTest();
} else {
s.invoke_primitives_test(0, 0);
pRS.finish();
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_struct.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_struct.java
new file mode 100644
index 0000000..2a55686
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_struct.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012 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.rs.test;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.renderscript.*;
+
+public class UT_struct extends UnitTest {
+ private Resources mRes;
+
+ protected UT_struct(RSTestCore rstc, Resources res, Context ctx) {
+ super(rstc, "Struct", ctx);
+ mRes = res;
+ }
+
+ public void run() {
+ RenderScript pRS = RenderScript.create(mCtx);
+ ScriptC_struct s = new ScriptC_struct(pRS, mRes, R.raw.struct);
+ pRS.setMessageHandler(mRsMessage);
+
+ ScriptField_Point2 p = new ScriptField_Point2(pRS, 1);
+ ScriptField_Point2.Item i = new ScriptField_Point2.Item();
+ int val = 100;
+ i.x = val;
+ i.y = val;
+ p.set(i, 0, true);
+ s.bind_point2(p);
+ s.invoke_struct_test(val);
+ pRS.finish();
+ waitForMessage();
+
+ val = 200;
+ p.set_x(0, val, true);
+ p.set_y(0, val, true);
+ s.invoke_struct_test(val);
+ pRS.finish();
+ waitForMessage();
+ pRS.destroy();
+ }
+}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java
index 748701d..0ac09ca 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java
@@ -307,7 +307,7 @@
ScriptC_vector s = new ScriptC_vector(pRS, mRes, R.raw.vector);
pRS.setMessageHandler(mRsMessage);
if (!initializeGlobals(s)) {
- result = -1;
+ failTest();
} else {
s.invoke_vector_test();
pRS.finish();
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java
index a97ffa7..edff83f 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java
@@ -21,7 +21,7 @@
public class UnitTest extends Thread {
public String name;
- public int result;
+ private int result;
private ScriptField_ListAllocs_s.Item mItem;
private RSTestCore mRSTC;
private boolean msgHandled;
@@ -63,7 +63,7 @@
}
}
- protected void updateUI() {
+ private void updateUI() {
if (mItem != null) {
mItem.result = result;
msgHandled = true;
@@ -104,6 +104,22 @@
}
}
+ public int getResult() {
+ return result;
+ }
+
+ public void failTest() {
+ result = -1;
+ updateUI();
+ }
+
+ public void passTest() {
+ if (result != -1) {
+ result = 1;
+ }
+ updateUI();
+ }
+
public void setItem(ScriptField_ListAllocs_s.Item item) {
mItem = item;
}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/constant.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/constant.rs
new file mode 100644
index 0000000..732eaef
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/constant.rs
@@ -0,0 +1,19 @@
+#include "shared.rsh"
+
+const float floatTest = 1.99f;
+const double doubleTest = 2.05;
+const char charTest = -8;
+const short shortTest = -16;
+const int intTest = -32;
+const long longTest = 17179869184l; // 1 << 34
+const long long longlongTest = 68719476736l; // 1 << 36
+
+const uchar ucharTest = 8;
+const ushort ushortTest = 16;
+const uint uintTest = 32;
+const ulong ulongTest = 4611686018427387904L;
+const int64_t int64_tTest = -17179869184l; // - 1 << 34
+const uint64_t uint64_tTest = 117179869184l;
+
+const bool boolTest = true;
+
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/struct.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/struct.rs
new file mode 100644
index 0000000..1cd728e
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/struct.rs
@@ -0,0 +1,37 @@
+#include "shared.rsh"
+
+typedef struct Point2 {
+ int x;
+ int y;
+} Point_2;
+Point_2 *point2;
+
+static bool test_Point_2(int expected) {
+ bool failed = false;
+
+ rsDebug("Point: ", point2[0].x, point2[0].y);
+ _RS_ASSERT(point2[0].x == expected);
+ _RS_ASSERT(point2[0].y == expected);
+
+ if (failed) {
+ rsDebug("test_Point_2 FAILED", 0);
+ }
+ else {
+ rsDebug("test_Point_2 PASSED", 0);
+ }
+
+ return failed;
+}
+
+void struct_test(int expected) {
+ bool failed = false;
+ failed |= test_Point_2(expected);
+
+ if (failed) {
+ rsSendToClientBlocking(RS_MSG_TEST_FAILED);
+ }
+ else {
+ rsSendToClientBlocking(RS_MSG_TEST_PASSED);
+ }
+}
+
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
index d5345b2..c5a397c 100644
--- a/tools/aapt/AaptAssets.h
+++ b/tools/aapt/AaptAssets.h
@@ -53,17 +53,6 @@
AXIS_END = AXIS_VERSION,
};
-enum {
- SDK_CUPCAKE = 3,
- SDK_DONUT = 4,
- SDK_ECLAIR = 5,
- SDK_ECLAIR_0_1 = 6,
- SDK_MR1 = 7,
- SDK_FROYO = 8,
- SDK_HONEYCOMB_MR2 = 13,
- SDK_ICE_CREAM_SANDWICH = 14,
-};
-
/**
* This structure contains a specific variation of a single file out
* of all the variations it can have that we can have.
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index 2d1060b..8e3a1c9 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -14,6 +14,18 @@
#include <utils/String8.h>
#include <utils/Vector.h>
+enum {
+ SDK_CUPCAKE = 3,
+ SDK_DONUT = 4,
+ SDK_ECLAIR = 5,
+ SDK_ECLAIR_0_1 = 6,
+ SDK_MR1 = 7,
+ SDK_FROYO = 8,
+ SDK_HONEYCOMB_MR2 = 13,
+ SDK_ICE_CREAM_SANDWICH = 14,
+ SDK_ICE_CREAM_SANDWICH_MR1 = 15,
+};
+
/*
* Things we can do.
*/
@@ -82,7 +94,6 @@
void setRequireLocalization(bool val) { mRequireLocalization = val; }
bool getPseudolocalize(void) const { return mPseudolocalize; }
void setPseudolocalize(bool val) { mPseudolocalize = val; }
- bool getWantUTF16(void) const { return mWantUTF16; }
void setWantUTF16(bool val) { mWantUTF16 = val; }
bool getValues(void) const { return mValues; }
void setValues(bool val) { mValues = val; }
@@ -103,6 +114,10 @@
bool getGenDependencies() { return mGenDependencies; }
void setGenDependencies(bool val) { mGenDependencies = val; }
+ bool getUTF16StringsOption() {
+ return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO);
+ }
+
/*
* Input options.
*/
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 89942de..607056a 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -479,6 +479,11 @@
#ifndef HAVE_ANDROID_OS
res.print(bundle->getValues());
#endif
+
+ } else if (strcmp("strings", option) == 0) {
+ const ResStringPool* pool = res.getTableStringBlock(0);
+ printStringPool(pool);
+
} else if (strcmp("xmltree", option) == 0) {
if (bundle->getFileSpecCount() < 3) {
fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
@@ -1382,7 +1387,7 @@
delete dir;
}
} else if (strcmp("badger", option) == 0) {
- printf(CONSOLE_DATA);
+ printf("%s", CONSOLE_DATA);
} else if (strcmp("configurations", option) == 0) {
Vector<ResTable_config> configs;
res.getConfigurations(&configs);
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 1ecf7da..c0fe538 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -847,8 +847,7 @@
* request UTF-16 encoding and the parameters of this package
* allow UTF-8 to be used.
*/
- if (!bundle->getWantUTF16()
- && bundle->isMinSdkAtLeast(SDK_FROYO)) {
+ if (!bundle->getUTF16StringsOption()) {
xmlFlags |= XML_COMPILE_UTF8;
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index fdb39ca..f59bba2 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2047,7 +2047,8 @@
uint32_t attrID,
const Vector<StringPool::entry_style_span>* style,
String16* outStr, void* accessorCookie,
- uint32_t attrType)
+ uint32_t attrType, const String8* configTypeName,
+ const ConfigDescription* config)
{
String16 finalStr;
@@ -2075,10 +2076,19 @@
if (outValue->dataType == outValue->TYPE_STRING) {
// Should do better merging styles.
if (pool) {
- if (style != NULL && style->size() > 0) {
- outValue->data = pool->add(finalStr, *style);
+ String8 configStr;
+ if (config != NULL) {
+ configStr = config->toString();
} else {
- outValue->data = pool->add(finalStr, true);
+ configStr = "(null)";
+ }
+ NOISY(printf("Adding to pool string style #%d config %s: %s\n",
+ style != NULL ? style->size() : 0,
+ configStr.string(), String8(finalStr).string()));
+ if (style != NULL && style->size() > 0) {
+ outValue->data = pool->add(finalStr, *style, configTypeName, config);
+ } else {
+ outValue->data = pool->add(finalStr, true, configTypeName, config);
}
} else {
// Caller will fill this in later.
@@ -2537,16 +2547,19 @@
return err;
}
+ const ConfigDescription nullConfig;
+
const size_t N = mOrderedPackages.size();
size_t pi;
const static String16 mipmap16("mipmap");
- bool useUTF8 = !bundle->getWantUTF16() && bundle->isMinSdkAtLeast(SDK_FROYO);
+ bool useUTF8 = !bundle->getUTF16StringsOption();
// Iterate through all data, collecting all values (strings,
// references, etc).
StringPool valueStrings = StringPool(false, useUTF8);
+ Vector<sp<Entry> > allEntries;
for (pi=0; pi<N; pi++) {
sp<Package> p = mOrderedPackages.itemAt(pi);
if (p->getTypes().size() == 0) {
@@ -2567,6 +2580,19 @@
const String16 typeName(t->getName());
typeStrings.add(typeName, false);
+ // This is a hack to tweak the sorting order of the final strings,
+ // to put stuff that is generally not language-specific first.
+ String8 configTypeName(typeName);
+ if (configTypeName == "drawable" || configTypeName == "layout"
+ || configTypeName == "color" || configTypeName == "anim"
+ || configTypeName == "interpolator" || configTypeName == "animator"
+ || configTypeName == "xml" || configTypeName == "menu"
+ || configTypeName == "mipmap" || configTypeName == "raw") {
+ configTypeName = "1complex";
+ } else {
+ configTypeName = "2value";
+ }
+
const bool filterable = (typeName != mipmap16);
const size_t N = t->getOrderedConfigs().size();
@@ -2586,10 +2612,21 @@
continue;
}
e->setNameIndex(keyStrings.add(e->getName(), true));
- status_t err = e->prepareFlatten(&valueStrings, this);
+
+ // If this entry has no values for other configs,
+ // and is the default config, then it is special. Otherwise
+ // we want to add it with the config info.
+ ConfigDescription* valueConfig = NULL;
+ if (N != 1 || config == nullConfig) {
+ valueConfig = &config;
+ }
+
+ status_t err = e->prepareFlatten(&valueStrings, this,
+ &configTypeName, &config);
if (err != NO_ERROR) {
return err;
}
+ allEntries.add(e);
}
}
}
@@ -2598,6 +2635,17 @@
p->setKeyStrings(keyStrings.createStringBlock());
}
+ if (bundle->getOutputAPKFile() != NULL) {
+ // Now we want to sort the value strings for better locality. This will
+ // cause the positions of the strings to change, so we need to go back
+ // through out resource entries and update them accordingly. Only need
+ // to do this if actually writing the output file.
+ valueStrings.sortByConfig();
+ for (pi=0; pi<allEntries.size(); pi++) {
+ allEntries[pi]->remapStringValue(&valueStrings);
+ }
+ }
+
ssize_t strAmt = 0;
// Now build the array of package chunks.
@@ -3137,14 +3185,16 @@
return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
}
-status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table)
+status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table,
+ const String8* configTypeName, const ConfigDescription* config)
{
if (mType == TYPE_ITEM) {
Item& it = mItem;
AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value));
if (!table->stringToValue(&it.parsedValue, strings,
it.value, false, true, 0,
- &it.style, NULL, &ac, mItemFormat)) {
+ &it.style, NULL, &ac, mItemFormat,
+ configTypeName, config)) {
return UNKNOWN_ERROR;
}
} else if (mType == TYPE_BAG) {
@@ -3155,7 +3205,8 @@
AccessorCookie ac(it.sourcePos, String8(key), String8(it.value));
if (!table->stringToValue(&it.parsedValue, strings,
it.value, false, true, it.bagKeyId,
- &it.style, NULL, &ac, it.format)) {
+ &it.style, NULL, &ac, it.format,
+ configTypeName, config)) {
return UNKNOWN_ERROR;
}
}
@@ -3167,6 +3218,29 @@
return NO_ERROR;
}
+status_t ResourceTable::Entry::remapStringValue(StringPool* strings)
+{
+ if (mType == TYPE_ITEM) {
+ Item& it = mItem;
+ if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
+ it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
+ }
+ } else if (mType == TYPE_BAG) {
+ const size_t N = mBag.size();
+ for (size_t i=0; i<N; i++) {
+ Item& it = mBag.editValueAt(i);
+ if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
+ it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
+ }
+ }
+ } else {
+ mPos.error("Error: entry %s is not a single item or a bag.\n",
+ String8(mName).string());
+ return UNKNOWN_ERROR;
+ }
+ return NO_ERROR;
+}
+
ssize_t ResourceTable::Entry::flatten(Bundle* bundle, const sp<AaptFile>& data, bool isPublic)
{
size_t amt = 0;
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index 8123bb3..a3e0666 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -76,6 +76,37 @@
class Type;
class Entry;
+ struct ConfigDescription : public ResTable_config {
+ ConfigDescription() {
+ memset(this, 0, sizeof(*this));
+ size = sizeof(ResTable_config);
+ }
+ ConfigDescription(const ResTable_config&o) {
+ *static_cast<ResTable_config*>(this) = o;
+ size = sizeof(ResTable_config);
+ }
+ ConfigDescription(const ConfigDescription&o) {
+ *static_cast<ResTable_config*>(this) = o;
+ }
+
+ ConfigDescription& operator=(const ResTable_config& o) {
+ *static_cast<ResTable_config*>(this) = o;
+ size = sizeof(ResTable_config);
+ return *this;
+ }
+ ConfigDescription& operator=(const ConfigDescription& o) {
+ *static_cast<ResTable_config*>(this) = o;
+ return *this;
+ }
+
+ inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; }
+ inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; }
+ inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; }
+ inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; }
+ inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; }
+ inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; }
+ };
+
ResourceTable(Bundle* bundle, const String16& assetsPackage);
status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets);
@@ -183,7 +214,9 @@
uint32_t attrID,
const Vector<StringPool::entry_style_span>* style = NULL,
String16* outStr = NULL, void* accessorCookie = NULL,
- uint32_t attrType = ResTable_map::TYPE_ANY);
+ uint32_t attrType = ResTable_map::TYPE_ANY,
+ const String8* configTypeName = NULL,
+ const ConfigDescription* config = NULL);
status_t assignResourceIds();
status_t addSymbols(const sp<AaptSymbols>& outSymbols = NULL);
@@ -305,7 +338,10 @@
status_t assignResourceIds(ResourceTable* table,
const String16& package);
- status_t prepareFlatten(StringPool* strings, ResourceTable* table);
+ status_t prepareFlatten(StringPool* strings, ResourceTable* table,
+ const String8* configTypeName, const ConfigDescription* config);
+
+ status_t remapStringValue(StringPool* strings);
ssize_t flatten(Bundle*, const sp<AaptFile>& data, bool isPublic);
@@ -322,37 +358,6 @@
uint32_t mParentId;
SourcePos mPos;
};
-
- struct ConfigDescription : public ResTable_config {
- ConfigDescription() {
- memset(this, 0, sizeof(*this));
- size = sizeof(ResTable_config);
- }
- ConfigDescription(const ResTable_config&o) {
- *static_cast<ResTable_config*>(this) = o;
- size = sizeof(ResTable_config);
- }
- ConfigDescription(const ConfigDescription&o) {
- *static_cast<ResTable_config*>(this) = o;
- }
-
- ConfigDescription& operator=(const ResTable_config& o) {
- *static_cast<ResTable_config*>(this) = o;
- size = sizeof(ResTable_config);
- return *this;
- }
- ConfigDescription& operator=(const ConfigDescription& o) {
- *static_cast<ResTable_config*>(this) = o;
- return *this;
- }
-
- inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; }
- inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; }
- inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; }
- inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; }
- inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; }
- inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; }
- };
class ConfigList : public RefBase {
public:
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index 9a0a1c4..fe88e37 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -5,8 +5,10 @@
//
#include "StringPool.h"
+#include "ResourceTable.h"
#include <utils/ByteOrder.h>
+#include <utils/SortedVector.h>
#if HAVE_PRINTF_ZD
# define ZD "%zd"
@@ -30,31 +32,87 @@
void printStringPool(const ResStringPool* pool)
{
+ SortedVector<const void*> uniqueStrings;
+ const size_t N = pool->size();
+ for (size_t i=0; i<N; i++) {
+ size_t len;
+ if (pool->isUTF8()) {
+ uniqueStrings.add(pool->string8At(i, &len));
+ } else {
+ uniqueStrings.add(pool->stringAt(i, &len));
+ }
+ }
+
+ printf("String pool of " ZD " unique %s %s strings, " ZD " entries and "
+ ZD " styles using " ZD " bytes:\n",
+ (ZD_TYPE)uniqueStrings.size(), pool->isUTF8() ? "UTF-8" : "UTF-16",
+ pool->isSorted() ? "sorted" : "non-sorted",
+ (ZD_TYPE)N, (ZD_TYPE)pool->styleCount(), (ZD_TYPE)pool->bytes());
+
const size_t NS = pool->size();
for (size_t s=0; s<NS; s++) {
- size_t len;
- const char *str = (const char*)pool->string8At(s, &len);
- if (str == NULL) {
- str = String8(pool->stringAt(s, &len)).string();
- }
-
- printf("String #" ZD ": %s\n", (ZD_TYPE) s, str);
+ String8 str = pool->string8ObjectAt(s);
+ printf("String #" ZD ": %s\n", (ZD_TYPE) s, str.string());
}
}
+String8 StringPool::entry::makeConfigsString() const {
+ String8 configStr(configTypeName);
+ if (configStr.size() > 0) configStr.append(" ");
+ if (configs.size() > 0) {
+ for (size_t j=0; j<configs.size(); j++) {
+ if (j > 0) configStr.append(", ");
+ configStr.append(configs[j].toString());
+ }
+ } else {
+ configStr = "(none)";
+ }
+ return configStr;
+}
+
+int StringPool::entry::compare(const entry& o) const {
+ // Strings with styles go first, to reduce the size of the
+ // styles array.
+ if (hasStyles) {
+ return o.hasStyles ? 0 : -1;
+ }
+ if (o.hasStyles) {
+ return 1;
+ }
+ int comp = configTypeName.compare(o.configTypeName);
+ if (comp != 0) {
+ return comp;
+ }
+ const size_t LHN = configs.size();
+ const size_t RHN = o.configs.size();
+ size_t i=0;
+ while (i < LHN && i < RHN) {
+ comp = configs[i].compareLogical(o.configs[i]);
+ if (comp != 0) {
+ return comp;
+ }
+ i++;
+ }
+ if (LHN < RHN) return -1;
+ else if (LHN > RHN) return 1;
+ return 0;
+}
+
StringPool::StringPool(bool sorted, bool utf8)
: mSorted(sorted), mUTF8(utf8), mValues(-1), mIdents(-1)
{
}
-ssize_t StringPool::add(const String16& value, bool mergeDuplicates)
+ssize_t StringPool::add(const String16& value, bool mergeDuplicates,
+ const String8* configTypeName, const ResTable_config* config)
{
- return add(String16(), value, mergeDuplicates);
+ return add(String16(), value, mergeDuplicates, configTypeName, config);
}
-ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans)
+ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans,
+ const String8* configTypeName, const ResTable_config* config)
{
- ssize_t res = add(String16(), value, false);
+ ssize_t res = add(String16(), value, false, configTypeName, config);
if (res >= 0) {
addStyleSpans(res, spans);
}
@@ -62,7 +120,7 @@
}
ssize_t StringPool::add(const String16& ident, const String16& value,
- bool mergeDuplicates)
+ bool mergeDuplicates, const String8* configTypeName, const ResTable_config* config)
{
if (ident.size() > 0) {
ssize_t idx = mIdents.valueFor(ident);
@@ -84,20 +142,43 @@
}
}
+ if (configTypeName != NULL) {
+ entry& ent = mEntries.editItemAt(eidx);
+ NOISY(printf("*** adding config type name %s, was %s\n",
+ configTypeName->string(), ent.configTypeName.string()));
+ if (ent.configTypeName.size() <= 0) {
+ ent.configTypeName = *configTypeName;
+ } else if (ent.configTypeName != *configTypeName) {
+ ent.configTypeName = " ";
+ }
+ }
+
+ if (config != NULL) {
+ // Add this to the set of configs associated with the string.
+ entry& ent = mEntries.editItemAt(eidx);
+ size_t addPos;
+ for (addPos=0; addPos<ent.configs.size(); addPos++) {
+ int cmp = ent.configs.itemAt(addPos).compareLogical(*config);
+ if (cmp >= 0) {
+ if (cmp > 0) {
+ NOISY(printf("*** inserting config: %s\n", config->toString().string()));
+ ent.configs.insertAt(*config, addPos);
+ }
+ break;
+ }
+ }
+ if (addPos >= ent.configs.size()) {
+ NOISY(printf("*** adding config: %s\n", config->toString().string()));
+ ent.configs.add(*config);
+ }
+ }
+
const bool first = vidx < 0;
if (first || !mergeDuplicates) {
pos = mEntryArray.add(eidx);
if (first) {
vidx = mValues.add(value, pos);
- const size_t N = mEntryArrayToValues.size();
- for (size_t i=0; i<N; i++) {
- size_t& e = mEntryArrayToValues.editItemAt(i);
- if ((ssize_t)e >= vidx) {
- e++;
- }
- }
}
- mEntryArrayToValues.add(vidx);
if (!mSorted) {
entry& ent = mEntries.editItemAt(eidx);
ent.indices.add(pos);
@@ -147,6 +228,7 @@
entry_style& style = mEntryStyleArray.editItemAt(idx);
style.spans.add(span);
+ mEntries.editItemAt(mEntryArray[idx]).hasStyles = true;
return NO_ERROR;
}
@@ -169,6 +251,132 @@
return mIdents.size();
}
+int StringPool::config_sort(const size_t* lhs, const size_t* rhs, void* state)
+{
+ StringPool* pool = (StringPool*)state;
+ const entry& lhe = pool->mEntries[pool->mEntryArray[*lhs]];
+ const entry& rhe = pool->mEntries[pool->mEntryArray[*rhs]];
+ return lhe.compare(rhe);
+}
+
+void StringPool::sortByConfig()
+{
+ LOG_ALWAYS_FATAL_IF(mSorted, "Can't sort string pool containing identifiers.");
+ LOG_ALWAYS_FATAL_IF(mIdents.size() > 0, "Can't sort string pool containing identifiers.");
+ LOG_ALWAYS_FATAL_IF(mOriginalPosToNewPos.size() > 0, "Can't sort string pool after already sorted.");
+
+ const size_t N = mEntryArray.size();
+
+ // This is a vector that starts out with a 1:1 mapping to entries
+ // in the array, which we will sort to come up with the desired order.
+ // At that point it maps from the new position in the array to the
+ // original position the entry appeared.
+ Vector<size_t> newPosToOriginalPos;
+ for (size_t i=0; i<mEntryArray.size(); i++) {
+ newPosToOriginalPos.add(i);
+ }
+
+ // Sort the array.
+ NOISY(printf("SORTING STRINGS BY CONFIGURATION...\n"));
+ newPosToOriginalPos.sort(config_sort, this);
+ NOISY(printf("DONE SORTING STRINGS BY CONFIGURATION.\n"));
+
+ // Create the reverse mapping from the original position in the array
+ // to the new position where it appears in the sorted array. This is
+ // so that clients can re-map any positions they had previously stored.
+ mOriginalPosToNewPos = newPosToOriginalPos;
+ for (size_t i=0; i<N; i++) {
+ mOriginalPosToNewPos.editItemAt(newPosToOriginalPos[i]) = i;
+ }
+
+#if 0
+ SortedVector<entry> entries;
+
+ for (size_t i=0; i<N; i++) {
+ printf("#%d was %d: %s\n", i, newPosToOriginalPos[i],
+ mEntries[mEntryArray[newPosToOriginalPos[i]]].makeConfigsString().string());
+ entries.add(mEntries[mEntryArray[i]]);
+ }
+
+ for (size_t i=0; i<entries.size(); i++) {
+ printf("Sorted config #%d: %s\n", i,
+ entries[i].makeConfigsString().string());
+ }
+#endif
+
+ // Now we rebuild the arrays.
+ Vector<entry> newEntries;
+ Vector<size_t> newEntryArray;
+ Vector<entry_style> newEntryStyleArray;
+ DefaultKeyedVector<size_t, size_t> origOffsetToNewOffset;
+
+ for (size_t i=0; i<N; i++) {
+ // We are filling in new offset 'i'; oldI is where we can find it
+ // in the original data structure.
+ size_t oldI = newPosToOriginalPos[i];
+ // This is the actual entry associated with the old offset.
+ const entry& oldEnt = mEntries[mEntryArray[oldI]];
+ // This is the same entry the last time we added it to the
+ // new entry array, if any.
+ ssize_t newIndexOfOffset = origOffsetToNewOffset.indexOfKey(oldI);
+ size_t newOffset;
+ if (newIndexOfOffset < 0) {
+ // This is the first time we have seen the entry, so add
+ // it.
+ newOffset = newEntries.add(oldEnt);
+ newEntries.editItemAt(newOffset).indices.clear();
+ } else {
+ // We have seen this entry before, use the existing one
+ // instead of adding it again.
+ newOffset = origOffsetToNewOffset.valueAt(newIndexOfOffset);
+ }
+ // Update the indices to include this new position.
+ newEntries.editItemAt(newOffset).indices.add(i);
+ // And add the offset of the entry to the new entry array.
+ newEntryArray.add(newOffset);
+ // Add any old style to the new style array.
+ if (mEntryStyleArray.size() > 0) {
+ if (oldI < mEntryStyleArray.size()) {
+ newEntryStyleArray.add(mEntryStyleArray[oldI]);
+ } else {
+ newEntryStyleArray.add(entry_style());
+ }
+ }
+ }
+
+ // Now trim any entries at the end of the new style array that are
+ // not needed.
+ for (ssize_t i=newEntryStyleArray.size()-1; i>=0; i--) {
+ const entry_style& style = newEntryStyleArray[i];
+ if (style.spans.size() > 0) {
+ // That's it.
+ break;
+ }
+ // This one is not needed; remove.
+ newEntryStyleArray.removeAt(i);
+ }
+
+ // All done, install the new data structures and upate mValues with
+ // the new positions.
+ mEntries = newEntries;
+ mEntryArray = newEntryArray;
+ mEntryStyleArray = newEntryStyleArray;
+ mValues.clear();
+ for (size_t i=0; i<mEntries.size(); i++) {
+ const entry& ent = mEntries[i];
+ mValues.add(ent.value, ent.indices[0]);
+ }
+
+#if 0
+ printf("FINAL SORTED STRING CONFIGS:\n");
+ for (size_t i=0; i<mEntries.size(); i++) {
+ const entry& ent = mEntries[i];
+ printf("#" ZD " %s: %s\n", (ZD_TYPE)i, ent.makeConfigsString().string(),
+ String8(ent.value).string());
+ }
+#endif
+}
+
sp<AaptFile> StringPool::createStringBlock()
{
sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(),
diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h
index 7275259..255bdbf 100644
--- a/tools/aapt/StringPool.h
+++ b/tools/aapt/StringPool.h
@@ -40,12 +40,28 @@
public:
struct entry {
entry() : offset(0) { }
- entry(const String16& _value) : value(_value), offset(0) { }
- entry(const entry& o) : value(o.value), offset(o.offset), indices(o.indices) { }
+ entry(const String16& _value) : value(_value), offset(0), hasStyles(false) { }
+ entry(const entry& o) : value(o.value), offset(o.offset),
+ hasStyles(o.hasStyles), indices(o.indices),
+ configTypeName(o.configTypeName), configs(o.configs) { }
String16 value;
size_t offset;
+ bool hasStyles;
Vector<size_t> indices;
+ String8 configTypeName;
+ Vector<ResTable_config> configs;
+
+ String8 makeConfigsString() const;
+
+ int compare(const entry& o) const;
+
+ inline bool operator<(const entry& o) const { return compare(o) < 0; }
+ inline bool operator<=(const entry& o) const { return compare(o) <= 0; }
+ inline bool operator==(const entry& o) const { return compare(o) == 0; }
+ inline bool operator!=(const entry& o) const { return compare(o) != 0; }
+ inline bool operator>=(const entry& o) const { return compare(o) >= 0; }
+ inline bool operator>(const entry& o) const { return compare(o) > 0; }
};
struct entry_style_span {
@@ -84,12 +100,15 @@
* if this string pool is sorted, the returned index will not be valid
* when the pool is finally written.
*/
- ssize_t add(const String16& value, bool mergeDuplicates = false);
+ ssize_t add(const String16& value, bool mergeDuplicates = false,
+ const String8* configTypeName = NULL, const ResTable_config* config = NULL);
- ssize_t add(const String16& value, const Vector<entry_style_span>& spans);
+ ssize_t add(const String16& value, const Vector<entry_style_span>& spans,
+ const String8* configTypeName = NULL, const ResTable_config* config = NULL);
ssize_t add(const String16& ident, const String16& value,
- bool mergeDuplicates = false);
+ bool mergeDuplicates = false,
+ const String8* configTypeName = NULL, const ResTable_config* config = NULL);
status_t addStyleSpan(size_t idx, const String16& name,
uint32_t start, uint32_t end);
@@ -102,6 +121,18 @@
size_t countIdentifiers() const;
+ // Sort the contents of the string block by the configuration associated
+ // with each item. After doing this you can use mapOriginalPosToNewPos()
+ // to find out the new position given the position originall returned by
+ // add().
+ void sortByConfig();
+
+ // For use after sortByConfig() to map from the original position of
+ // a string to its new sorted position.
+ size_t mapOriginalPosToNewPos(size_t originalPos) const {
+ return mOriginalPosToNewPos.itemAt(originalPos);
+ }
+
sp<AaptFile> createStringBlock();
status_t writeStringBlock(const sp<AaptFile>& pool);
@@ -125,27 +156,41 @@
const Vector<size_t>* offsetsForString(const String16& val) const;
private:
+ static int config_sort(const size_t* lhs, const size_t* rhs, void* state);
+
const bool mSorted;
const bool mUTF8;
- // Raw array of unique strings, in some arbitrary order.
+
+ // The following data structures represent the actual structures
+ // that will be generated for the final string pool.
+
+ // Raw array of unique strings, in some arbitrary order. This is the
+ // actual strings that appear in the final string pool, in the order
+ // that they will be written.
Vector<entry> mEntries;
// Array of indices into mEntries, in the order they were
// added to the pool. This can be different than mEntries
// if the same string was added multiple times (it will appear
// once in mEntries, with multiple occurrences in this array).
+ // This is the lookup array that will be written for finding
+ // the string for each offset/position in the string pool.
Vector<size_t> mEntryArray;
// Optional style span information associated with each index of
// mEntryArray.
Vector<entry_style> mEntryStyleArray;
- // Mapping from indices in mEntryArray to indices in mValues.
- Vector<size_t> mEntryArrayToValues;
+
+ // The following data structures are used for book-keeping as the
+ // string pool is constructed.
+
// Unique set of all the strings added to the pool, mapped to
// the first index of mEntryArray where the value was added.
DefaultKeyedVector<String16, ssize_t> mValues;
// Unique set of all (optional) identifiers of strings in the
// pool, mapping to indices in mEntries.
DefaultKeyedVector<String16, ssize_t> mIdents;
-
+ // This array maps from the original position a string was placed at
+ // in mEntryArray to its new position after being sorted with sortByConfig().
+ Vector<size_t> mOriginalPosToNewPos;
};
#endif