Merge "remove uses of Surface in favor of IGraphicBufferProducer" into jb-mr2-dev
diff --git a/api/current.txt b/api/current.txt
index 3132a91..be104a0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5919,7 +5919,6 @@
field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
- field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
field public static final java.lang.String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -10046,16 +10045,20 @@
field public static final int TYPE_ACCELEROMETER = 1; // 0x1
field public static final int TYPE_ALL = -1; // 0xffffffff
field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd
+ field public static final int TYPE_GAME_ROTATION_VECTOR = 15; // 0xf
field public static final int TYPE_GRAVITY = 9; // 0x9
field public static final int TYPE_GYROSCOPE = 4; // 0x4
+ field public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16; // 0x10
field public static final int TYPE_LIGHT = 5; // 0x5
field public static final int TYPE_LINEAR_ACCELERATION = 10; // 0xa
field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2
+ field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe
field public static final deprecated int TYPE_ORIENTATION = 3; // 0x3
field public static final int TYPE_PRESSURE = 6; // 0x6
field public static final int TYPE_PROXIMITY = 8; // 0x8
field public static final int TYPE_RELATIVE_HUMIDITY = 12; // 0xc
field public static final int TYPE_ROTATION_VECTOR = 11; // 0xb
+ field public static final int TYPE_SIGNIFICANT_MOTION = 17; // 0x11
field public static final deprecated int TYPE_TEMPERATURE = 7; // 0x7
}
@@ -10077,6 +10080,7 @@
}
public abstract class SensorManager {
+ method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
method public static float getAltitude(float, float);
method public static void getAngleChange(float[], float[], float[]);
method public android.hardware.Sensor getDefaultSensor(int);
@@ -10092,6 +10096,7 @@
method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int);
method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, android.os.Handler);
method public static boolean remapCoordinateSystem(float[], int, int, float[]);
+ method public boolean requestTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
method public deprecated void unregisterListener(android.hardware.SensorListener);
method public deprecated void unregisterListener(android.hardware.SensorListener, int);
method public void unregisterListener(android.hardware.SensorEventListener, android.hardware.Sensor);
@@ -10155,6 +10160,17 @@
field public static final float STANDARD_GRAVITY = 9.80665f;
}
+ public final class TriggerEvent {
+ field public android.hardware.Sensor sensor;
+ field public long timestamp;
+ field public final float[] values;
+ }
+
+ public abstract class TriggerEventListener {
+ ctor public TriggerEventListener();
+ method public abstract void onTrigger(android.hardware.TriggerEvent);
+ }
+
}
package android.hardware.display {
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 18492ab..f44dc5c0 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -529,7 +529,8 @@
case FORMAT_UINT32:
mValue[offset++] = (byte)(value & 0xFF);
mValue[offset++] = (byte)((value >> 8) & 0xFF);
- mValue[offset] = (byte)((value >> 16) & 0xFF);
+ mValue[offset++] = (byte)((value >> 16) & 0xFF);
+ mValue[offset] = (byte)((value >> 24) & 0xFF);
break;
default:
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 91a1a94..6b69377 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -534,7 +534,7 @@
*
* <p>The connection may not be established right away, but will be
* completed when the remote device is available. A
- * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+ * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
* invoked when the connection state changes as a result of this function.
*
* <p>The autoConnect paramter determines whether to actively connect to
@@ -553,7 +553,7 @@
* @return true, if the connection attempt was initiated successfully
*/
public boolean connect(BluetoothDevice device, boolean autoConnect) {
- if (DBG) Log.d(TAG, "connect: " + device.getAddress() + ", auto: " + autoConnect);
+ if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
if (mService == null || mServerIf == 0) return false;
try {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 60e9f58..53c47d2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1164,12 +1164,13 @@
* additional optional contextual information about where the user was when they requested
* the voice assist.
* Output: nothing.
+ * @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
/**
- * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+ * An optional field on {@link #ACTION_ASSIST}
* containing the name of the current foreground application package at the time
* the assist was invoked.
*/
@@ -1177,7 +1178,7 @@
= "android.intent.extra.ASSIST_PACKAGE";
/**
- * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+ * An optional field on {@link #ACTION_ASSIST}
* containing additional contextual information supplied by the current
* foreground app at the time of the assist request. This is a {@link Bundle} of
* additional data.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c507245..0d463ee 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -691,6 +691,17 @@
public static final int DELETE_ALL_USERS = 0x00000002;
/**
+ * Flag parameter for {@link #deletePackage} to indicate that, if you are calling
+ * uninstall on a system that has been updated, then don't do the normal process
+ * of uninstalling the update and rolling back to the older system version (which
+ * needs to happen for all users); instead, just mark the app as uninstalled for
+ * the current user.
+ *
+ * @hide
+ */
+ public static final int DELETE_SYSTEM_APP = 0x00000004;
+
+ /**
* Return code for when package deletion succeeds. This is passed to the
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
* succeeded in deleting the package.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e1887bc..5eac903 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1941,6 +1941,28 @@
return false;
}
+ } else if (tagName.equals("library")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestLibrary);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestLibrary_name);
+
+ sa.recycle();
+
+ if (lname != null) {
+ if (owner.libraryNames == null) {
+ owner.libraryNames = new ArrayList<String>();
+ }
+ if (!owner.libraryNames.contains(lname)) {
+ owner.libraryNames.add(lname.intern());
+ }
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
} else if (tagName.equals("uses-library")) {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesLibrary);
@@ -3182,7 +3204,8 @@
public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>();
public ArrayList<String> protectedBroadcasts;
-
+
+ public ArrayList<String> libraryNames = null;
public ArrayList<String> usesLibraries = null;
public ArrayList<String> usesOptionalLibraries = null;
public String[] usesLibraryFiles = null;
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 41384d2..af4c074 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -114,11 +114,90 @@
/** A constant describing an ambient temperature sensor type */
public static final int TYPE_AMBIENT_TEMPERATURE = 13;
- /**
+ /**
+ * A constant describing a magnetic field uncalibrated sensor type. See
+ * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
+ * <p>
+ * No periodic calibration is performed (ie: there are no discontinuities
+ * in the data stream while using this sensor). Assumptions that the
+ * magnetic field is due to the Earth's poles is avoided. Factory calibration
+ * and temperature compensation is still performed.
+ * </p>
+ */
+ public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14;
+
+ /**
+ * Identical to {@link #TYPE_ROTATION_VECTOR} except that it doesn't
+ * use the geomagnetic field. Therefore the Y axis doesn't
+ * point north, but instead to some other reference, that reference is
+ * allowed to drift by the same order of magnitude as the gyroscope
+ * drift around the Z axis.
+ * <p>
+ * In the ideal case, a phone rotated and returning to the same real-world
+ * orientation should report the same game rotation vector
+ * (without using the earth's geomagnetic field). However, the orientation
+ * may drift somewhat over time.
+ * </p>
+ */
+
+ public static final int TYPE_GAME_ROTATION_VECTOR = 15;
+
+ /**
+ * A constant describing a gyroscope uncalibrated sensor type. See
+ * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
+ * <p>
+ * No gyro-drift compensation is performed.
+ * Factory calibration and temperature compensation is still applied
+ * to the rate of rotation (angular speeds).
+ * </p>
+ */
+ public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16;
+
+ /**
+ * A constant describing the significant motion trigger sensor.
+ * See {@link android.hardware.SensorEvent#values} for more details.
+ * <p>
+ * It triggers when an event occurs and then automatically disables
+ * itself. The sensor continues to operate while the device is asleep
+ * and will automatically wake the device to notify when significant
+ * motion is detected. The application does not need to hold any wake
+ * locks for this sensor to trigger.
+ * </p>
+ */
+ public static final int TYPE_SIGNIFICANT_MOTION = 17;
+
+ /**
* A constant describing all sensor types.
*/
public static final int TYPE_ALL = -1;
+ /* Reporting mode constants for sensors. Each sensor will have exactly one
+ reporting mode associated with it. */
+ // Events are reported at a constant rate.
+ static int REPORTING_MODE_CONTINUOUS = 1;
+
+ // Events are reported only when the value changes.
+ static int REPORTING_MODE_ON_CHANGE = 2;
+
+ // Upon detection of an event, the sensor deactivates itself and then sends a single event.
+ static int REPORTING_MODE_ONE_SHOT = 3;
+
+ // Note: This needs to be updated, whenever a new sensor is added.
+ private static int[] sSensorReportingModes = {
+ REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS,
+ REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS,
+ REPORTING_MODE_ON_CHANGE, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS,
+ REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE,
+ REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS,
+ REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ONE_SHOT };
+
+ static int getReportingMode(Sensor sensor) {
+ // mType starts from offset 1.
+ return sSensorReportingModes[sensor.mType - 1];
+ }
+
/* Some of these fields are set only by the native bindings in
* SensorManager.
*/
@@ -132,7 +211,6 @@
private float mPower;
private int mMinDelay;
-
Sensor() {
}
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 51a17c1..84c9131 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -17,11 +17,9 @@
package android.hardware;
/**
- * <p>
* This class represents a {@link android.hardware.Sensor Sensor} event and
* holds informations such as the sensor's type, the time-stamp, accuracy and of
* course the sensor's {@link SensorEvent#values data}.
- * </p>
*
* <p>
* <u>Definition of the coordinate system used by the SensorEvent API.</u>
@@ -67,15 +65,9 @@
* Sensor.TYPE_ACCELEROMETER}:</h4> All values are in SI units (m/s^2)
*
* <ul>
- * <p>
- * values[0]: Acceleration minus Gx on the x-axis
- * </p>
- * <p>
- * values[1]: Acceleration minus Gy on the y-axis
- * </p>
- * <p>
- * values[2]: Acceleration minus Gz on the z-axis
- * </p>
+ * <li> values[0]: Acceleration minus Gx on the x-axis </li>
+ * <li> values[1]: Acceleration minus Gy on the y-axis </li>
+ * <li> values[2]: Acceleration minus Gz on the z-axis </li>
* </ul>
*
* <p>
@@ -165,15 +157,9 @@
* definition of positive rotation and does not agree with the definition of
* roll given earlier.
* <ul>
- * <p>
- * values[0]: Angular speed around the x-axis
- * </p>
- * <p>
- * values[1]: Angular speed around the y-axis
- * </p>
- * <p>
- * values[2]: Angular speed around the z-axis
- * </p>
+ * <li> values[0]: Angular speed around the x-axis </li>
+ * <li> values[1]: Angular speed around the y-axis </li>
+ * <li> values[2]: Angular speed around the z-axis </li>
* </ul>
* <p>
* Typically the output of the gyroscope is integrated over time to
@@ -233,22 +219,19 @@
* </p>
* <h4>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:</h4>
* <ul>
- * <p>
- * values[0]: Ambient light level in SI lux units
+ * <li>values[0]: Ambient light level in SI lux units </li>
* </ul>
*
* <h4>{@link android.hardware.Sensor#TYPE_PRESSURE Sensor.TYPE_PRESSURE}:</h4>
* <ul>
- * <p>
- * values[0]: Atmospheric pressure in hPa (millibar)
+ * <li>values[0]: Atmospheric pressure in hPa (millibar) </li>
* </ul>
*
* <h4>{@link android.hardware.Sensor#TYPE_PROXIMITY Sensor.TYPE_PROXIMITY}:
* </h4>
*
* <ul>
- * <p>
- * values[0]: Proximity sensor distance measured in centimeters
+ * <li>values[0]: Proximity sensor distance measured in centimeters </li>
* </ul>
*
* <p>
@@ -304,39 +287,23 @@
* </p>
*
* <ul>
- * <p>
- * values[0]: x*sin(θ/2)
- * </p>
- * <p>
- * values[1]: y*sin(θ/2)
- * </p>
- * <p>
- * values[2]: z*sin(θ/2)
- * </p>
- * <p>
- * values[3]: cos(θ/2) <i>(optional: only if value.length = 4)</i>
- * </p>
+ * <li> values[0]: x*sin(θ/2) </li>
+ * <li> values[1]: y*sin(θ/2) </li>
+ * <li> values[2]: z*sin(θ/2) </li>
+ * <li> values[3]: cos(θ/2) <i>(optional: only if value.length = 4)</i> </li>
* </ul>
*
* <h4>{@link android.hardware.Sensor#TYPE_ORIENTATION
* Sensor.TYPE_ORIENTATION}:</h4> All values are angles in degrees.
*
* <ul>
- * <p>
- * values[0]: Azimuth, angle between the magnetic north direction and the
+ * <li> values[0]: Azimuth, angle between the magnetic north direction and the
* y-axis, around the z-axis (0 to 359). 0=North, 90=East, 180=South,
- * 270=West
- * </p>
- *
- * <p>
- * values[1]: Pitch, rotation around x-axis (-180 to 180), with positive
- * values when the z-axis moves <b>toward</b> the y-axis.
- * </p>
- *
- * <p>
- * values[2]: Roll, rotation around y-axis (-90 to 90), with positive values
- * when the x-axis moves <b>toward</b> the z-axis.
- * </p>
+ * 270=West </li>
+ * <li> values[1]: Pitch, rotation around x-axis (-180 to 180), with positive
+ * values when the z-axis moves <b>toward</b> the y-axis. </li>
+ * <li> values[2]: Roll, rotation around y-axis (-90 to 90), with positive values
+ * when the x-axis moves <b>toward</b> the z-axis. </li>
* </ul>
*
* <p>
@@ -364,9 +331,7 @@
* <h4>{@link android.hardware.Sensor#TYPE_RELATIVE_HUMIDITY
* Sensor.TYPE_RELATIVE_HUMIDITY}:</h4>
* <ul>
- * <p>
- * values[0]: Relative ambient air humidity in percent
- * </p>
+ * <li> values[0]: Relative ambient air humidity in percent </li>
* </ul>
* <p>
* When relative ambient air humidity and ambient temperature are
@@ -423,21 +388,58 @@
* </h4>
*
* <ul>
- * <p>
- * values[0]: ambient (room) temperature in degree Celsius.
+ * <li> values[0]: ambient (room) temperature in degree Celsius.</li>
* </ul>
*
* @see SensorEvent
* @see GeomagneticField
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD_UNCALIBRATED} </h4>
+ * All values are in micro-Tesla (uT) and measure the ambient magnetic field
+ * in the X, Y and Z axis.
+ * <p>
+ * No periodic calibration is performed (ie: there are no discontinuities
+ * in the data stream while using this sensor). Assumptions that the the
+ * magnetic field is due to the Earth's poles is avoided. Factory calibration
+ * and temperature compensation is still performed.
+ * </p>
+ *
+ * <h4> {@link android.hardware.Sensor#TYPE_GYROSCOPE_UNCALIBRATED} </h4>
+ * All values are in radians/second and measure the rate of rotation
+ * around the X, Y and Z axis. An estimation of the drift on each axis is
+ * reported as well.
+ * <p>
+ * No gyro-drift compensation is performed. Factory calibration and temperature
+ * compensation is still applied to the rate of rotation (angular speeds).
+ * </p>
+ * <p>
+ * The coordinate system is the same as is used for the
+ * {@link android.hardware.Sensor#TYPE_ACCELEROMETER}
+ * Rotation is positive in the counter-clockwise direction (right-hand rule).
+ * That is, an observer looking from some positive location on the x, y or z axis
+ * at a device positioned on the origin would report positive rotation if the device
+ * appeared to be rotating counter clockwise.
+ * The range would at least be 17.45 rad/s (ie: ~1000 deg/s).
+ * <ul>
+ * <li> values[0] : angular speed (w/o drift compensation) around the X axis in rad/s </li>
+ * <li> values[1] : angular speed (w/o drift compensation) around the Y axis in rad/s </li>
+ * <li> values[2] : angular speed (w/o drift compensation) around the Z axis in rad/s </li>
+ * <li> values[3] : estimated drift around X axis in rad/s </li>
+ * <li> values[4] : estimated drift around Y axis in rad/s </li>
+ * <li> values[5] : estimated drift around Z axis in rad/s </li>
+ * </ul>
+ * </p>
+ * <h4></h4>
+ * <h4> Pro Tip: Always use the length of the values array while performing operations
+ * on it. In earlier versions, this used to be always 3 which has changed now. </h4>
*/
-
public final float[] values;
/**
* The sensor that generated this event. See
* {@link android.hardware.SensorManager SensorManager} for details.
*/
- public Sensor sensor;
+ public Sensor sensor;
/**
* The accuracy of this event. See {@link android.hardware.SensorManager
@@ -445,13 +447,11 @@
*/
public int accuracy;
-
/**
* The time in nanosecond at which the event happened
*/
public long timestamp;
-
SensorEvent(int size) {
values = new float[size];
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index c0d2fae..ce7bc7e 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -38,7 +38,11 @@
* hours. Note that the system will <i>not</i> disable sensors automatically when
* the screen turns off.
* </p>
- *
+ * <p class="note">
+ * Note: Don't use this mechanism with a Trigger Sensor, have a look
+ * at {@link TriggerEventListener}. {@link Sensor#TYPE_SIGNIFICANT_MOTION}
+ * is an example of a trigger sensor.
+ * </p>
* <pre class="prettyprint">
* public class SensorActivity extends Activity, implements SensorEventListener {
* private final SensorManager mSensorManager;
@@ -515,6 +519,12 @@
/**
* Unregisters a listener for the sensors with which it is registered.
*
+ * <p class="note"></p>
+ * Note: Don't use this method with a one shot trigger sensor such as
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
+ * Use {@link #cancelTriggerSensor(TriggerEventListener, Sensor)} instead.
+ * </p>
+ *
* @param listener
* a SensorEventListener object
*
@@ -524,6 +534,7 @@
* @see #unregisterListener(SensorEventListener)
* @see #registerListener(SensorEventListener, Sensor, int)
*
+ * @throws IllegalArgumentException when sensor is a trigger sensor.
*/
public void unregisterListener(SensorEventListener listener, Sensor sensor) {
if (listener == null || sensor == null) {
@@ -558,6 +569,12 @@
* Registers a {@link android.hardware.SensorEventListener
* SensorEventListener} for the given sensor.
*
+ * <p class="note"></p>
+ * Note: Don't use this method with a one shot trigger sensor such as
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
+ * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
+ * </p>
+ *
* @param listener
* A {@link android.hardware.SensorEventListener SensorEventListener}
* object.
@@ -581,6 +598,7 @@
* @see #unregisterListener(SensorEventListener)
* @see #unregisterListener(SensorEventListener, Sensor)
*
+ * @throws IllegalArgumentException when sensor is null or a trigger sensor
*/
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) {
return registerListener(listener, sensor, rate, null);
@@ -590,6 +608,12 @@
* Registers a {@link android.hardware.SensorEventListener
* SensorEventListener} for the given sensor.
*
+ * <p class="note"></p>
+ * Note: Don't use this method with a one shot trigger sensor such as
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
+ * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
+ * </p>
+ *
* @param listener
* A {@link android.hardware.SensorEventListener SensorEventListener}
* object.
@@ -617,6 +641,7 @@
* @see #unregisterListener(SensorEventListener)
* @see #unregisterListener(SensorEventListener, Sensor)
*
+ * @throws IllegalArgumentException when sensor is null or a trigger sensor
*/
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,
Handler handler) {
@@ -1304,6 +1329,68 @@
Q[3] = rv[2];
}
+ /**
+ * Requests receiving trigger events for a trigger sensor.
+ *
+ * <p>
+ * When the sensor detects a trigger event condition, such as significant motion in
+ * the case of the {@link Sensor#TYPE_SIGNIFICANT_MOTION}, the provided trigger listener
+ * will be invoked once and then its request to receive trigger events will be canceled.
+ * To continue receiving trigger events, the application must request to receive trigger
+ * events again.
+ * </p>
+ *
+ * @param listener The listener on which the
+ * {@link TriggerEventListener#onTrigger(TriggerEvent)} will be delivered.
+ * @param sensor The sensor to be enabled.
+ *
+ * @return true if the sensor was successfully enabled.
+ *
+ * @throws IllegalArgumentException when sensor is null or not a trigger sensor.
+ */
+ public boolean requestTriggerSensor(TriggerEventListener listener, Sensor sensor) {
+ return requestTriggerSensorImpl(listener, sensor);
+ }
+
+ /**
+ * @hide
+ */
+ protected abstract boolean requestTriggerSensorImpl(TriggerEventListener listener,
+ Sensor sensor);
+
+ /**
+ * Cancels receiving trigger events for a trigger sensor.
+ *
+ * <p>
+ * Note that a Trigger sensor will be auto disabled if
+ * {@link TriggerEventListener#onTrigger(TriggerEvent)} has triggered.
+ * This method is provided in case the user wants to explicitly cancel the request
+ * to receive trigger events.
+ * </p>
+ *
+ * @param listener The listener on which the
+ * {@link TriggerEventListener#onTrigger(TriggerEvent)}
+ * is delivered.It should be the same as the one used
+ * in {@link #requestTriggerSensor(TriggerEventListener, Sensor)}
+ * @param sensor The sensor for which the trigger request should be canceled.
+ * If null, it cancels receiving trigger for all sensors associated
+ * with the listener.
+ *
+ * @return true if successfully canceled.
+ *
+ * @throws IllegalArgumentException when sensor is a trigger sensor.
+ */
+ public boolean cancelTriggerSensor(TriggerEventListener listener, Sensor sensor) {
+ return cancelTriggerSensorImpl(listener, sensor);
+ }
+
+ /**
+ * @hide
+ */
+ protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,
+ Sensor sensor);
+
+
private LegacySensorManager getLegacySensorManager() {
synchronized (mSensorListByType) {
if (mLegacySensorManager == null) {
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 9591631..893d701 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,18 +16,19 @@
package android.hardware;
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-
-import dalvik.system.CloseGuard;
-
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
+import android.util.Log;
+import android.util.Pools;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import dalvik.system.CloseGuard;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
/**
* Sensor manager implementation that communicates with the built-in
@@ -45,22 +46,21 @@
private static final SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
// Listener list
- private final ArrayList<SensorEventListenerSensorPair> mListenerDelegates = new ArrayList<SensorEventListenerSensorPair>();
+ private final HashMap<SensorEventListener, SensorEventQueue> mSensorListeners =
+ new HashMap<SensorEventListener, SensorEventQueue>();
+ private final HashMap<TriggerEventListener, TriggerEventQueue> mTriggerListeners =
+ new HashMap<TriggerEventListener, TriggerEventQueue>();
- // Common pool of sensor events.
- private static SensorEventPool sPool;
+ private static final int MAX_EVENTS = 16;
+ private static Pools.SynchronizedPool<SensorEvent> sSensorEventPool;
+ private static Pools.SynchronizedPool<TriggerEvent> sTriggerEventPool;
// Looper associated with the context in which this instance was created.
private final Looper mMainLooper;
- // maps a SensorEventListener to a SensorEventQueue
- private final Hashtable<SensorEventListener, SensorEventQueue> mSensorEventQueueMap;
-
/** {@hide} */
public SystemSensorManager(Looper mainLooper) {
mMainLooper = mainLooper;
- mSensorEventQueueMap = new Hashtable<SensorEventListener, SensorEventQueue>();
-
synchronized(sSensorModuleLock) {
if (!sSensorModuleInitialized) {
sSensorModuleInitialized = true;
@@ -81,7 +81,10 @@
}
} while (i>0);
- sPool = new SensorEventPool( sFullSensorsList.size()*2 );
+ sSensorEventPool = new Pools.SynchronizedPool<SensorEvent>(
+ Math.max(sFullSensorsList.size()*2, 1));
+ sTriggerEventPool = new Pools.SynchronizedPool<TriggerEvent>(
+ Math.max(sFullSensorsList.size()*2, 1));
}
}
}
@@ -102,128 +105,133 @@
// Invariants to preserve:
// - one Looper per SensorEventListener
// - one Looper per SensorEventQueue
- // We map SensorEventListeners to a SensorEventQueue, which holds the looper
+ // We map SensorEventListener to a SensorEventQueue, which holds the looper
+ if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
- if (sensor == null) throw new NullPointerException("sensor cannot be null");
+ // Trigger Sensors should use the requestTriggerSensor call.
+ if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) return false;
- boolean result;
- synchronized (mSensorEventQueueMap) {
- // check if we already have this SensorEventListener, Sensor pair
- // registered -- if so, we ignore the register. This is not ideal
- // but this is what the implementation has always been doing.
- for (SensorEventListenerSensorPair l : mListenerDelegates) {
- if (l.isSameListenerSensorPair(listener, sensor)) {
- // already added, just return silently.
- return true;
- }
- }
-
- // now find the SensorEventQueue associated to this listener
- SensorEventQueue queue = mSensorEventQueueMap.get(listener);
- if (queue != null) {
- result = queue.addSensor(sensor, delay);
- if (result) {
- // create a new ListenerDelegate for this pair
- mListenerDelegates.add(new SensorEventListenerSensorPair(listener, sensor));
- }
- } else {
+ synchronized (mSensorListeners) {
+ SensorEventQueue queue = mSensorListeners.get(listener);
+ if (queue == null) {
Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
- queue = new SensorEventQueue(listener, looper.getQueue());
- result = queue.addSensor(sensor, delay);
- if (result) {
- // create a new ListenerDelegate for this pair
- mListenerDelegates.add(new SensorEventListenerSensorPair(listener, sensor));
- mSensorEventQueueMap.put(listener, queue);
- } else {
+ queue = new SensorEventQueue(listener, looper);
+ if (!queue.addSensor(sensor, delay)) {
queue.dispose();
+ return false;
}
+ mSensorListeners.put(listener, queue);
+ return true;
+ } else {
+ return queue.addSensor(sensor, delay);
}
}
- return result;
}
/** @hide */
@Override
protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
- synchronized (mSensorEventQueueMap) {
+ // Trigger Sensors should use the cancelTriggerSensor call.
+ if (sensor != null && Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) {
+ return;
+ }
- // remove this listener/sensor from our list
- final ArrayList<SensorEventListenerSensorPair> copy =
- new ArrayList<SensorEventListenerSensorPair>(mListenerDelegates);
- int lastIndex = copy.size()-1;
- for (int i=lastIndex ; i>= 0 ; i--) {
- if (copy.get(i).isSameListenerSensorPair(listener, sensor)) {
- mListenerDelegates.remove(i);
- }
- }
-
- // find the SensorEventQueue associated to this SensorEventListener
- SensorEventQueue queue = mSensorEventQueueMap.get(listener);
+ synchronized (mSensorListeners) {
+ SensorEventQueue queue = mSensorListeners.get(listener);
if (queue != null) {
- if (sensor != null) {
- queue.removeSensor(sensor);
+ boolean result;
+ if (sensor == null) {
+ result = queue.removeAllSensors();
} else {
- queue.removeAllSensors();
+ result = queue.removeSensor(sensor);
}
- if (!queue.hasSensors()) {
- mSensorEventQueueMap.remove(listener);
+ if (result && !queue.hasSensors()) {
+ mSensorListeners.remove(listener);
queue.dispose();
}
}
}
}
+ /** @hide */
+ @Override
+ protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+ if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
- /*
- * ListenerDelegate is essentially a SensorEventListener, Sensor pair
- * and is associated with a single SensorEventQueue.
- */
- private static final class SensorEventListenerSensorPair {
- private final SensorEventListener mSensorEventListener;
- private final Sensor mSensor;
- public SensorEventListenerSensorPair(SensorEventListener listener, Sensor sensor) {
- mSensorEventListener = listener;
- mSensor = sensor;
- }
- public boolean isSameListenerSensorPair(SensorEventListener listener, Sensor sensor) {
- // if sensor is null, we match only on the listener
- if (sensor != null) {
- return (listener == mSensorEventListener) &&
- (sensor.getHandle() == mSensor.getHandle());
+ if (Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) return false;
+
+ synchronized (mTriggerListeners) {
+ TriggerEventQueue queue = mTriggerListeners.get(listener);
+ if (queue == null) {
+ queue = new TriggerEventQueue(listener, mMainLooper, this);
+ if (!queue.addSensor(sensor, 0)) {
+ queue.dispose();
+ return false;
+ }
+ mTriggerListeners.put(listener, queue);
+ return true;
} else {
- return (listener == mSensorEventListener);
+ return queue.addSensor(sensor, 0);
}
}
}
+ /** @hide */
+ @Override
+ protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+ if (sensor != null && Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) {
+ return false;
+ }
+ synchronized (mTriggerListeners) {
+ TriggerEventQueue queue = mTriggerListeners.get(listener);
+ if (queue != null) {
+ boolean result;
+ if (sensor == null) {
+ result = queue.removeAllSensors();
+ } else {
+ result = queue.removeSensor(sensor);
+ }
+ if (result && !queue.hasSensors()) {
+ mTriggerListeners.remove(listener);
+ queue.dispose();
+ }
+ return result;
+ }
+ return false;
+ }
+ }
+
/*
- * SensorEventQueue is the communication channel with the sensor service,
- * there is a one-to-one mapping between SensorEventQueue and
- * SensorEventListener.
+ * BaseEventQueue is the communication channel with the sensor service,
+ * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
+ * the queues and the listeners.
*/
- private static final class SensorEventQueue {
- private static native int nativeInitSensorEventQueue(SensorEventQueue eventQ, MessageQueue msgQ, float[] scratch);
+ private static abstract class BaseEventQueue {
+ private native int nativeInitBaseEventQueue(BaseEventQueue eventQ, MessageQueue msgQ,
+ float[] scratch);
private static native int nativeEnableSensor(int eventQ, int handle, int us);
private static native int nativeDisableSensor(int eventQ, int handle);
private static native void nativeDestroySensorEventQueue(int eventQ);
private int nSensorEventQueue;
- private final SensorEventListener mListener;
private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
- private final SparseIntArray mSensorAccuracies = new SparseIntArray();
- private final SparseBooleanArray mFirstEvent = new SparseBooleanArray();
+ protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
+ protected final SparseBooleanArray mFirstEvent = new SparseBooleanArray();
private final CloseGuard mCloseGuard = CloseGuard.get();
private final float[] mScratch = new float[16];
- public SensorEventQueue(SensorEventListener listener, MessageQueue msgQ) {
- nSensorEventQueue = nativeInitSensorEventQueue(this, msgQ, mScratch);
- mListener = listener;
+ BaseEventQueue(Looper looper) {
+ nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch);
mCloseGuard.open("dispose");
}
+
public void dispose() {
dispose(false);
}
public boolean addSensor(Sensor sensor, int delay) {
+ // Check if already present.
+ if (mActiveSensors.get(sensor.getHandle())) return false;
+
if (enableSensor(sensor, delay) == 0) {
mActiveSensors.put(sensor.getHandle(), true);
return true;
@@ -231,7 +239,7 @@
return false;
}
- public void removeAllSensors() {
+ public boolean removeAllSensors() {
for (int i=0 ; i<mActiveSensors.size(); i++) {
if (mActiveSensors.valueAt(i) == true) {
int handle = mActiveSensors.keyAt(i);
@@ -244,21 +252,24 @@
}
}
}
+ return true;
}
- public void removeSensor(Sensor sensor) {
+ public boolean removeSensor(Sensor sensor) {
final int handle = sensor.getHandle();
if (mActiveSensors.get(handle)) {
disableSensor(sensor);
mActiveSensors.put(sensor.getHandle(), false);
+ return true;
}
+ return false;
}
public boolean hasSensors() {
// no more sensors are set
return mActiveSensors.indexOfValue(true) >= 0;
}
-
+
@Override
protected void finalize() throws Throwable {
try {
@@ -291,17 +302,30 @@
if (sensor == null) throw new NullPointerException();
return nativeDisableSensor(nSensorEventQueue, sensor.getHandle());
}
+ protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy,
+ long timestamp);
+ }
+
+ static final class SensorEventQueue extends BaseEventQueue {
+ private final SensorEventListener mListener;
+
+ public SensorEventQueue(SensorEventListener listener, Looper looper) {
+ super(looper);
+ mListener = listener;
+ }
// Called from native code.
@SuppressWarnings("unused")
- private void dispatchSensorEvent(int handle, float[] values, int inAccuracy, long timestamp) {
- // this is always called on the same thread.
- final SensorEvent t = sPool.getFromPool();
+ @Override
+ protected void dispatchSensorEvent(int handle, float[] values, int inAccuracy,
+ long timestamp) {
+ final Sensor sensor = sHandleToSensor.get(handle);
+ SensorEvent t = sSensorEventPool.acquire();
+ if (t == null) t = new SensorEvent(MAX_EVENTS);
try {
- final Sensor sensor = sHandleToSensor.get(handle);
- final SensorEventListener listener = mListener;
- // FIXME: handle more than 3 values
- System.arraycopy(values, 0, t.values, 0, 3);
+ // Copy the entire values array.
+ // Any changes in length will be handled at the native layer.
+ System.arraycopy(values, 0, t.values, 0, t.values.length);
t.timestamp = timestamp;
t.accuracy = inAccuracy;
t.sensor = sensor;
@@ -313,72 +337,57 @@
final int accuracy = mSensorAccuracies.get(handle);
if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
mSensorAccuracies.put(handle, t.accuracy);
- listener.onAccuracyChanged(t.sensor, t.accuracy);
+ mListener.onAccuracyChanged(t.sensor, t.accuracy);
}
break;
default:
// For other sensors, just report the accuracy once
if (mFirstEvent.get(handle) == false) {
mFirstEvent.put(handle, true);
- listener.onAccuracyChanged(
+ mListener.onAccuracyChanged(
t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
}
break;
}
- listener.onSensorChanged(t);
+ mListener.onSensorChanged(t);
} finally {
- sPool.returnToPool(t);
+ sSensorEventPool.release(t);
}
}
}
- /*
- * A dumb pool of SensorEvent
- */
- private static final class SensorEventPool {
- private final int mPoolSize;
- private final SensorEvent mPool[];
- private int mNumItemsInPool;
+ static final class TriggerEventQueue extends BaseEventQueue {
+ private final TriggerEventListener mListener;
+ private SensorManager mManager;
- private SensorEvent createSensorEvent() {
- // maximal size for all legacy events is 3
- return new SensorEvent(3);
+ public TriggerEventQueue(TriggerEventListener listener, Looper looper,
+ SensorManager manager) {
+ super(looper);
+ mListener = listener;
+ mManager = manager;
}
- SensorEventPool(int poolSize) {
- mPoolSize = poolSize;
- mNumItemsInPool = poolSize;
- mPool = new SensorEvent[poolSize];
- }
+ // Called from native code.
+ @SuppressWarnings("unused")
+ @Override
+ protected void dispatchSensorEvent(int handle, float[] values, int accuracy, long timestamp) {
+ final Sensor sensor = sHandleToSensor.get(handle);
+ TriggerEvent t = sTriggerEventPool.acquire();
+ if (t == null) t = new TriggerEvent(MAX_EVENTS);
- SensorEvent getFromPool() {
- SensorEvent t = null;
- synchronized (this) {
- if (mNumItemsInPool > 0) {
- // remove the "top" item from the pool
- final int index = mPoolSize - mNumItemsInPool;
- t = mPool[index];
- mPool[index] = null;
- mNumItemsInPool--;
- }
- }
- if (t == null) {
- // the pool was empty or this item was removed from the pool for
- // the first time. In any case, we need to create a new item.
- t = createSensorEvent();
- }
- return t;
- }
+ try {
+ // Copy the entire values array.
+ // Any changes in length will be handled at the native layer.
+ System.arraycopy(values, 0, t.values, 0, t.values.length);
+ t.timestamp = timestamp;
+ t.sensor = sensor;
- void returnToPool(SensorEvent t) {
- synchronized (this) {
- // is there space left in the pool?
- if (mNumItemsInPool < mPoolSize) {
- // if so, return the item to the pool
- mNumItemsInPool++;
- final int index = mPoolSize - mNumItemsInPool;
- mPool[index] = t;
- }
+ // A trigger sensor should be auto disabled.
+ mManager.cancelTriggerSensorImpl(mListener, sensor);
+
+ mListener.onTrigger(t);
+ } finally {
+ sTriggerEventPool.release(t);
}
}
}
diff --git a/core/java/android/hardware/TriggerEvent.java b/core/java/android/hardware/TriggerEvent.java
new file mode 100644
index 0000000..bdd39f3
--- /dev/null
+++ b/core/java/android/hardware/TriggerEvent.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 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.hardware;
+
+/**
+ * This class represents a Trigger Event - the event
+ * associated with a Trigger Sensor. When the sensor detects a trigger
+ * event condition, such as significant motion in the case of the
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}, the {@link TriggerEventListener}
+ * is called with the TriggerEvent. The sensor is automatically canceled
+ * after the trigger.
+ * <p>
+ * This class holds information such as the value of the sensor
+ * when the trigger happened, the timestamp along with detailed
+ * information regarding the Sensor itself.
+ * </p>
+ * @see android.hardware.SensorManager
+ * @see android.hardware.TriggerEvent
+ * @see android.hardware.Sensor
+ */
+public final class TriggerEvent {
+ /**
+ * <p>
+ * The length and contents of the {@link #values values} array depends on
+ * which {@link android.hardware.Sensor sensor} type is being monitored (see
+ * also {@link SensorEvent} for a definition of the coordinate system used).
+ * </p>
+ * <h4> {@link Sensor#TYPE_SIGNIFICANT_MOTION} </h4>
+ * The value field is of length 1. value[0] = 1.0 when the sensor triggers.
+ * 1.0 is the only allowed value.
+ */
+ public final float[] values;
+
+ /**
+ * The sensor that generated this event. See
+ * {@link android.hardware.SensorManager SensorManager} for details.
+ */
+ public Sensor sensor;
+
+ /**
+ * The time in nanosecond at which the event happened
+ */
+ public long timestamp;
+
+ TriggerEvent(int size) {
+ values = new float[size];
+ }
+}
diff --git a/core/java/android/hardware/TriggerEventListener.java b/core/java/android/hardware/TriggerEventListener.java
new file mode 100644
index 0000000..76b2796
--- /dev/null
+++ b/core/java/android/hardware/TriggerEventListener.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 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.hardware;
+
+/**
+ * This class is the listener used to handle Trigger Sensors.
+ * Trigger Sensors are sensors that trigger an event and are automatically
+ * disabled. {@link Sensor#TYPE_SIGNIFICANT_MOTION} is one such example.
+ * <p>
+ * SensorManager lets you access the device's {@link android.hardware.Sensor
+ * sensors}. Get an instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String)
+ * Context.getSystemService()} with the argument
+ * {@link android.content.Context#SENSOR_SERVICE}.
+ * Usage details are explained in the example below.
+ * </p>
+ *
+ * <pre class="prettyprint">
+ * class TriggerListener extends TriggerEventListener {
+ * public void onTrigger(TriggerEvent event) {
+ * // Do Work.
+ *
+ * // As it is a one shot sensor, it will be canceled automatically.
+ * // SensorManager.requestTriggerSensor(this, mSigMotion); needs to
+ * // be called again, if needed.
+ * }
+ * }
+ * public class SensorActivity extends Activity {
+ * private final SensorManager mSensorManager;
+ * private final Sensor mSigMotion;
+ * private final TriggerEventListener mListener = new TriggerEventListener();
+ *
+ * public SensorActivity() {
+ * mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
+ * mSigMotion = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+ * }
+ *
+ * protected void onResume() {
+ * super.onResume();
+ * mSensorManager.requestTriggerSensor(mListener, mSigMotion);
+ * }
+ *
+ * protected void onPause() {
+ * super.onPause();
+ * // Call disable to ensure that the trigger request has been canceled.
+ * mSensorManager.cancelTriggerSensor(mListener, mSigMotion);
+ * }
+ *
+ * }
+ * </pre>
+ *
+ * @see TriggerEvent
+ * @see Sensor
+ */
+public abstract class TriggerEventListener {
+ /**
+ * The method that will be called when the sensor
+ * is triggered. Override this method in your implementation
+ * of this class.
+ *
+ * @param event The details of the event.
+ */
+ public abstract void onTrigger(TriggerEvent event);
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 99624cc..288ceff 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -39,6 +39,7 @@
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.util.Slog;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -351,6 +352,7 @@
* Take care of attaching the given window token provided by the system.
*/
public void attachToken(IBinder token) {
+ Slog.i(TAG, "attachToken: Existing token=" + mToken + " new token=" + token);
if (mToken == null) {
mToken = token;
mWindow.setToken(token);
@@ -417,7 +419,7 @@
* Handle a request by the system to show the soft input area.
*/
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
- if (DEBUG) Log.v(TAG, "showSoftInput()");
+ if (true || DEBUG) Slog.v(TAG, "showSoftInput()");
boolean wasVis = isInputViewShown();
mShowInputFlags = 0;
if (onShowInputRequested(flags, false)) {
@@ -1388,7 +1390,7 @@
}
public void showWindow(boolean showInput) {
- if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
+ if (true || DEBUG) Slog.v(TAG, "Showing window: showInput=" + showInput
+ " mShowInputRequested=" + mShowInputRequested
+ " mWindowAdded=" + mWindowAdded
+ " mWindowCreated=" + mWindowCreated
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index ec8d77e..5d13a18 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -146,9 +146,9 @@
if (route != null) {
String routeIface = route.getInterface();
if (routeIface != null && !routeIface.equals(mIfaceName)) {
- throw new IllegalStateException(
+ throw new IllegalArgumentException(
"Route added with non-matching interface: " + routeIface +
- " vs. mIfaceName");
+ " vs. " + mIfaceName);
}
mRoutes.add(routeWithInterface(route));
}
@@ -370,7 +370,7 @@
public CompareResult<RouteInfo> compareRoutes(LinkProperties target) {
/*
* Duplicate the RouteInfos into removed, we will be removing
- * routes which are common between mDnses and target
+ * routes which are common between mRoutes and target
* leaving the routes that are different. And route address which
* are in target but not in mRoutes are placed in added.
*/
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index c0a894b..2a2f7cf 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -23,8 +23,8 @@
import java.net.Socket;
import java.net.SocketException;
import java.security.KeyManagementException;
+import java.security.PrivateKey;
import java.security.cert.X509Certificate;
-import java.security.interfaces.ECPrivateKey;
import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
@@ -89,7 +89,7 @@
private TrustManager[] mTrustManagers = null;
private KeyManager[] mKeyManagers = null;
private byte[] mNpnProtocols = null;
- private ECPrivateKey mChannelIdPrivateKey = null;
+ private PrivateKey mChannelIdPrivateKey = null;
private final int mHandshakeTimeoutMillis;
private final SSLClientSessionCache mSessionCache;
@@ -321,7 +321,7 @@
}
/**
- * Sets the {@link ECPrivateKey} to be used for TLS Channel ID by connections made by this
+ * Sets the private key to be used for TLS Channel ID by connections made by this
* factory.
*
* @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables
@@ -330,7 +330,7 @@
*
* @hide
*/
- public void setChannelIdPrivateKey(ECPrivateKey privateKey) {
+ public void setChannelIdPrivateKey(PrivateKey privateKey) {
mChannelIdPrivateKey = privateKey;
}
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 4379418..703dcff 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -810,9 +810,15 @@
return TextToSpeech.ERROR;
}
+ // In test env, ParcelFileDescriptor instance may be EXACTLY the same
+ // one that is used by client. And it will be closed by a client, thus
+ // preventing us from writing anything to it.
+ final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
+ fileDescriptor.detachFd());
+
SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
Binder.getCallingUid(), Binder.getCallingPid(), params, text,
- new ParcelFileDescriptor.AutoCloseOutputStream(fileDescriptor));
+ new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 67041ac..c7dacf3 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -5419,7 +5419,7 @@
ClipData clipData = cm.getPrimaryClip();
if (clipData != null) {
ClipData.Item clipItem = clipData.getItemAt(0);
- CharSequence pasteText = clipItem.getText();
+ CharSequence pasteText = clipItem.coerceToText(mContext);
if (mInputConnection != null) {
mInputConnection.replaceSelection(pasteText);
}
diff --git a/core/java/com/android/internal/view/IInputConnectionCallback.aidl b/core/java/com/android/internal/view/IInputConnectionCallback.aidl
deleted file mode 100644
index 5b5b3df..0000000
--- a/core/java/com/android/internal/view/IInputConnectionCallback.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.view;
-
-import android.graphics.Rect;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.inputmethod.TextBoxAttribute;
-import com.android.internal.view.IInputContext;
-import android.os.IBinder;
-
-/**
- * {@hide}
- */
-oneway interface IInputMethodCallback {
- void finishedEvent(int seq, boolean handled);
-}
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 3083cb1..6374494 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -31,7 +31,7 @@
static struct {
jclass clazz;
jmethodID dispatchSensorEvent;
-} gSensorEventQueueClassInfo;
+} gBaseEventQueueClassInfo;
namespace android {
@@ -145,7 +145,7 @@
env->SetFloatArrayRegion(mScratch, 0, 16, buffer[i].data);
env->CallVoidMethod(mReceiverObject,
- gSensorEventQueueClassInfo.dispatchSensorEvent,
+ gBaseEventQueueClassInfo.dispatchSensorEvent,
buffer[i].sensor,
mScratch,
buffer[i].vector.status,
@@ -209,9 +209,9 @@
(void*)nativeGetNextSensor },
};
-static JNINativeMethod gSensorEventQueueMethods[] = {
- {"nativeInitSensorEventQueue",
- "(Landroid/hardware/SystemSensorManager$SensorEventQueue;Landroid/os/MessageQueue;[F)I",
+static JNINativeMethod gBaseEventQueueMethods[] = {
+ {"nativeInitBaseEventQueue",
+ "(Landroid/hardware/SystemSensorManager$BaseEventQueue;Landroid/os/MessageQueue;[F)I",
(void*)nativeInitSensorEventQueue },
{"nativeEnableSensor",
@@ -245,13 +245,13 @@
jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager",
gSystemSensorManagerMethods, NELEM(gSystemSensorManagerMethods));
- jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager$SensorEventQueue",
- gSensorEventQueueMethods, NELEM(gSensorEventQueueMethods));
+ jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager$BaseEventQueue",
+ gBaseEventQueueMethods, NELEM(gBaseEventQueueMethods));
- FIND_CLASS(gSensorEventQueueClassInfo.clazz, "android/hardware/SystemSensorManager$SensorEventQueue");
+ FIND_CLASS(gBaseEventQueueClassInfo.clazz, "android/hardware/SystemSensorManager$BaseEventQueue");
- GET_METHOD_ID(gSensorEventQueueClassInfo.dispatchSensorEvent,
- gSensorEventQueueClassInfo.clazz,
+ GET_METHOD_ID(gBaseEventQueueClassInfo.dispatchSensorEvent,
+ gBaseEventQueueClassInfo.clazz,
"dispatchSensorEvent", "(I[FIJ)V");
return 0;
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 95e0b5b..2fc7f4a 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -329,7 +329,7 @@
<string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Ermöglicht der App, die Benutzeroberfläche zur Bestätigung der vollständigen Sicherung zu starten. Kann nicht von jeder App verwendet werden."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"Nicht autorisierte Fenster anzeigen"</string>
<string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Ermöglicht der App die Erstellung von Fenstern, die von der Benutzeroberfläche des internen Systems verwendet werden. Nicht für normale Apps vorgesehen."</string>
- <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Über andere Apps zeichnen"</string>
+ <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Über anderen Apps einblenden"</string>
<string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Ermöglicht der App, über andere Apps oder Teile der Benutzeroberfläche zu zeichnen. Dies kann sich auf die Oberfläche in jeder App auswirken oder die erwartete Darstellung in anderen Apps verändern."</string>
<string name="permlab_setAnimationScale" msgid="2805103241153907174">"Allgemeine Animationsgeschwindigkeit einstellen"</string>
<string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Ermöglicht der App, die allgemeine Animationsgeschwindigkeit (langsamere oder schnellere Animationen) jederzeit anzupassen."</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 985b088..1fc59b6 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -182,7 +182,7 @@
<string name="permgroupdesc_bluetoothNetwork" msgid="5625288577164282391">"Acceder a dispositivos y redes a través de Bluetooth"</string>
<string name="permgrouplab_audioSettings" msgid="8329261670151871235">"Configuración de audio"</string>
<string name="permgroupdesc_audioSettings" msgid="2641515403347568130">"Cambiar la configuración de audio"</string>
- <string name="permgrouplab_affectsBattery" msgid="6209246653424798033">"Afecta la batería."</string>
+ <string name="permgrouplab_affectsBattery" msgid="6209246653424798033">"Afecta la batería"</string>
<string name="permgroupdesc_affectsBattery" msgid="6441275320638916947">"Uso de las características que se pueden agotar rápidamente la batería"</string>
<string name="permgrouplab_calendar" msgid="5863508437783683902">"Calendario"</string>
<string name="permgroupdesc_calendar" msgid="5777534316982184416">"Acceso directo a calendario y eventos"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 5a701a0..86ead94 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -329,7 +329,7 @@
<string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Permite que a aplicação inicie a IU de confirmação de cópia de segurança completa. Não deve ser utilizado por qualquer aplicação."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"apresentar janelas não autorizadas"</string>
<string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Permite que a aplicação crie janelas que se destinam a ser utilizadas pela interface de utilizador do sistema interno. Nunca é necessário para aplicações normais."</string>
- <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"desenhar sobre outras aplicações"</string>
+ <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"mostrar sobre outras aplicações"</string>
<string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Permite que a aplicação se sobreponha a outras aplicações ou partes da interface de utilizador. Poderá interferir na utilização da interface de qualquer aplicação ou alterar o que pensa estar a ver noutras aplicações."</string>
<string name="permlab_setAnimationScale" msgid="2805103241153907174">"modificar velocidade global da animação"</string>
<string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Permite que a aplicação altere a velocidade global da animação (animações mais rápidas ou mais lentas) em qualquer altura."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 9eb11b2..4650d55 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -329,7 +329,7 @@
<string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Permite que o aplicativo lance a interface de usuário de confirmação de backup completo. Não deve ser usado por qualquer aplicativo."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"exibir janelas não autorizadas"</string>
<string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Permite que o aplicativo crie janelas destinadas ao uso pela interface interna do sistema. Não deve ser usado em aplicativos normais."</string>
- <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"induzir outros aplicativos"</string>
+ <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"sobrepor outros aplicativos"</string>
<string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Permite que o aplicativo se sobreponha visualmente a outros aplicativos ou a partes da interface do usuário. Podem interferir com o uso da interface de qualquer aplicativo ou alterar o que você acha que está vendo em outros aplicativos."</string>
<string name="permlab_setAnimationScale" msgid="2805103241153907174">"modificar velocidade de animação global"</string>
<string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Permite que o aplicativo altere a velocidade de animação global (animação mais rápida ou mais lenta) a qualquer momento."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index eb7185f..88c56bc 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1200,8 +1200,8 @@
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formataţi"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Depanarea USB este conectată"</string>
<string name="adb_active_notification_message" msgid="1016654627626476142">"Atingeţi pentru a dezactiva depanarea USB."</string>
- <string name="select_input_method" msgid="4653387336791222978">"Alegeţi metoda de introducere"</string>
- <string name="configure_input_methods" msgid="9091652157722495116">"Configurare metode introducere"</string>
+ <string name="select_input_method" msgid="4653387336791222978">"Alegeți metoda de introducere de text"</string>
+ <string name="configure_input_methods" msgid="9091652157722495116">"Configurați metode introducere"</string>
<string name="use_physical_keyboard" msgid="6203112478095117625">"Tastatură fizică"</string>
<string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="select_keyboard_layout_notification_title" msgid="1407367017263030773">"Selectaţi aspectul tastaturii"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index b8b1c29..9475472 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -329,7 +329,7 @@
<string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Приложение сможет отображать окно подтверждения полного резервного копирования. Это разрешение не предназначено для всех приложений."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"показывать неавторизованные окна"</string>
<string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Приложение сможет создавать окна для интерфейса внутренней системы. Это разрешение не используется обычными приложениями."</string>
- <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Показ сообщений поверх других окон"</string>
+ <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Показ элементов интерфейса поверх других окон"</string>
<string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Разрешает приложению отображать элементы своего интерфейса поверх окон других программ. Это может мешать вашему взаимодействию с другими приложениями и вести к недоразумениям."</string>
<string name="permlab_setAnimationScale" msgid="2805103241153907174">"изменять глобальную скорость анимации"</string>
<string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Приложение сможет в любой момент изменить общую скорость анимации."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index adfc22a..370c7d6 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -751,7 +751,7 @@
<string name="relationTypeSister" msgid="1735983554479076481">"Syster"</string>
<string name="relationTypeSpouse" msgid="394136939428698117">"Make/maka"</string>
<string name="sipAddressTypeCustom" msgid="2473580593111590945">"Anpassad"</string>
- <string name="sipAddressTypeHome" msgid="6093598181069359295">"Startsida"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Hem"</string>
<string name="sipAddressTypeWork" msgid="6920725730797099047">"Arbete"</string>
<string name="sipAddressTypeOther" msgid="4408436162950119849">"Övrigt"</string>
<string name="keyguard_password_enter_pin_code" msgid="3037685796058495017">"Ange PIN-kod"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 554c1aa..b952bd4 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1347,8 +1347,8 @@
<string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
<string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Enter"</string>
<string name="activitychooserview_choose_application" msgid="2125168057199941199">"选择应用"</string>
- <string name="shareactionprovider_share_with" msgid="806688056141131819">"共享对象"</string>
- <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"与“<xliff:g id="APPLICATION_NAME">%s</xliff:g>”共享"</string>
+ <string name="shareactionprovider_share_with" msgid="806688056141131819">"分享方式"</string>
+ <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"使用<xliff:g id="APPLICATION_NAME">%s</xliff:g>分享"</string>
<string name="content_description_sliding_handle" msgid="415975056159262248">"滑动手柄。触摸并按住。"</string>
<string name="description_direction_up" msgid="7169032478259485180">"向上滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
<string name="description_direction_down" msgid="5087739728639014595">"向下滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
@@ -1399,7 +1399,7 @@
<string name="sha1_fingerprint" msgid="7930330235269404581">"SHA-1 指纹:"</string>
<string name="activity_chooser_view_see_all" msgid="4292569383976636200">"查看全部"</string>
<string name="activity_chooser_view_dialog_title_default" msgid="4710013864974040615">"选择活动"</string>
- <string name="share_action_provider_share_with" msgid="5247684435979149216">"分享对象"</string>
+ <string name="share_action_provider_share_with" msgid="5247684435979149216">"分享方式"</string>
<string name="status_bar_device_locked" msgid="3092703448690669768">"设备已锁定。"</string>
<string name="list_delimeter" msgid="3975117572185494152">"、 "</string>
<string name="sending" msgid="3245653681008218030">"正在发送..."</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d899e9d..f1d8c03 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1065,6 +1065,21 @@
<attr name="maxSdkVersion" format="integer" />
</declare-styleable>
+ <!-- The <code>library</code> tag declares that this apk is providing itself
+ as a shared library for other applications to use. It can only be used
+ with apks that are built in to the system image. Other apks can link to
+ it with the {@link #AndroidManifestUsesLibrary uses-library} tag.
+
+ <p>This appears as a child tag of the
+ {@link #AndroidManifestApplication application} tag. -->
+ <declare-styleable name="AndroidManifestLibrary" parent="AndroidManifest">
+ <!-- Required public name of the library, which other components and
+ packages will use when referring to this library. This is a string using
+ Java-style scoping to ensure it is unique. The name should typically
+ be the same as the apk's package name. -->
+ <attr name="name" />
+ </declare-styleable>
+
<!-- The <code>uses-libraries</code> specifies a shared library that this
package requires to be linked against. Specifying this flag tells the
system to include this library's code in your class loader.
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index e3b6b5f..fffaa00 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -197,4 +197,63 @@
}
}
+ private void assertAllRoutesHaveInterface(String iface, LinkProperties lp) {
+ for (RouteInfo r : lp.getRoutes()) {
+ assertEquals(iface, r.getInterface());
+ }
+ }
+
+ @SmallTest
+ public void testRouteInterfaces() {
+ LinkAddress prefix = new LinkAddress(
+ NetworkUtils.numericToInetAddress("2001:db8::"), 32);
+ InetAddress address = NetworkUtils.numericToInetAddress(ADDRV6);
+
+ // Add a route with no interface to a LinkProperties with no interface. No errors.
+ LinkProperties lp = new LinkProperties();
+ RouteInfo r = new RouteInfo(prefix, address, null);
+ lp.addRoute(r);
+ assertEquals(1, lp.getRoutes().size());
+ assertAllRoutesHaveInterface(null, lp);
+
+ // Add a route with an interface. Except an exception.
+ r = new RouteInfo(prefix, address, "wlan0");
+ try {
+ lp.addRoute(r);
+ fail("Adding wlan0 route to LP with no interface, expect exception");
+ } catch (IllegalArgumentException expected) {}
+
+ // Change the interface name. All the routes should change their interface name too.
+ lp.setInterfaceName("rmnet0");
+ assertAllRoutesHaveInterface("rmnet0", lp);
+
+ // Now add a route with the wrong interface. This causes an exception too.
+ try {
+ lp.addRoute(r);
+ fail("Adding wlan0 route to rmnet0 LP, expect exception");
+ } catch (IllegalArgumentException expected) {}
+
+ // If the interface name matches, the route is added.
+ lp.setInterfaceName("wlan0");
+ lp.addRoute(r);
+ assertEquals(2, lp.getRoutes().size());
+ assertAllRoutesHaveInterface("wlan0", lp);
+
+ // Routes with null interfaces are converted to wlan0.
+ r = RouteInfo.makeHostRoute(NetworkUtils.numericToInetAddress(ADDRV6), null);
+ lp.addRoute(r);
+ assertEquals(3, lp.getRoutes().size());
+ assertAllRoutesHaveInterface("wlan0", lp);
+
+ // Check comparisons work.
+ LinkProperties lp2 = new LinkProperties(lp);
+ assertAllRoutesHaveInterface("wlan0", lp);
+ assertEquals(0, lp.compareRoutes(lp2).added.size());
+ assertEquals(0, lp.compareRoutes(lp2).removed.size());
+
+ lp2.setInterfaceName("p2p0");
+ assertAllRoutesHaveInterface("p2p0", lp2);
+ assertEquals(3, lp.compareRoutes(lp2).added.size());
+ assertEquals(3, lp.compareRoutes(lp2).removed.size());
+ }
}
diff --git a/docs/downloads/training/InteractiveChart.zip b/docs/downloads/training/InteractiveChart.zip
new file mode 100644
index 0000000..95248ad
--- /dev/null
+++ b/docs/downloads/training/InteractiveChart.zip
Binary files differ
diff --git a/docs/html/guide/topics/manifest/intent-filter-element.jd b/docs/html/guide/topics/manifest/intent-filter-element.jd
index f90541c..68da981 100644
--- a/docs/html/guide/topics/manifest/intent-filter-element.jd
+++ b/docs/html/guide/topics/manifest/intent-filter-element.jd
@@ -119,7 +119,11 @@
<p>
The value must be an integer, such as "{@code 100}". Higher numbers have a
-higher priority.
+higher priority. The default value is 0.
+The value must be greater than -1000 and less than 1000.</p>
+
+<p>Also see {@link android.content.IntentFilter#setPriority
+setPriority()}.
</p></dd>
</dl></dd>
diff --git a/docs/html/guide/topics/resources/animation-resource.jd b/docs/html/guide/topics/resources/animation-resource.jd
index 3af52aa..ef64f07 100644
--- a/docs/html/guide/topics/resources/animation-resource.jd
+++ b/docs/html/guide/topics/resources/animation-resource.jd
@@ -217,7 +217,7 @@
</dd>
<dt id="val-animator-element"><code><animator></code></dt>
- <dd>Animates a over a specified amount of time.
+ <dd>Performs an animation over a specified amount of time.
Represents a {@link android.animation.ValueAnimator}.
<p class="caps">attributes:</p>
diff --git a/docs/html/training/gestures/detector.jd b/docs/html/training/gestures/detector.jd
index 06d0e98..65ddb1b 100644
--- a/docs/html/training/gestures/detector.jd
+++ b/docs/html/training/gestures/detector.jd
@@ -25,12 +25,18 @@
<li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
</li>
<li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
- <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
<li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
<li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
<li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
</ul>
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
</div>
</div>
diff --git a/docs/html/training/gestures/index.jd b/docs/html/training/gestures/index.jd
index 0191450..16ca7b0 100644
--- a/docs/html/training/gestures/index.jd
+++ b/docs/html/training/gestures/index.jd
@@ -20,12 +20,18 @@
<li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
</li>
<li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
- <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
<li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
<li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
<li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
</ul>
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
</div>
</div>
diff --git a/docs/html/training/gestures/movement.jd b/docs/html/training/gestures/movement.jd
index f2c49d7..fdc1ea4 100644
--- a/docs/html/training/gestures/movement.jd
+++ b/docs/html/training/gestures/movement.jd
@@ -24,12 +24,18 @@
<li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
</li>
<li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
- <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
<li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
<li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
<li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
</ul>
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
</div>
</div>
diff --git a/docs/html/training/gestures/multi.jd b/docs/html/training/gestures/multi.jd
index d4c5b1d..6a0df11 100644
--- a/docs/html/training/gestures/multi.jd
+++ b/docs/html/training/gestures/multi.jd
@@ -25,12 +25,18 @@
<li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
</li>
<li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
- <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
<li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
<li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
<li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
</ul>
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
</div>
</div>
diff --git a/docs/html/training/gestures/scale.jd b/docs/html/training/gestures/scale.jd
index 17e4085..f2e4eb8 100644
--- a/docs/html/training/gestures/scale.jd
+++ b/docs/html/training/gestures/scale.jd
@@ -15,6 +15,7 @@
<h2>This lesson teaches you to</h2>
<ol>
<li><a href="#drag">Drag an Object</a></li>
+ <li><a href="#pan">Drag to Pan</a></li>
<li><a href="#scale">Use Touch to Perform Scaling</a></li>
</ol>
@@ -25,20 +26,25 @@
<li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
</li>
<li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
- <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
<li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
<li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
<li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
</ul>
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
</div>
</div>
+
<p>This lesson describes how to use touch gestures to drag and scale on-screen
objects, using {@link android.view.View#onTouchEvent onTouchEvent()} to intercept
-touch events. Here is the original <a
-href="http://code.google.com/p/android-touchexample/">source code</a>
-for the examples used in this lesson.
+touch events.
</p>
<h2 id="drag">Drag an Object</h2>
@@ -128,17 +134,15 @@
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float y = MotionEventCompat.getY(ev, pointerIndex);
- // Only move if the ScaleGestureDetector isn't processing a gesture.
- if (!mScaleDetector.isInProgress()) {
- // Calculate the distance moved
- final float dx = x - mLastTouchX;
- final float dy = y - mLastTouchY;
+ // Calculate the distance moved
+ final float dx = x - mLastTouchX;
+ final float dy = y - mLastTouchY;
- mPosX += dx;
- mPosY += dy;
+ mPosX += dx;
+ mPosY += dy;
- invalidate();
- }
+ invalidate();
+
// Remember this touch position for the next move event
mLastTouchX = x;
mLastTouchY = y;
@@ -175,6 +179,88 @@
return true;
}</pre>
+<h2 id="pan">Drag to Pan</h2>
+
+<p>The previous section showed an example of dragging an object around the screen. Another
+common scenario is <em>panning</em>, which is when a user's dragging motion causes scrolling
+in both the x and y axes. The above snippet directly intercepted the {@link android.view.MotionEvent}
+actions to implement dragging. The snippet in this section takes advantage of the platform's
+built-in support for common gestures. It overrides
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in
+{@link android.view.GestureDetector.SimpleOnGestureListener}.</p>
+
+<p>To provide a little more context, {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}
+is called when a user is dragging his finger to pan the content.
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} is only called when
+a finger is down; as soon as the finger is lifted from the screen, the gesture either ends,
+or a fling gesture is started (if the finger was moving with some speed just before it was lifted).
+For more discussion of scrolling vs. flinging, see <a href="scroll.html">Animating a Scroll Gesture</a>.</p>
+
+<p>Here is the snippet for {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}:
+
+
+<pre>// The current viewport. This rectangle represents the currently visible
+// chart domain and range.
+private RectF mCurrentViewport =
+ new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+
+// The current destination rectangle (in pixel coordinates) into which the
+// chart data should be drawn.
+private Rect mContentRect;
+
+private final GestureDetector.SimpleOnGestureListener mGestureListener
+ = new GestureDetector.SimpleOnGestureListener() {
+...
+
+@Override
+public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+ // Scrolling uses math based on the viewport (as opposed to math using pixels).
+
+ // Pixel offset is the offset in screen pixels, while viewport offset is the
+ // offset within the current viewport.
+ float viewportOffsetX = distanceX * mCurrentViewport.width()
+ / mContentRect.width();
+ float viewportOffsetY = -distanceY * mCurrentViewport.height()
+ / mContentRect.height();
+ ...
+ // Updates the viewport, refreshes the display.
+ setViewportBottomLeft(
+ mCurrentViewport.left + viewportOffsetX,
+ mCurrentViewport.bottom + viewportOffsetY);
+ ...
+ return true;
+}</pre>
+
+<p>The implementation of {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}
+scrolls the viewport in response to the touch gesture:</p>
+
+<pre>
+/**
+ * Sets the current viewport (defined by mCurrentViewport) to the given
+ * X and Y positions. Note that the Y value represents the topmost pixel position,
+ * and thus the bottom of the mCurrentViewport rectangle.
+ */
+private void setViewportBottomLeft(float x, float y) {
+ /*
+ * Constrains within the scroll range. The scroll range is simply the viewport
+ * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the
+ * extremes were 0 and 10, and the viewport size was 2, the scroll range would
+ * be 0 to 8.
+ */
+
+ float curWidth = mCurrentViewport.width();
+ float curHeight = mCurrentViewport.height();
+ x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth));
+ y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX));
+
+ mCurrentViewport.set(x, y - curHeight, x + curWidth, y);
+
+ // Invalidates the View to update the display.
+ ViewCompat.postInvalidateOnAnimation(this);
+}
+</pre>
+
<h2 id="scale">Use Touch to Perform Scaling</h2>
<p>As discussed in <a href="detector.html">Detecting Common Gestures</a>,
@@ -191,10 +277,10 @@
{@link android.view.ScaleGestureDetector.SimpleOnScaleGestureListener}
as a helper class that you can extend if you don’t care about all of the reported events.</p>
-<p>Here is a snippet that gives you the basic idea of how to perform scaling.
-Here is the original <a
-href="http://code.google.com/p/android-touchexample/">source code</a>
-for the examples.</p>
+
+<h3>Basic scaling example</h3>
+
+<p>Here is a snippet that illustrates the basic ingredients involved in scaling.</p>
<pre>private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
@@ -238,3 +324,88 @@
return true;
}
}</pre>
+
+
+
+
+<h3>More complex scaling example</h3>
+<p>Here is a more complex example from the {@code InteractiveChart} sample provided with this class.
+The {@code InteractiveChart} sample supports both scrolling (panning) and scaling with multiple fingers,
+using the {@link android.view.ScaleGestureDetector} "span"
+({@link android.view.ScaleGestureDetector#getCurrentSpanX getCurrentSpanX/Y}) and
+"focus" ({@link android.view.ScaleGestureDetector#getFocusX getFocusX/Y}) features:</p>
+
+<pre>@Override
+private RectF mCurrentViewport =
+ new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+private Rect mContentRect;
+private ScaleGestureDetector mScaleGestureDetector;
+...
+public boolean onTouchEvent(MotionEvent event) {
+ boolean retVal = mScaleGestureDetector.onTouchEvent(event);
+ retVal = mGestureDetector.onTouchEvent(event) || retVal;
+ return retVal || super.onTouchEvent(event);
+}
+
+/**
+ * The scale listener, used for handling multi-finger scale gestures.
+ */
+private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
+ = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ /**
+ * This is the active focal point in terms of the viewport. Could be a local
+ * variable but kept here to minimize per-frame allocations.
+ */
+ private PointF viewportFocus = new PointF();
+ private float lastSpanX;
+ private float lastSpanY;
+
+ // Detects that new pointers are going down.
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
+ lastSpanX = ScaleGestureDetectorCompat.
+ getCurrentSpanX(scaleGestureDetector);
+ lastSpanY = ScaleGestureDetectorCompat.
+ getCurrentSpanY(scaleGestureDetector);
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
+
+ float spanX = ScaleGestureDetectorCompat.
+ getCurrentSpanX(scaleGestureDetector);
+ float spanY = ScaleGestureDetectorCompat.
+ getCurrentSpanY(scaleGestureDetector);
+
+ float newWidth = lastSpanX / spanX * mCurrentViewport.width();
+ float newHeight = lastSpanY / spanY * mCurrentViewport.height();
+
+ float focusX = scaleGestureDetector.getFocusX();
+ float focusY = scaleGestureDetector.getFocusY();
+ // Makes sure that the chart point is within the chart region.
+ // See the sample for the implementation of hitTest().
+ hitTest(scaleGestureDetector.getFocusX(),
+ scaleGestureDetector.getFocusY(),
+ viewportFocus);
+
+ mCurrentViewport.set(
+ viewportFocus.x
+ - newWidth * (focusX - mContentRect.left)
+ / mContentRect.width(),
+ viewportFocus.y
+ - newHeight * (mContentRect.bottom - focusY)
+ / mContentRect.height(),
+ 0,
+ 0);
+ mCurrentViewport.right = mCurrentViewport.left + newWidth;
+ mCurrentViewport.bottom = mCurrentViewport.top + newHeight;
+ ...
+ // Invalidates the View to update the display.
+ ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
+
+ lastSpanX = spanX;
+ lastSpanY = spanY;
+ return true;
+ }
+};</pre>
diff --git a/docs/html/training/gestures/scroll.jd b/docs/html/training/gestures/scroll.jd
index 8576948..bd1537a 100644
--- a/docs/html/training/gestures/scroll.jd
+++ b/docs/html/training/gestures/scroll.jd
@@ -14,6 +14,7 @@
<!-- table of contents -->
<h2>This lesson teaches you to</h2>
<ol>
+ <li><a href="#term">Understand Scrolling Terminology</a></li>
<li><a href="#scroll">Implement Touch-Based Scrolling</a></li>
</ol>
@@ -24,12 +25,18 @@
<li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
</li>
<li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
- <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
<li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
<li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
<li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
</ul>
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
</div>
</div>
@@ -45,7 +52,26 @@
<p>You can use scrollers ({@link android.widget.Scroller} or {@link
android.widget.OverScroller}) to collect the data you need to produce a
-scrolling animation in response to a touch event.</p>
+scrolling animation in response to a touch event. They are similar, but
+{@link android.widget.OverScroller}
+includes methods for indicating to users that they've reached the content edges
+after a pan or fling gesture. The {@code InteractiveChart} sample
+uses the the {@link android.widget.EdgeEffect} class
+(actually the {@link android.support.v4.widget.EdgeEffectCompat} class)
+to display a "glow" effect when users reach the content edges.</p>
+
+<p class="note"><strong>Note:</strong> We recommend that you
+use {@link android.widget.OverScroller} rather than {@link
+android.widget.Scroller} for scrolling animations.
+{@link android.widget.OverScroller} provides the best backward
+compatibility with older devices.
+<br />
+Also note that you generally only need to use scrollers
+when implementing scrolling yourself. {@link android.widget.ScrollView} and
+{@link android.widget.HorizontalScrollView} do all of this for you if you nest your
+layout within them.
+</p>
+
<p>A scroller is used to animate scrolling over time, using platform-standard
scrolling physics (friction, velocity, etc.). The scroller itself doesn't
@@ -54,101 +80,280 @@
responsibility to get and apply new coordinates at a rate that will make the
scrolling animation look smooth.</p>
-<p class="note"><strong>Note:</strong> You generally only need to use scrollers
-when implementing scrolling yourself. {@link android.widget.ScrollView} and
-{@link android.widget.HorizontalScrollView} do all this for you do all of this for you if you nest your layout within them.</p>
-
-<h2 id = "scroll">Implement Touch-Based Scrolling</h2>
-<p>This snippet illustrates the basics of using a scroller. It uses a
-{@link android.view.GestureDetector}, and overrides the
-{@link android.view.GestureDetector.SimpleOnGestureListener} methods
-{@link android.view.GestureDetector.OnGestureListener#onDown onDown()} and
-{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}. It also
-overrides {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}
-to return {@code false} since you don't need to animate a scroll.</p>
+<h2 id="term">Understand Scrolling Terminology</h2>
+<p>"Scrolling" is a word that can take on different meanings in Android, depending on the context.</p>
-<p>It's common to use scrollers in conjunction with a fling gesture, but they
+<p><strong>Scrolling</strong> is the general process of moving the viewport (that is, the 'window'
+of content you're looking at). When scrolling is in both the x and y axes, it's called
+<em>panning</em>. The sample application provided with this class, {@code InteractiveChart}, illustrates
+two different types of scrolling, dragging and flinging:</p>
+<ul>
+ <li><strong>Dragging</strong> is the type of scrolling that occurs when a user drags her
+finger across the touch screen. Simple dragging is often implemented by overriding
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in
+{@link android.view.GestureDetector.OnGestureListener}. For more discussion of dragging, see
+<a href="dragging.jd">Dragging and Scaling</a>.</li>
+
+ <li><strong>Flinging</strong> is the type of scrolling that occurs when a user
+drags and lifts her finger quickly. After the user lifts her finger, you generally
+want to keep scrolling (moving the viewport), but decelerate until the viewport stops moving.
+Flinging can be implemented by overriding
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}
+in {@link android.view.GestureDetector.OnGestureListener}, and by using
+a scroller object. This is the use
+case that is the topic of this lesson.</li>
+</ul>
+
+<p>It's common to use scroller objects
+in conjunction with a fling gesture, but they
can be used in pretty much any context where you want the UI to display
-scrolling in response to a touch event. For example, you could override {@link
-android.view.View#onTouchEvent onTouchEvent()} to process touch events directly,
-and produce a scrolling effect in response to those touch events.</p>
+scrolling in response to a touch event. For example, you could override
+{@link android.view.View#onTouchEvent onTouchEvent()} to process touch
+events directly, and produce a scrolling effect or a "snapping to page" animation
+in response to those touch events.</p>
-<pre>
-private OverScroller mScroller = new OverScroller(context);
-private GestureDetector.SimpleOnGestureListener mGestureListener
+<h2 id="#scroll">Implement Touch-Based Scrolling</h2>
+
+<p>This section describes how to use a scroller.
+The snippet shown below comes from the {@code InteractiveChart} sample
+provided with this class.
+It uses a
+{@link android.view.GestureDetector}, and overrides the
+{@link android.view.GestureDetector.SimpleOnGestureListener} method
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}.
+It uses {@link android.widget.OverScroller} to track the fling gesture.
+If the user reaches the content edges
+after the fling gesture, the app displays a "glow" effect.
+</p>
+
+<p class="note"><strong>Note:</strong> The {@code InteractiveChart} sample app displays a
+chart that you can zoom, pan, scroll, and so on. In the following snippet,
+{@code mContentRect} represents the rectangle coordinates within the view that the chart
+will be drawn into. At any given time, a subset of the total chart domain and range are drawn
+into this rectangular area.
+{@code mCurrentViewport} represents the portion of the chart that is currently
+visible in the screen. Because pixel offsets are generally treated as integers,
+{@code mContentRect} is of the type {@link android.graphics.Rect}. Because the
+graph domain and range are decimal/float values, {@code mCurrentViewport} is of
+the type {@link android.graphics.RectF}.</p>
+
+<p>The first part of the snippet shows the implementation of
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}:</p>
+
+<pre>// The current viewport. This rectangle represents the currently visible
+// chart domain and range. The viewport is the part of the app that the
+// user manipulates via touch gestures.
+private RectF mCurrentViewport =
+ new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+
+// The current destination rectangle (in pixel coordinates) into which the
+// chart data should be drawn.
+private Rect mContentRect;
+
+private OverScroller mScroller;
+private RectF mScrollerStartViewport;
+...
+private final GestureDetector.SimpleOnGestureListener mGestureListener
= new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
- // Abort any active scroll animations and invalidate.
+ // Initiates the decay phase of any active edge effects.
+ releaseEdgeEffects();
+ mScrollerStartViewport.set(mCurrentViewport);
+ // Aborts any active scroll animations and invalidates.
mScroller.forceFinished(true);
- // There is also a compatibility version:
- // ViewCompat.postInvalidateOnAnimation
- postInvalidateOnAnimation();
+ ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
return true;
}
-
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2,
- float distanceX, float distanceY) {
- // You don't use a scroller in onScroll because you don't need to animate
- // a scroll. The scroll occurs instantly in response to touch feedback.
- return false;
- }
-
+ ...
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
- // Before flinging, abort the current animation.
- mScroller.forceFinished(true);
- // Begin the scroll animation
- mScroller.fling(
- // Current scroll position
- startX,
- startY,
- // Velocities, negated for natural touch response
- (int) -velocityX,
- (int) -velocityY,
- // Minimum and maximum scroll positions. The minimum scroll
- // position is generally zero and the maximum scroll position
- // is generally the content size less the screen size. So if the
- // content width is 1000 pixels and the screen width is 200
- // pixels, the maximum scroll offset should be 800 pixels.
- minX, maxX,
- minY, maxY,
- // The maximum overscroll bounds. This is useful when using
- // the EdgeEffect class to draw overscroll "glow" overlays.
- mContentRect.width() / 2,
- mContentRect.height() / 2);
- // Invalidate to trigger computeScroll()
- postInvalidateOnAnimation();
+ fling((int) -velocityX, (int) -velocityY);
return true;
}
};
+private void fling(int velocityX, int velocityY) {
+ // Initiates the decay phase of any active edge effects.
+ releaseEdgeEffects();
+ // Flings use math in pixels (as opposed to math based on the viewport).
+ Point surfaceSize = computeScrollSurfaceSize();
+ mScrollerStartViewport.set(mCurrentViewport);
+ int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left -
+ AXIS_X_MIN) / (
+ AXIS_X_MAX - AXIS_X_MIN));
+ int startY = (int) (surfaceSize.y * (AXIS_Y_MAX -
+ mScrollerStartViewport.bottom) / (
+ AXIS_Y_MAX - AXIS_Y_MIN));
+ // Before flinging, aborts the current animation.
+ mScroller.forceFinished(true);
+ // Begins the animation
+ mScroller.fling(
+ // Current scroll position
+ startX,
+ startY,
+ velocityX,
+ velocityY,
+ /*
+ * Minimum and maximum scroll positions. The minimum scroll
+ * position is generally zero and the maximum scroll position
+ * is generally the content size less the screen size. So if the
+ * content width is 1000 pixels and the screen width is 200
+ * pixels, the maximum scroll offset should be 800 pixels.
+ */
+ 0, surfaceSize.x - mContentRect.width(),
+ 0, surfaceSize.y - mContentRect.height(),
+ // The edges of the content. This comes into play when using
+ // the EdgeEffect class to draw "glow" overlays.
+ mContentRect.width() / 2,
+ mContentRect.height() / 2);
+ // Invalidates to trigger computeScroll()
+ ViewCompat.postInvalidateOnAnimation(this);
+}</pre>
+
+<p>When {@link android.view.GestureDetector.OnGestureListener#onFling onFling()} calls
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()},
+it triggers
+{@link android.view.View#computeScroll computeScroll()} to update the values for x and y.
+This is typically be done when a view child is animating a scroll using a scroller object, as in this example. </p>
+
+<p>Most views pass the scroller object's x and y position directly to
+{@link android.view.View#scrollTo scrollTo()}.
+The following implementation of {@link android.view.View#computeScroll computeScroll()}
+takes a different approach—it calls
+{@link android.widget.OverScroller#computeScrollOffset computeScrollOffset()} to get the current
+location of x and y. When the criteria for displaying an overscroll "glow" edge effect are met
+(the display is zoomed in, x or y is out of bounds, and the app isn't already showing an overscroll),
+the code sets up the overscroll glow effect and calls
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()}
+to trigger an invalidate on the view:</p>
+
+<pre>// Edge effect / overscroll tracking objects.
+private EdgeEffectCompat mEdgeEffectTop;
+private EdgeEffectCompat mEdgeEffectBottom;
+private EdgeEffectCompat mEdgeEffectLeft;
+private EdgeEffectCompat mEdgeEffectRight;
+
+private boolean mEdgeEffectTopActive;
+private boolean mEdgeEffectBottomActive;
+private boolean mEdgeEffectLeftActive;
+private boolean mEdgeEffectRightActive;
+
@Override
public void computeScroll() {
super.computeScroll();
- // Compute the current scroll offsets. If this returns true, then the
- // scroll has not yet finished.
+ boolean needsInvalidate = false;
+
+ // The scroller isn't finished, meaning a fling or programmatic pan
+ // operation is currently active.
if (mScroller.computeScrollOffset()) {
+ Point surfaceSize = computeScrollSurfaceSize();
int currX = mScroller.getCurrX();
int currY = mScroller.getCurrY();
- // Actually render the scrolled viewport, or actually scroll the
- // view using View.scrollTo.
+ boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN
+ || mCurrentViewport.right < AXIS_X_MAX);
+ boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN
+ || mCurrentViewport.bottom < AXIS_Y_MAX);
- // If currX or currY are outside the bounds, render the overscroll
- // glow using EdgeEffect.
+ /*
+ * If you are zoomed in and currX or currY is
+ * outside of bounds and you're not already
+ * showing overscroll, then render the overscroll
+ * glow edge effect.
+ */
+ if (canScrollX
+ && currX < 0
+ && mEdgeEffectLeft.isFinished()
+ && !mEdgeEffectLeftActive) {
+ mEdgeEffectLeft.onAbsorb((int)
+ OverScrollerCompat.getCurrVelocity(mScroller));
+ mEdgeEffectLeftActive = true;
+ needsInvalidate = true;
+ } else if (canScrollX
+ && currX > (surfaceSize.x - mContentRect.width())
+ && mEdgeEffectRight.isFinished()
+ && !mEdgeEffectRightActive) {
+ mEdgeEffectRight.onAbsorb((int)
+ OverScrollerCompat.getCurrVelocity(mScroller));
+ mEdgeEffectRightActive = true;
+ needsInvalidate = true;
+ }
- } else {
- // The scroll has finished.
- }
+ if (canScrollY
+ && currY < 0
+ && mEdgeEffectTop.isFinished()
+ && !mEdgeEffectTopActive) {
+ mEdgeEffectTop.onAbsorb((int)
+ OverScrollerCompat.getCurrVelocity(mScroller));
+ mEdgeEffectTopActive = true;
+ needsInvalidate = true;
+ } else if (canScrollY
+ && currY > (surfaceSize.y - mContentRect.height())
+ && mEdgeEffectBottom.isFinished()
+ && !mEdgeEffectBottomActive) {
+ mEdgeEffectBottom.onAbsorb((int)
+ OverScrollerCompat.getCurrVelocity(mScroller));
+ mEdgeEffectBottomActive = true;
+ needsInvalidate = true;
+ }
+ ...
+ }</pre>
+
+<p>Here is the section of the code that performs the actual zoom:</p>
+
+<pre>// Custom object that is functionally similar to Scroller
+Zoomer mZoomer;
+private PointF mZoomFocalPoint = new PointF();
+...
+
+// If a zoom is in progress (either programmatically or via double
+// touch), performs the zoom.
+if (mZoomer.computeZoom()) {
+ float newWidth = (1f - mZoomer.getCurrZoom()) *
+ mScrollerStartViewport.width();
+ float newHeight = (1f - mZoomer.getCurrZoom()) *
+ mScrollerStartViewport.height();
+ float pointWithinViewportX = (mZoomFocalPoint.x -
+ mScrollerStartViewport.left)
+ / mScrollerStartViewport.width();
+ float pointWithinViewportY = (mZoomFocalPoint.y -
+ mScrollerStartViewport.top)
+ / mScrollerStartViewport.height();
+ mCurrentViewport.set(
+ mZoomFocalPoint.x - newWidth * pointWithinViewportX,
+ mZoomFocalPoint.y - newHeight * pointWithinViewportY,
+ mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),
+ mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));
+ constrainViewport();
+ needsInvalidate = true;
+}
+if (needsInvalidate) {
+ ViewCompat.postInvalidateOnAnimation(this);
+}
+</pre>
+
+<p>This is the {@code computeScrollSurfaceSize()} method that's called in the above snippet. It
+computes the current scrollable surface size, in pixels. For example, if the entire chart area is visible,
+this is simply the current size of {@code mContentRect}. If the chart is zoomed in 200% in both directions,
+the returned size will be twice as large horizontally and vertically.</p>
+
+<pre>private Point computeScrollSurfaceSize() {
+ return new Point(
+ (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN)
+ / mCurrentViewport.width()),
+ (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN)
+ / mCurrentViewport.height()));
}</pre>
-<p>For another example of scroller usage, see the <a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the
-{@link android.support.v4.view.ViewPager} class.</p>
+<p>For another example of scroller usage, see the
+<a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the
+{@link android.support.v4.view.ViewPager} class. It scrolls in response to flings,
+and uses scrolling to implement the "snapping to page" animation.</p>
+
diff --git a/docs/html/training/gestures/viewgroup.jd b/docs/html/training/gestures/viewgroup.jd
index 257a5d8..5b32300 100644
--- a/docs/html/training/gestures/viewgroup.jd
+++ b/docs/html/training/gestures/viewgroup.jd
@@ -26,12 +26,19 @@
<li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
</li>
<li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
- <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
<li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
<li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
<li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
</ul>
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
+
</div>
</div>
diff --git a/docs/html/training/id-auth/authenticate.jd b/docs/html/training/id-auth/authenticate.jd
index c316af7..3084bea 100644
--- a/docs/html/training/id-auth/authenticate.jd
+++ b/docs/html/training/id-auth/authenticate.jd
@@ -114,7 +114,7 @@
new Handler(new OnError())); // Callback called if an error occurs
</pre>
-<p>In this example, <code>OnTokenAcquired</code> is a class that extends
+<p>In this example, <code>OnTokenAcquired</code> is a class that implements
{@link android.accounts.AccountManagerCallback}. {@link android.accounts.AccountManager} calls
{@link android.accounts.AccountManagerCallback#run run()} on <code>OnTokenAcquired</code> with an
{@link android.accounts.AccountManagerFuture} that contains a {@link android.os.Bundle}. If
diff --git a/docs/html/training/implementing-navigation/lateral.jd b/docs/html/training/implementing-navigation/lateral.jd
index b59bab2..9a31d7a 100644
--- a/docs/html/training/implementing-navigation/lateral.jd
+++ b/docs/html/training/implementing-navigation/lateral.jd
@@ -131,6 +131,9 @@
ViewPager mViewPager;
public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_collection_demo);
+
// ViewPager and its adapters use support library
// fragments, so use getSupportFragmentManager.
mDemoCollectionPagerAdapter =
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 85b2052..1618110 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -7,6 +7,7 @@
LOCAL_SRC_FILES:= \
utils/Blur.cpp \
utils/SortedListImpl.cpp \
+ thread/TaskManager.cpp \
font/CacheTexture.cpp \
font/Font.cpp \
FontRenderer.cpp \
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index ca699d5..dc32a7e 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -25,6 +25,9 @@
#include <cutils/compiler.h>
+#include "thread/TaskProcessor.h"
+#include "thread/TaskManager.h"
+
#include "FontRenderer.h"
#include "GammaFontRenderer.h"
#include "TextureCache.h"
@@ -278,6 +281,8 @@
GammaFontRenderer* fontRenderer;
+ TaskManager tasks;
+
Dither dither;
Stencil stencil;
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 9e6ec84..fb687cd 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -31,69 +31,34 @@
// Path precaching
///////////////////////////////////////////////////////////////////////////////
-bool PathCache::PrecacheThread::threadLoop() {
- mSignal.wait();
- Vector<Task> tasks;
- {
- Mutex::Autolock l(mLock);
- tasks = mTasks;
- mTasks.clear();
- }
-
- Caches& caches = Caches::getInstance();
- uint32_t maxSize = caches.maxTextureSize;
-
- ATRACE_BEGIN("pathPrecache");
- for (size_t i = 0; i < tasks.size(); i++) {
- const Task& task = tasks.itemAt(i);
-
- float left, top, offset;
- uint32_t width, height;
- PathCache::computePathBounds(task.path, task.paint, left, top, offset, width, height);
-
- if (width <= maxSize && height <= maxSize) {
- SkBitmap* bitmap = new SkBitmap();
-
- PathTexture* texture = task.texture;
- texture->left = left;
- texture->top = top;
- texture->offset = offset;
- texture->width = width;
- texture->height = height;
-
- PathCache::drawPath(task.path, task.paint, *bitmap, left, top, offset, width, height);
-
- texture->future()->produce(bitmap);
- } else {
- task.texture->future()->produce(NULL);
- }
- }
- ATRACE_END();
- return true;
+PathCache::PathProcessor::PathProcessor(Caches& caches):
+ TaskProcessor<SkBitmap*>(&caches.tasks), mMaxTextureSize(caches.maxTextureSize) {
}
-void PathCache::PrecacheThread::addTask(PathTexture* texture, SkPath* path, SkPaint* paint) {
- if (!isRunning()) {
- run("libhwui:pathPrecache", PRIORITY_DEFAULT);
+void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
+ sp<PathTask> t = static_cast<PathTask* >(task.get());
+ ATRACE_NAME("pathPrecache");
+
+ float left, top, offset;
+ uint32_t width, height;
+ PathCache::computePathBounds(t->path, t->paint, left, top, offset, width, height);
+
+ PathTexture* texture = t->texture;
+ texture->left = left;
+ texture->top = top;
+ texture->offset = offset;
+ texture->width = width;
+ texture->height = height;
+
+ if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
+ SkBitmap* bitmap = new SkBitmap();
+ PathCache::drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height);
+ t->setResult(bitmap);
+ } else {
+ texture->width = 0;
+ texture->height = 0;
+ t->setResult(NULL);
}
-
- Task task;
- task.texture = texture;
- task.path = path;
- task.paint = paint;
-
- Mutex::Autolock l(mLock);
- mTasks.add(task);
- mSignal.signal();
-}
-
-void PathCache::PrecacheThread::exit() {
- {
- Mutex::Autolock l(mLock);
- mTasks.clear();
- }
- requestExit();
- mSignal.signal();
}
///////////////////////////////////////////////////////////////////////////////
@@ -101,11 +66,10 @@
///////////////////////////////////////////////////////////////////////////////
PathCache::PathCache(): ShapeCache<PathCacheEntry>("path",
- PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE), mThread(new PrecacheThread()) {
+ PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE) {
}
PathCache::~PathCache() {
- mThread->exit();
}
void PathCache::remove(SkPath* path) {
@@ -165,17 +129,17 @@
} else {
// A bitmap is attached to the texture, this means we need to
// upload it as a GL texture
- if (texture->future() != NULL) {
+ const sp<Task<SkBitmap*> >& task = texture->task();
+ if (task != NULL) {
// But we must first wait for the worker thread to be done
// producing the bitmap, so let's wait
- SkBitmap* bitmap = texture->future()->get();
+ SkBitmap* bitmap = task->getResult();
if (bitmap) {
addTexture(entry, bitmap, texture);
- texture->clearFuture();
+ texture->clearTask();
} else {
- ALOGW("Path too large to be rendered into a texture (%dx%d)",
- texture->width, texture->height);
- texture->clearFuture();
+ ALOGW("Path too large to be rendered into a texture");
+ texture->clearTask();
texture = NULL;
mCache.remove(entry);
}
@@ -189,6 +153,10 @@
}
void PathCache::precache(SkPath* path, SkPaint* paint) {
+ if (!Caches::getInstance().tasks.canRunTasks()) {
+ return;
+ }
+
path = getSourcePath(path);
PathCacheEntry entry(path, paint);
@@ -205,7 +173,9 @@
if (generate) {
// It is important to specify the generation ID so we do not
// attempt to precache the same path several times
- texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID(), true);
+ texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID());
+ sp<PathTask> task = new PathTask(path, paint, texture);
+ texture->setTask(task);
// During the precaching phase we insert path texture objects into
// the cache that do not point to any GL texture. They are instead
@@ -215,7 +185,11 @@
// asks for a path texture. This is also when the cache limit will
// be enforced.
mCache.put(entry, texture);
- mThread->addTask(texture, path, paint);
+
+ if (mProcessor == NULL) {
+ mProcessor = new PathProcessor(Caches::getInstance());
+ }
+ mProcessor->add(task);
}
}
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 1d28ecb..27031a5 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -23,6 +23,8 @@
#include "Debug.h"
#include "ShapeCache.h"
#include "thread/Signal.h"
+#include "thread/Task.h"
+#include "thread/TaskProcessor.h"
class SkPaint;
class SkPath;
@@ -100,32 +102,33 @@
void precache(SkPath* path, SkPaint* paint);
private:
- class PrecacheThread: public Thread {
+ class PathTask: public Task<SkBitmap*> {
public:
- PrecacheThread(): mSignal(Condition::WAKE_UP_ONE) { }
+ PathTask(SkPath* path, SkPaint* paint, PathTexture* texture):
+ path(path), paint(paint), texture(texture) {
+ }
- void addTask(PathTexture* texture, SkPath* path, SkPaint* paint);
- void exit();
+ ~PathTask() {
+ delete future()->get();
+ }
- private:
- struct Task {
- PathTexture* texture;
- SkPath* path;
- SkPaint* paint;
- };
-
- virtual bool threadLoop();
-
- // Lock for the list of tasks
- Mutex mLock;
- Vector<Task> mTasks;
-
- // Signal used to wake up the thread when a new
- // task is available in the list
- mutable Signal mSignal;
+ SkPath* path;
+ SkPaint* paint;
+ PathTexture* texture;
};
- sp<PrecacheThread> mThread;
+ class PathProcessor: public TaskProcessor<SkBitmap*> {
+ public:
+ PathProcessor(Caches& caches);
+ ~PathProcessor() { }
+
+ virtual void onProcess(const sp<Task<SkBitmap*> >& task);
+
+ private:
+ uint32_t mMaxTextureSize;
+ };
+
+ sp<PathProcessor> mProcessor;
Vector<SkPath*> mGarbage;
mutable Mutex mLock;
}; // class PathCache
diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h
index 67ae85b..92314b0 100644
--- a/libs/hwui/ShapeCache.h
+++ b/libs/hwui/ShapeCache.h
@@ -30,12 +30,11 @@
#include <utils/JenkinsHash.h>
#include <utils/LruCache.h>
#include <utils/Trace.h>
-#include <utils/CallStack.h>
#include "Debug.h"
#include "Properties.h"
#include "Texture.h"
-#include "thread/Future.h"
+#include "thread/Task.h"
namespace android {
namespace uirenderer {
@@ -62,14 +61,8 @@
PathTexture(): Texture() {
}
- PathTexture(bool hasFuture): Texture() {
- if (hasFuture) {
- mFuture = new Future<SkBitmap*>();
- }
- }
-
~PathTexture() {
- clearFuture();
+ clearTask();
}
/**
@@ -85,19 +78,22 @@
*/
float offset;
- sp<Future<SkBitmap*> > future() const {
- return mFuture;
+ sp<Task<SkBitmap*> > task() const {
+ return mTask;
}
- void clearFuture() {
- if (mFuture != NULL) {
- delete mFuture->get();
- mFuture.clear();
+ void setTask(const sp<Task<SkBitmap*> >& task) {
+ mTask = task;
+ }
+
+ void clearTask() {
+ if (mTask != NULL) {
+ mTask.clear();
}
}
private:
- sp<Future<SkBitmap*> > mFuture;
+ sp<Task<SkBitmap*> > mTask;
}; // struct PathTexture
/**
@@ -551,8 +547,8 @@
}
static PathTexture* createTexture(float left, float top, float offset,
- uint32_t width, uint32_t height, uint32_t id, bool hasFuture = false) {
- PathTexture* texture = new PathTexture(hasFuture);
+ uint32_t width, uint32_t height, uint32_t id) {
+ PathTexture* texture = new PathTexture();
texture->left = left;
texture->top = top;
texture->offset = offset;
@@ -721,7 +717,9 @@
ALOGD("Shape %s deleted, size = %d", mName, size);
}
- glDeleteTextures(1, &texture->id);
+ if (texture->id) {
+ glDeleteTextures(1, &texture->id);
+ }
delete texture;
}
}
diff --git a/libs/hwui/thread/Future.h b/libs/hwui/thread/Future.h
index 340fec7..a3ff3bc 100644
--- a/libs/hwui/thread/Future.h
+++ b/libs/hwui/thread/Future.h
@@ -24,7 +24,7 @@
namespace android {
namespace uirenderer {
-template<class T>
+template<typename T>
class Future: public LightRefBase<Future<T> > {
public:
Future(Condition::WakeUpType type = Condition::WAKE_UP_ONE): mBarrier(type), mResult() { }
diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h
new file mode 100644
index 0000000..9a211a2
--- /dev/null
+++ b/libs/hwui/thread/Task.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 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 ANDROID_HWUI_TASK_H
+#define ANDROID_HWUI_TASK_H
+
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include <utils/RefBase.h>
+#include <utils/Trace.h>
+
+#include "Future.h"
+
+namespace android {
+namespace uirenderer {
+
+class TaskBase: public RefBase {
+public:
+ TaskBase() { }
+ virtual ~TaskBase() { }
+};
+
+template<typename T>
+class Task: public TaskBase {
+public:
+ Task(): mFuture(new Future<T>()) { }
+ virtual ~Task() { }
+
+ T getResult() const {
+ ATRACE_NAME("waitForTask");
+ return mFuture->get();
+ }
+
+ void setResult(T result) {
+ mFuture->produce(result);
+ }
+
+protected:
+ const sp<Future<T> >& future() const {
+ return mFuture;
+ }
+
+private:
+ sp<Future<T> > mFuture;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_H
diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp
new file mode 100644
index 0000000..ce6c8c0
--- /dev/null
+++ b/libs/hwui/thread/TaskManager.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 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 <sys/sysinfo.h>
+
+#include "Task.h"
+#include "TaskProcessor.h"
+#include "TaskManager.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Manager
+///////////////////////////////////////////////////////////////////////////////
+
+TaskManager::TaskManager() {
+ // Get the number of available CPUs. This value does not change over time.
+ int cpuCount = sysconf(_SC_NPROCESSORS_ONLN);
+
+ for (int i = 0; i < cpuCount / 2; i++) {
+ String8 name;
+ name.appendFormat("hwuiTask%d", i + 1);
+ mThreads.add(new WorkerThread(name));
+ }
+}
+
+TaskManager::~TaskManager() {
+ for (size_t i = 0; i < mThreads.size(); i++) {
+ mThreads[i]->exit();
+ }
+}
+
+bool TaskManager::canRunTasks() const {
+ return mThreads.size() > 0;
+}
+
+bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor) {
+ if (mThreads.size() > 0) {
+ TaskWrapper wrapper(task, processor);
+
+ size_t minQueueSize = INT_MAX;
+ sp<WorkerThread> thread;
+
+ for (size_t i = 0; i < mThreads.size(); i++) {
+ if (mThreads[i]->getTaskCount() < minQueueSize) {
+ thread = mThreads[i];
+ minQueueSize = mThreads[i]->getTaskCount();
+ }
+ }
+
+ return thread->addTask(wrapper);
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Thread
+///////////////////////////////////////////////////////////////////////////////
+
+bool TaskManager::WorkerThread::threadLoop() {
+ mSignal.wait();
+ Vector<TaskWrapper> tasks;
+ {
+ Mutex::Autolock l(mLock);
+ tasks = mTasks;
+ mTasks.clear();
+ }
+
+ for (size_t i = 0; i < tasks.size(); i++) {
+ const TaskWrapper& task = tasks.itemAt(i);
+ task.mProcessor->process(task.mTask);
+ }
+
+ return true;
+}
+
+bool TaskManager::WorkerThread::addTask(TaskWrapper task) {
+ if (!isRunning()) {
+ run(mName.string(), PRIORITY_DEFAULT);
+ }
+
+ Mutex::Autolock l(mLock);
+ ssize_t index = mTasks.add(task);
+ mSignal.signal();
+
+ return index >= 0;
+}
+
+size_t TaskManager::WorkerThread::getTaskCount() const {
+ Mutex::Autolock l(mLock);
+ return mTasks.size();
+}
+
+void TaskManager::WorkerThread::exit() {
+ {
+ Mutex::Autolock l(mLock);
+ mTasks.clear();
+ }
+ requestExit();
+ mSignal.signal();
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
new file mode 100644
index 0000000..bc86062
--- /dev/null
+++ b/libs/hwui/thread/TaskManager.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 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 ANDROID_HWUI_TASK_MANAGER_H
+#define ANDROID_HWUI_TASK_MANAGER_H
+
+#include <utils/Mutex.h>
+#include <utils/String8.h>
+#include <utils/Thread.h>
+#include <utils/Vector.h>
+
+#include "Signal.h"
+
+namespace android {
+namespace uirenderer {
+
+template <typename T>
+class Task;
+class TaskBase;
+
+template <typename T>
+class TaskProcessor;
+class TaskProcessorBase;
+
+class TaskManager {
+public:
+ TaskManager();
+ ~TaskManager();
+
+ /**
+ * Returns true if this task manager can run tasks,
+ * false otherwise. This method will typically return
+ * true on a single CPU core device.
+ */
+ bool canRunTasks() const;
+
+private:
+ template <typename T>
+ friend class TaskProcessor;
+
+ template<typename T>
+ bool addTask(const sp<Task<T> >& task, const sp<TaskProcessor<T> >& processor) {
+ return addTaskBase(sp<TaskBase>(task), sp<TaskProcessorBase>(processor));
+ }
+
+ bool addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor);
+
+ struct TaskWrapper {
+ TaskWrapper(): mTask(), mProcessor() { }
+
+ TaskWrapper(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor):
+ mTask(task), mProcessor(processor) {
+ }
+
+ sp<TaskBase> mTask;
+ sp<TaskProcessorBase> mProcessor;
+ };
+
+ class WorkerThread: public Thread {
+ public:
+ WorkerThread(const String8 name): mSignal(Condition::WAKE_UP_ONE), mName(name) { }
+
+ bool addTask(TaskWrapper task);
+ size_t getTaskCount() const;
+ void exit();
+
+ private:
+ virtual bool threadLoop();
+
+ // Lock for the list of tasks
+ mutable Mutex mLock;
+ Vector<TaskWrapper> mTasks;
+
+ // Signal used to wake up the thread when a new
+ // task is available in the list
+ mutable Signal mSignal;
+
+ const String8 mName;
+ };
+
+ Vector<sp<WorkerThread> > mThreads;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_MANAGER_H
diff --git a/libs/hwui/thread/TaskProcessor.h b/libs/hwui/thread/TaskProcessor.h
new file mode 100644
index 0000000..d1269f0
--- /dev/null
+++ b/libs/hwui/thread/TaskProcessor.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013 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 ANDROID_HWUI_TASK_PROCESSOR_H
+#define ANDROID_HWUI_TASK_PROCESSOR_H
+
+#include <utils/RefBase.h>
+
+#include "Task.h"
+#include "TaskManager.h"
+
+namespace android {
+namespace uirenderer {
+
+class TaskProcessorBase: public RefBase {
+public:
+ TaskProcessorBase() { }
+ virtual ~TaskProcessorBase() { };
+
+private:
+ friend class TaskManager;
+
+ virtual void process(const sp<TaskBase>& task) = 0;
+};
+
+template<typename T>
+class TaskProcessor: public TaskProcessorBase {
+public:
+ TaskProcessor(TaskManager* manager): mManager(manager) { }
+ virtual ~TaskProcessor() { }
+
+ bool add(const sp<Task<T> >& task);
+
+ virtual void onProcess(const sp<Task<T> >& task) = 0;
+
+private:
+ virtual void process(const sp<TaskBase>& task) {
+ sp<Task<T> > realTask = static_cast<Task<T>* >(task.get());
+ // This is the right way to do it but sp<> doesn't play nice
+ // sp<Task<T> > realTask = static_cast<sp<Task<T> > >(task);
+ onProcess(realTask);
+ }
+
+ TaskManager* mManager;
+};
+
+template<typename T>
+bool TaskProcessor<T>::add(const sp<Task<T> >& task) {
+ if (mManager) {
+ sp<TaskProcessor<T> > self(this);
+ return mManager->addTask(task, self);
+ }
+ return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_PROCESSOR_H
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 86976b8..135d2c8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2171,15 +2171,36 @@
/**
* @hide
* Registers a remote control display that will be sent information by remote control clients.
- * @param rcd
+ * Use this method if your IRemoteControlDisplay is not going to display artwork, otherwise
+ * use {@link #registerRemoteControlDisplay(IRemoteControlDisplay, int, int)} to pass the
+ * artwork size directly, or
+ * {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork
+ * is not yet needed.
+ * @param rcd the IRemoteControlDisplay
*/
public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ // passing a negative value for art work width and height as they are unknown at this stage
+ registerRemoteControlDisplay(rcd, /*w*/-1, /*h*/ -1);
+ }
+
+ /**
+ * @hide
+ * Registers a remote control display that will be sent information by remote control clients.
+ * @param rcd
+ * @param w the maximum width of the expected bitmap. Negative values indicate it is
+ * useless to send artwork.
+ * @param h the maximum height of the expected bitmap. Negative values indicate it is
+ * useless to send artwork.
+ */
+ public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
if (rcd == null) {
return;
}
IAudioService service = getService();
try {
- service.registerRemoteControlDisplay(rcd);
+ // passing a negative value for art work width and height as they are unknown at
+ // this stage
+ service.registerRemoteControlDisplay(rcd, w, h);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e);
}
@@ -2223,63 +2244,6 @@
}
}
- // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
- /**
- * @hide
- * Broadcast intent action indicating that the displays on the remote controls
- * should be updated because a new remote control client is now active. If there is no
- * {@link #EXTRA_REMOTE_CONTROL_CLIENT}, the remote control display should be cleared
- * because there is no valid client to supply it with information.
- *
- * @see #EXTRA_REMOTE_CONTROL_CLIENT
- */
- public static final String REMOTE_CONTROL_CLIENT_CHANGED =
- "android.media.REMOTE_CONTROL_CLIENT_CHANGED";
-
- // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
- /**
- * @hide
- * The IRemoteControlClientDispatcher monotonically increasing generation counter.
- *
- * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION
- */
- public static final String EXTRA_REMOTE_CONTROL_CLIENT_GENERATION =
- "android.media.EXTRA_REMOTE_CONTROL_CLIENT_GENERATION";
-
- // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
- /**
- * @hide
- * The name of the RemoteControlClient.
- * This String is passed as the client name when calling methods from the
- * IRemoteControlClientDispatcher interface.
- *
- * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION
- */
- public static final String EXTRA_REMOTE_CONTROL_CLIENT_NAME =
- "android.media.EXTRA_REMOTE_CONTROL_CLIENT_NAME";
-
- // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
- /**
- * @hide
- * The media button event receiver associated with the RemoteControlClient.
- * The {@link android.content.ComponentName} value of the event receiver can be retrieved with
- * {@link android.content.ComponentName#unflattenFromString(String)}
- *
- * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION
- */
- public static final String EXTRA_REMOTE_CONTROL_EVENT_RECEIVER =
- "android.media.EXTRA_REMOTE_CONTROL_EVENT_RECEIVER";
-
- // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
- /**
- * @hide
- * The flags describing what information has changed in the current remote control client.
- *
- * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION
- */
- public static final String EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED =
- "android.media.EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED";
-
/**
* @hide
* Reload audio settings. This method is called by Settings backup
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index ef96fec..f0a5c28 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1948,8 +1948,13 @@
!mBootCompleted) {
return;
}
- final long ident = Binder.clearCallingIdentity();
ScoClient client = getScoClient(cb, true);
+ // The calling identity must be cleared before calling ScoClient.incCount().
+ // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+ // and this must be done on behalf of system server to make sure permissions are granted.
+ // The caller identity must be cleared after getScoClient() because it is needed if a new
+ // client is created.
+ final long ident = Binder.clearCallingIdentity();
client.incCount();
Binder.restoreCallingIdentity(ident);
}
@@ -1960,8 +1965,11 @@
!mBootCompleted) {
return;
}
- final long ident = Binder.clearCallingIdentity();
ScoClient client = getScoClient(cb, false);
+ // The calling identity must be cleared before calling ScoClient.decCount().
+ // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+ // and this must be done on behalf of system server to make sure permissions are granted.
+ final long ident = Binder.clearCallingIdentity();
if (client != null) {
client.decCount();
}
@@ -4905,7 +4913,6 @@
" -- vol: " + rcse.mPlaybackVolume +
" -- volMax: " + rcse.mPlaybackVolumeMax +
" -- volObs: " + rcse.mRemoteVolumeObs);
-
}
}
synchronized (mMainRemote) {
@@ -4923,6 +4930,23 @@
/**
* Helper function:
+ * Display in the log the current entries in the list of remote control displays
+ */
+ private void dumpRCDList(PrintWriter pw) {
+ pw.println("\nRemote Control Display list entries:");
+ synchronized(mRCStack) {
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ pw.println(" IRCD: " + di.mRcDisplay +
+ " -- w:" + di.mArtworkExpectedWidth +
+ " -- h:" + di.mArtworkExpectedHeight);
+ }
+ }
+ }
+
+ /**
+ * Helper function:
* Remove any entry in the remote control stack that has the same package name as packageName
* Pre-condition: packageName != null
*/
@@ -5059,16 +5083,20 @@
*/
private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
PendingIntent newMediaIntent, boolean clearing) {
- // NOTE: Only one IRemoteControlDisplay supported in this implementation
- if (mRcDisplay != null) {
- try {
- mRcDisplay.setCurrentClientId(
- newClientGeneration, newMediaIntent, clearing);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc() "+e);
- // if we had a display before, stop monitoring its death
- rcDisplay_stopDeathMonitor_syncRcStack();
- mRcDisplay = null;
+ synchronized(mRCStack) {
+ if (mRcDisplays.size() > 0) {
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = displayIterator.next();
+ try {
+ di.mRcDisplay.setCurrentClientId(
+ newClientGeneration, newMediaIntent, clearing);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e);
+ di.release();
+ displayIterator.remove();
+ }
+ }
}
}
}
@@ -5086,7 +5114,7 @@
try {
se.mRcClient.setCurrentClientGenerationId(newClientGeneration);
} catch (RemoteException e) {
- Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()"+e);
+ Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
stackIterator.remove();
se.unlinkToRcClientDeath();
}
@@ -5144,8 +5172,7 @@
// tell the current client that it needs to send info
try {
- mCurrentRcClient.onInformationRequested(mCurrentRcClientGen,
- flags, mArtworkExpectedWidth, mArtworkExpectedHeight);
+ mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags);
} catch (RemoteException e) {
Log.e(TAG, "Current valid remote client is dead: "+e);
mCurrentRcClient = null;
@@ -5409,13 +5436,9 @@
rccId = rcse.mRccId;
// there is a new (non-null) client:
- // 1/ give the new client the current display (if any)
- if (mRcDisplay != null) {
- try {
- rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
- } catch (RemoteException e) {
- Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
- }
+ // 1/ give the new client the displays (if any)
+ if (mRcDisplays.size() > 0) {
+ plugRemoteControlDisplaysIntoClient_syncRcStack(rcse.mRcClient);
}
// 2/ monitor the new client's death
IBinder b = rcse.mRcClient.asBinder();
@@ -5485,102 +5508,141 @@
}
}
- /**
- * The remote control displays.
- * Access synchronized on mRCStack
- * NOTE: Only one IRemoteControlDisplay supported in this implementation
- */
- private IRemoteControlDisplay mRcDisplay;
- private RcDisplayDeathHandler mRcDisplayDeathHandler;
- private int mArtworkExpectedWidth = -1;
- private int mArtworkExpectedHeight = -1;
- /**
- * Inner class to monitor remote control display deaths, and unregister them from the list
- * of displays if necessary.
- */
- private class RcDisplayDeathHandler implements IBinder.DeathRecipient {
- private IBinder mCb; // To be notified of client's death
- public RcDisplayDeathHandler(IBinder b) {
- if (DEBUG_RC) Log.i(TAG, "new RcDisplayDeathHandler for "+b);
- mCb = b;
+ /**
+ * A class to encapsulate all the information about a remote control display.
+ * After instanciation, init() must always be called before the object is added in the list
+ * of displays.
+ * Before being removed from the list of displays, release() must always be called (otherwise
+ * it will leak death handlers).
+ */
+ private class DisplayInfoForServer implements IBinder.DeathRecipient {
+ /** may never be null */
+ private IRemoteControlDisplay mRcDisplay;
+ private IBinder mRcDisplayBinder;
+ private int mArtworkExpectedWidth = -1;
+ private int mArtworkExpectedHeight = -1;
+
+ public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
+ if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h);
+ mRcDisplay = rcd;
+ mRcDisplayBinder = rcd.asBinder();
+ mArtworkExpectedWidth = w;
+ mArtworkExpectedHeight = h;
+ }
+
+ public boolean init() {
+ try {
+ mRcDisplayBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ // remote control display is DOA, disqualify it
+ Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder);
+ return false;
+ }
+ return true;
+ }
+
+ public void release() {
+ try {
+ mRcDisplayBinder.unlinkToDeath(this, 0);
+ } catch (java.util.NoSuchElementException e) {
+ // not much we can do here, the display should have been unregistered anyway
+ Log.e(TAG, "Error in DisplaInfoForServer.relase()", e);
+ }
}
public void binderDied() {
synchronized(mRCStack) {
- Log.w(TAG, "RemoteControl: display died");
- mRcDisplay = null;
+ Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died");
+ // remove the display from the list
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ if (di.mRcDisplay == mRcDisplay) {
+ if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
+ displayIterator.remove();
+ return;
+ }
+ }
}
}
-
- public void unlinkToRcDisplayDeath() {
- if (DEBUG_RC) Log.i(TAG, "unlinkToRcDisplayDeath for "+mCb);
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (java.util.NoSuchElementException e) {
- // not much we can do here, the display was being unregistered anyway
- Log.e(TAG, "Encountered " + e + " in unlinkToRcDisplayDeath()");
- e.printStackTrace();
- }
- }
-
- }
-
- private void rcDisplay_stopDeathMonitor_syncRcStack() {
- if (mRcDisplay != null) { // implies (mRcDisplayDeathHandler != null)
- // we had a display before, stop monitoring its death
- mRcDisplayDeathHandler.unlinkToRcDisplayDeath();
- }
}
- private void rcDisplay_startDeathMonitor_syncRcStack() {
- if (mRcDisplay != null) {
- // new non-null display, monitor its death
- IBinder b = mRcDisplay.asBinder();
- mRcDisplayDeathHandler = new RcDisplayDeathHandler(b);
+ /**
+ * The remote control displays.
+ * Access synchronized on mRCStack
+ */
+ private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
+
+ /**
+ * Plug each registered display into the specified client
+ * @param rcc, guaranteed non null
+ */
+ private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) {
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
try {
- b.linkToDeath(mRcDisplayDeathHandler, 0);
+ rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
+ di.mArtworkExpectedHeight);
} catch (RemoteException e) {
- // remote control display is DOA, disqualify it
- Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + b);
- mRcDisplay = null;
+ Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
}
}
}
/**
+ * Is the remote control display interface already registered
+ * @param rcd
+ * @return true if the IRemoteControlDisplay is already in the list of displays
+ */
+ private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Register an IRemoteControlDisplay.
* Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
* at the top of the stack to update the new display with its information.
- * Since only one IRemoteControlDisplay is supported, this will unregister the previous display.
+ * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int)
* @param rcd the IRemoteControlDisplay to register. No effect if null.
+ * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
*/
- public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
- if ((mRcDisplay == rcd) || (rcd == null)) {
+ if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
return;
}
- // if we had a display before, stop monitoring its death
- rcDisplay_stopDeathMonitor_syncRcStack();
- mRcDisplay = rcd;
- // new display, start monitoring its death
- rcDisplay_startDeathMonitor_syncRcStack();
+ DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
+ if (!di.init()) {
+ if (DEBUG_RC) Log.e(TAG, " error registering RCD");
+ return;
+ }
+ // add RCD to list of displays
+ mRcDisplays.add(di);
- // let all the remote control clients know there is a new display, so the remote
- // control stack traversal order doesn't matter.
- // No need to unplug the previous because we only support one display
- // and the clients don't track the death of the display
+ // let all the remote control clients know there is a new display (so the remote
+ // control stack traversal order doesn't matter).
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = stackIterator.next();
if(rcse.mRcClient != null) {
try {
- rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
+ rcse.mRcClient.plugRemoteControlDisplay(rcd, w, h);
} catch (RemoteException e) {
- Log.e(TAG, "Error connecting remote control display to client: " + e);
- e.printStackTrace();
+ Log.e(TAG, "Error connecting RCD to client: ", e);
}
}
}
@@ -5593,44 +5655,86 @@
/**
* Unregister an IRemoteControlDisplay.
- * Since only one IRemoteControlDisplay is supported, this has no effect if the one to
- * unregister is not the current one.
+ * No effect if the IRemoteControlDisplay hasn't been successfully registered.
+ * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay)
* @param rcd the IRemoteControlDisplay to unregister. No effect if null.
*/
public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
synchronized(mRCStack) {
- // only one display here, so you can only unregister the current display
- if ((rcd == null) || (rcd != mRcDisplay)) {
- if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD");
+ if (rcd == null) {
return;
}
- // if we had a display before, stop monitoring its death
- rcDisplay_stopDeathMonitor_syncRcStack();
- mRcDisplay = null;
- // disconnect this remote control display from all the clients, so the remote
- // control stack traversal order doesn't matter
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
- while(stackIterator.hasNext()) {
- RemoteControlStackEntry rcse = stackIterator.next();
- if(rcse.mRcClient != null) {
- try {
- rcse.mRcClient.unplugRemoteControlDisplay(rcd);
- } catch (RemoteException e) {
- Log.e(TAG, "Error disconnecting remote control display to client: " + e);
- e.printStackTrace();
+ boolean displayWasPluggedIn = false;
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext() && !displayWasPluggedIn) {
+ final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+ displayWasPluggedIn = true;
+ di.release();
+ displayIterator.remove();
+ }
+ }
+
+ if (displayWasPluggedIn) {
+ // disconnect this remote control display from all the clients, so the remote
+ // control stack traversal order doesn't matter
+ final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ final RemoteControlStackEntry rcse = stackIterator.next();
+ if(rcse.mRcClient != null) {
+ try {
+ rcse.mRcClient.unplugRemoteControlDisplay(rcd);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error disconnecting remote control display to client: ", e);
+ }
}
}
+ } else {
+ if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD");
}
}
}
+ /**
+ * Update the size of the artwork used by an IRemoteControlDisplay.
+ * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int)
+ * @param rcd the IRemoteControlDisplay with the new artwork size requirement
+ * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ */
public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
synchronized(mRCStack) {
- // NOTE: Only one IRemoteControlDisplay supported in this implementation
- mArtworkExpectedWidth = w;
- mArtworkExpectedHeight = h;
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ boolean artworkSizeUpdate = false;
+ while (displayIterator.hasNext() && !artworkSizeUpdate) {
+ final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+ if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+ if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
+ di.mArtworkExpectedWidth = w;
+ di.mArtworkExpectedHeight = h;
+ artworkSizeUpdate = true;
+ }
+ }
+ }
+ if (artworkSizeUpdate) {
+ // RCD is currently plugged in and its artwork size has changed, notify all RCCs,
+ // stack traversal order doesn't matter
+ final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ final RemoteControlStackEntry rcse = stackIterator.next();
+ if(rcse.mRcClient != null) {
+ try {
+ rcse.mRcClient.setBitmapSizeForDisplay(rcd, w, h);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
+ }
+ }
+ }
+ }
}
}
@@ -6233,6 +6337,7 @@
dumpFocusStack(pw);
dumpRCStack(pw);
dumpRCCStack(pw);
+ dumpRCDList(pw);
dumpStreamStates(pw);
dumpRingerMode(pw);
pw.println("\nAudio routes:");
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ea99069..312c252 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -131,8 +131,31 @@
oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
in IRemoteControlClient rcClient);
- oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd);
+ /**
+ * Register an IRemoteControlDisplay.
+ * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
+ * at the top of the stack to update the new display with its information.
+ * @param rcd the IRemoteControlDisplay to register. No effect if null.
+ * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ */
+ oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h);
+ /**
+ * Unregister an IRemoteControlDisplay.
+ * No effect if the IRemoteControlDisplay hasn't been successfully registered.
+ * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
+ */
oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd);
+ /**
+ * Update the size of the artwork used by an IRemoteControlDisplay.
+ * @param rcd the IRemoteControlDisplay with the new artwork size requirement
+ * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ */
oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h);
oneway void setPlaybackInfoForRcc(int rccId, int what, int value);
diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl
index 0fbba20..5600263 100644
--- a/media/java/android/media/IRemoteControlClient.aidl
+++ b/media/java/android/media/IRemoteControlClient.aidl
@@ -34,18 +34,17 @@
* parameters are valid.
* @param generationId
* @param infoFlags
- * @param artWidth if > 0, artHeight must be > 0 too.
- * @param artHeight
* FIXME: is infoFlags required? since the RCC pushes info, this might always be called
* with RC_INFO_ALL
*/
- void onInformationRequested(int generationId, int infoFlags, int artWidth, int artHeight);
+ void onInformationRequested(int generationId, int infoFlags);
/**
* Sets the generation counter of the current client that is displayed on the remote control.
*/
void setCurrentClientGenerationId(int clientGeneration);
- void plugRemoteControlDisplay(IRemoteControlDisplay rcd);
+ void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h);
void unplugRemoteControlDisplay(IRemoteControlDisplay rcd);
+ void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h);
}
\ No newline at end of file
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 4c71ace..9a0ecdf 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -36,6 +36,8 @@
import android.util.Log;
import java.lang.IllegalArgumentException;
+import java.util.ArrayList;
+import java.util.Iterator;
/**
* RemoteControlClient enables exposing information meant to be consumed by remote controls
@@ -498,13 +500,7 @@
if (key != BITMAP_KEY_ARTWORK) {
throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
}
- if ((mArtworkExpectedWidth > 0) && (mArtworkExpectedHeight > 0)) {
- mEditorArtwork = scaleBitmapIfTooBig(bitmap,
- mArtworkExpectedWidth, mArtworkExpectedHeight);
- } else {
- // no valid resize dimensions, store as is
- mEditorArtwork = bitmap;
- }
+ mEditorArtwork = bitmap;
mArtworkChanged = true;
return this;
}
@@ -536,10 +532,10 @@
synchronized(mCacheLock) {
// assign the edited data
mMetadata = new Bundle(mEditorMetadata);
- if ((mArtwork != null) && (!mArtwork.equals(mEditorArtwork))) {
- mArtwork.recycle();
+ if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) {
+ mOriginalArtwork.recycle();
}
- mArtwork = mEditorArtwork;
+ mOriginalArtwork = mEditorArtwork;
mEditorArtwork = null;
if (mMetadataChanged & mArtworkChanged) {
// send to remote control display if conditions are met
@@ -571,7 +567,7 @@
editor.mArtworkChanged = true;
} else {
editor.mEditorMetadata = new Bundle(mMetadata);
- editor.mEditorArtwork = mArtwork;
+ editor.mEditorArtwork = mOriginalArtwork;
editor.mMetadataChanged = false;
editor.mArtworkChanged = false;
}
@@ -766,11 +762,7 @@
* accessed to be resized, in which case a copy will be made. This would add overhead in
* Bundle operations.
*/
- private Bitmap mArtwork;
- private final int ARTWORK_DEFAULT_SIZE = 256;
- private final int ARTWORK_INVALID_SIZE = -1;
- private int mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
- private int mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
+ private Bitmap mOriginalArtwork;
/**
* Cache for the transport control mask.
* Access synchronized on mCacheLock
@@ -802,10 +794,27 @@
private final PendingIntent mRcMediaIntent;
/**
- * The remote control display to which this client will send information.
- * NOTE: Only one IRemoteControlDisplay supported in this implementation
+ * A class to encapsulate all the information about a remote control display.
+ * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay
*/
- private IRemoteControlDisplay mRcDisplay;
+ private class DisplayInfoForClient {
+ /** may never be null */
+ private IRemoteControlDisplay mRcDisplay;
+ private int mArtworkExpectedWidth;
+ private int mArtworkExpectedHeight;
+
+ DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) {
+ mRcDisplay = rcd;
+ mArtworkExpectedWidth = w;
+ mArtworkExpectedHeight = h;
+ }
+ }
+
+ /**
+ * The list of remote control displays to which this client will send information.
+ * Accessed and modified synchronized on mCacheLock
+ */
+ private ArrayList<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(1);
/**
* @hide
@@ -827,17 +836,14 @@
*/
private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
- public void onInformationRequested(int clientGeneration, int infoFlags,
- int artWidth, int artHeight) {
+ public void onInformationRequested(int clientGeneration, int infoFlags) {
// only post messages, we can't block here
if (mEventHandler != null) {
// signal new client
mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
mEventHandler.dispatchMessage(
- mEventHandler.obtainMessage(
- MSG_NEW_INTERNAL_CLIENT_GEN,
- artWidth, artHeight,
- new Integer(clientGeneration)));
+ mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN,
+ /*arg1*/ clientGeneration, /*arg2, ignored*/ 0));
// send the information
mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
mEventHandler.removeMessages(MSG_REQUEST_METADATA);
@@ -861,21 +867,29 @@
}
}
- public void plugRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
// only post messages, we can't block here
- if (mEventHandler != null) {
+ if ((mEventHandler != null) && (rcd != null)) {
mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
- MSG_PLUG_DISPLAY, rcd));
+ MSG_PLUG_DISPLAY, w, h, rcd));
}
}
public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
// only post messages, we can't block here
- if (mEventHandler != null) {
+ if ((mEventHandler != null) && (rcd != null)) {
mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
MSG_UNPLUG_DISPLAY, rcd));
}
}
+
+ public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) {
+ // only post messages, we can't block here
+ if ((mEventHandler != null) && (rcd != null)) {
+ mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+ MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd));
+ }
+ }
};
/**
@@ -915,6 +929,7 @@
private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
private final static int MSG_PLUG_DISPLAY = 7;
private final static int MSG_UNPLUG_DISPLAY = 8;
+ private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9;
private class EventHandler extends Handler {
public EventHandler(RemoteControlClient rcc, Looper looper) {
@@ -945,17 +960,20 @@
}
break;
case MSG_NEW_INTERNAL_CLIENT_GEN:
- onNewInternalClientGen((Integer)msg.obj, msg.arg1, msg.arg2);
+ onNewInternalClientGen(msg.arg1);
break;
case MSG_NEW_CURRENT_CLIENT_GEN:
onNewCurrentClientGen(msg.arg1);
break;
case MSG_PLUG_DISPLAY:
- onPlugDisplay((IRemoteControlDisplay)msg.obj);
+ onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
break;
case MSG_UNPLUG_DISPLAY:
onUnplugDisplay((IRemoteControlDisplay)msg.obj);
break;
+ case MSG_UPDATE_DISPLAY_ARTWORK_SIZE:
+ onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
+ break;
default:
Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
}
@@ -963,75 +981,106 @@
}
//===========================================================
- // Communication with IRemoteControlDisplay
-
- private void detachFromDisplay_syncCacheLock() {
- mRcDisplay = null;
- mArtworkExpectedWidth = ARTWORK_INVALID_SIZE;
- mArtworkExpectedHeight = ARTWORK_INVALID_SIZE;
- }
+ // Communication with the IRemoteControlDisplay (the displays known to the system)
private void sendPlaybackState_syncCacheLock() {
- if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
- try {
- mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState,
- mPlaybackStateChangeTimeMs);
- } catch (RemoteException e) {
- Log.e(TAG, "Error in setPlaybackState(), dead display "+e);
- detachFromDisplay_syncCacheLock();
+ if (mCurrentClientGenId == mInternalClientGenId) {
+ final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ try {
+ di.mRcDisplay.setPlaybackState(mInternalClientGenId,
+ mPlaybackState, mPlaybackStateChangeTimeMs);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e);
+ displayIterator.remove();
+ }
}
}
}
private void sendMetadata_syncCacheLock() {
- if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
- try {
- mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
- } catch (RemoteException e) {
- Log.e(TAG, "Error in sendPlaybackState(), dead display "+e);
- detachFromDisplay_syncCacheLock();
+ if (mCurrentClientGenId == mInternalClientGenId) {
+ final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ try {
+ di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e);
+ displayIterator.remove();
+ }
}
}
}
private void sendTransportControlFlags_syncCacheLock() {
- if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
- try {
- mRcDisplay.setTransportControlFlags(mInternalClientGenId,
- mTransportControlFlags);
- } catch (RemoteException e) {
- Log.e(TAG, "Error in sendTransportControlFlags(), dead display "+e);
- detachFromDisplay_syncCacheLock();
+ if (mCurrentClientGenId == mInternalClientGenId) {
+ final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ try {
+ di.mRcDisplay.setTransportControlFlags(mInternalClientGenId,
+ mTransportControlFlags);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay,
+ e);
+ displayIterator.remove();
+ }
}
}
}
private void sendArtwork_syncCacheLock() {
- if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
- // even though we have already scaled in setArtwork(), when this client needs to
- // send the bitmap, there might be newer and smaller expected dimensions, so we have
- // to check again.
- mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
- try {
- mRcDisplay.setArtwork(mInternalClientGenId, mArtwork);
- } catch (RemoteException e) {
- Log.e(TAG, "Error in sendArtwork(), dead display "+e);
- detachFromDisplay_syncCacheLock();
+ // FIXME modify to cache all requested sizes?
+ if (mCurrentClientGenId == mInternalClientGenId) {
+ final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) {
+ displayIterator.remove();
+ }
}
}
}
- private void sendMetadataWithArtwork_syncCacheLock() {
- if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
- // even though we have already scaled in setArtwork(), when this client needs to
- // send the bitmap, there might be newer and smaller expected dimensions, so we have
- // to check again.
- mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
+ /**
+ * Send artwork to an IRemoteControlDisplay.
+ * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its
+ * dimension requirements.
+ * @return false if there was an error communicating with the IRemoteControlDisplay.
+ */
+ private boolean sendArtworkToDisplay(DisplayInfoForClient di) {
+ if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
+ Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
+ di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
try {
- mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, mArtwork);
+ di.mRcDisplay.setArtwork(mInternalClientGenId, artwork);
} catch (RemoteException e) {
- Log.e(TAG, "Error in setAllMetadata(), dead display "+e);
- detachFromDisplay_syncCacheLock();
+ Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void sendMetadataWithArtwork_syncCacheLock() {
+ // FIXME modify to cache all requested sizes?
+ if (mCurrentClientGenId == mInternalClientGenId) {
+ final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ try {
+ if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
+ Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
+ di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
+ di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
+ } else {
+ di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e);
+ displayIterator.remove();
+ }
}
}
}
@@ -1067,15 +1116,11 @@
//===========================================================
// Message handlers
- private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) {
+ private void onNewInternalClientGen(int clientGeneration) {
synchronized (mCacheLock) {
// this remote control client is told it is the "focused" one:
// it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
- mInternalClientGenId = clientGeneration.intValue();
- if (artWidth > 0) {
- mArtworkExpectedWidth = artWidth;
- mArtworkExpectedHeight = artHeight;
- }
+ mInternalClientGenId = clientGeneration;
}
}
@@ -1085,18 +1130,62 @@
}
}
- private void onPlugDisplay(IRemoteControlDisplay rcd) {
+ /** pre-condition rcd != null */
+ private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) {
synchronized(mCacheLock) {
- mRcDisplay = rcd;
+ // do we have this display already?
+ boolean displayKnown = false;
+ final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext() && !displayKnown) {
+ final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder());
+ if (displayKnown) {
+ // this display was known but the change in artwork size will cause the
+ // artwork to be refreshed
+ if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
+ di.mArtworkExpectedWidth = w;
+ di.mArtworkExpectedHeight = h;
+ if (!sendArtworkToDisplay(di)) {
+ displayIterator.remove();
+ }
+ }
+ }
+ }
+ if (!displayKnown) {
+ mRcDisplays.add(new DisplayInfoForClient(rcd, w, h));
+ }
}
}
+ /** pre-condition rcd != null */
private void onUnplugDisplay(IRemoteControlDisplay rcd) {
synchronized(mCacheLock) {
- if ((mRcDisplay != null) && (mRcDisplay.asBinder().equals(rcd.asBinder()))) {
- mRcDisplay = null;
- mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
- mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
+ final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+ displayIterator.remove();
+ return;
+ }
+ }
+ }
+ }
+
+ /** pre-condition rcd != null */
+ private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) {
+ synchronized(mCacheLock) {
+ final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) &&
+ ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
+ di.mArtworkExpectedWidth = w;
+ di.mArtworkExpectedHeight = h;
+ if (!sendArtworkToDisplay(di)) {
+ displayIterator.remove();
+ }
+ break;
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index f0b1d7e..9f54573 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -407,6 +407,7 @@
mSystemIconArea = (LinearLayout) mStatusBarView.findViewById(R.id.system_icon_area);
mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons);
mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons);
+ mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon);
mNotificationIcons.setOverflowIndicator(mMoreIcon);
mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents);
mTickerView = mStatusBarView.findViewById(R.id.ticker);
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java
index ffa88d5..fda47d5 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java
@@ -250,6 +250,15 @@
}
@Override
+ protected void onSizeChanged (int w, int h, int oldw, int oldh) {
+ if (mAttached) {
+ int dim = Math.min(512, Math.max(w, h));
+ if (DEBUG) Log.v(TAG, "TCV uses bitmap size=" + dim);
+ mAudioManager.remoteControlDisplayUsesBitmapSize(mIRCD, dim, dim);
+ }
+ }
+
+ @Override
public void onDetachedFromWindow() {
if (DEBUG) Log.v(TAG, "onDetachFromWindow()");
super.onDetachedFromWindow();
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
index 8562f0c..30c95fb 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
@@ -200,6 +200,9 @@
stretch, stretch, type, flags, PixelFormat.TRANSLUCENT);
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen;
+ lp.screenOrientation = enableScreenRotation ?
+ ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+
if (ActivityManager.isHighEndGfx()) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
lp.privateFlags |=
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 6ba5cff..d0048bf 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1243,7 +1243,11 @@
unbindCurrentMethodLocked(false, false);
return;
}
- if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
+ // Remove commands relating to the previous service. Otherwise WindowManagerService
+ // will reject the command because the token attached to these messages is invalid.
+ mCaller.removeMessages(MSG_SHOW_SOFT_INPUT);
+ mCaller.removeMessages(MSG_HIDE_SOFT_INPUT);
+ if (true || DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
if (mCurClient != null) {
@@ -1689,7 +1693,7 @@
}
}
- if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
+ if (true || DEBUG) Slog.v(TAG, "Client requesting input be shown");
return showCurrentInputLocked(flags, resultReceiver);
}
} finally {
@@ -1713,6 +1717,8 @@
boolean res = false;
if (mCurMethod != null) {
+ if (true ||DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken,
+ new RuntimeException("here").fillInStackTrace());
executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
resultReceiver));
@@ -1784,12 +1790,12 @@
boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mShowExplicitlyRequested || mShowForced)) {
- if (DEBUG) Slog.v(TAG,
+ if (true ||DEBUG) Slog.v(TAG,
"Not hiding: explicit show not cancelled by non-explicit hide");
return false;
}
if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
- if (DEBUG) Slog.v(TAG,
+ if (true ||DEBUG) Slog.v(TAG,
"Not hiding: forced show not cancelled by not-always hide");
return false;
}
@@ -2301,6 +2307,8 @@
case MSG_SHOW_SOFT_INPUT:
args = (SomeArgs)msg.obj;
try {
+ if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+ + msg.arg1 + ", " + args.arg2 + ")");
((IInputMethod)args.arg1).showSoftInput(msg.arg1,
(ResultReceiver)args.arg2);
} catch (RemoteException e) {
@@ -2310,6 +2318,8 @@
case MSG_HIDE_SOFT_INPUT:
args = (SomeArgs)msg.obj;
try {
+ if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+ + args.arg2 + ")");
((IInputMethod)args.arg1).hideSoftInput(0,
(ResultReceiver)args.arg2);
} catch (RemoteException e) {
@@ -2319,7 +2329,7 @@
case MSG_ATTACH_TOKEN:
args = (SomeArgs)msg.obj;
try {
- if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
+ if (true || DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
} catch (RemoteException e) {
}
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 1e394d6..9e036d1 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -1255,9 +1255,6 @@
sendAccessibilityEvent(notification, pkg);
}
- // finally, keep some of this information around for later use
- mArchive.record(n);
-
notifyPostedLocked(r);
} else {
Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
@@ -1472,6 +1469,9 @@
if (mLedNotification == r) {
mLedNotification = null;
}
+
+ // Save it for users of getHistoricalNotifications()
+ mArchive.record(r.sbn);
}
/**
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 51f001f..2d12a77 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -170,7 +170,7 @@
public class PackageManagerService extends IPackageManager.Stub {
static final String TAG = "PackageManager";
static final boolean DEBUG_SETTINGS = false;
- static final boolean DEBUG_PREFERRED = true;
+ static final boolean DEBUG_PREFERRED = false;
static final boolean DEBUG_UPGRADE = false;
private static final boolean DEBUG_INSTALL = false;
private static final boolean DEBUG_REMOVE = false;
@@ -339,9 +339,20 @@
final SparseArray<HashSet<String>> mSystemPermissions =
new SparseArray<HashSet<String>>();
+ static final class SharedLibraryEntry {
+ final String path;
+ final String apk;
+
+ SharedLibraryEntry(String _path, String _apk) {
+ path = _path;
+ apk = _apk;
+ }
+ }
+
// These are the built-in shared libraries that were read from the
// etc/permissions.xml file.
- final HashMap<String, String> mSharedLibraries = new HashMap<String, String>();
+ final HashMap<String, SharedLibraryEntry> mSharedLibraries
+ = new HashMap<String, SharedLibraryEntry>();
// Temporary for building the final shared libraries for an .apk.
String[] mTmpSharedLibraries = null;
@@ -390,8 +401,7 @@
final SparseArray<PackageVerificationState> mPendingVerification
= new SparseArray<PackageVerificationState>();
- final ArrayList<PackageParser.Package> mDeferredDexOpt =
- new ArrayList<PackageParser.Package>();
+ HashSet<PackageParser.Package> mDeferredDexOpt = null;
/** Token for keys in mPendingVerification. */
private int mPendingVerificationToken = 0;
@@ -514,10 +524,9 @@
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
- if (DEBUG_INSTALL) Slog.i(TAG, "init_copy");
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
- if (DEBUG_INSTALL) Slog.i(TAG, "idx=" + idx);
+ if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
// If a bind was already initiated we dont really
// need to do anything. The pending install
// will be processed later on.
@@ -1071,9 +1080,12 @@
* Also ensure all external libraries have had dexopt run on them.
*/
if (mSharedLibraries.size() > 0) {
- Iterator<String> libs = mSharedLibraries.values().iterator();
+ Iterator<SharedLibraryEntry> libs = mSharedLibraries.values().iterator();
while (libs.hasNext()) {
- String lib = libs.next();
+ String lib = libs.next().path;
+ if (lib == null) {
+ continue;
+ }
try {
if (dalvik.system.DexFile.isDexOptNeeded(lib)) {
libFiles.add(lib);
@@ -1277,6 +1289,10 @@
mDrmAppInstallObserver = null;
}
+ // Now that we know all of the shared libraries, update all clients to have
+ // the correct library paths.
+ updateAllSharedLibrariesLPw();
+
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
Slog.i(TAG, "Time to scan packages: "
@@ -1517,7 +1533,7 @@
+ parser.getPositionDescription());
} else {
//Log.i(TAG, "Got library " + lname + " in " + lfile);
- mSharedLibraries.put(lname, lfile);
+ mSharedLibraries.put(lname, new SharedLibraryEntry(lfile, null));
}
XmlUtils.skipCurrentTag(parser);
continue;
@@ -3249,6 +3265,7 @@
int parseFlags, int scanMode, long currentTime, UserHandle user) {
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
String scanPath = scanFile.getPath();
+ if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath);
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser(scanPath);
pp.setSeparateProcesses(mSeparateProcesses);
@@ -3278,6 +3295,7 @@
// package. Must look for it either under the original or real
// package name depending on our state.
updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+ if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
}
// First check if this is a system package that may involve an update
if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
@@ -3285,6 +3303,7 @@
// The path has changed from what was last scanned... check the
// version of the new path against what we have stored to determine
// what to do.
+ if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
if (pkg.mVersionCode < ps.versionCode) {
// The system package has been updated and the code path does not match
// Ignore entry. Skip it.
@@ -3298,6 +3317,7 @@
updatedPkg.codePath = scanFile;
updatedPkg.codePathString = scanFile.toString();
}
+ updatedPkg.pkg = pkg;
mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
return null;
} else {
@@ -3353,6 +3373,7 @@
*/
if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Signature mismatch!");
deletePackageLI(pkg.packageName, null, true, 0, null, false);
ps = null;
} else {
@@ -3484,28 +3505,28 @@
}
public void performBootDexOpt() {
- ArrayList<PackageParser.Package> pkgs = null;
+ HashSet<PackageParser.Package> pkgs = null;
synchronized (mPackages) {
- if (mDeferredDexOpt.size() > 0) {
- pkgs = new ArrayList<PackageParser.Package>(mDeferredDexOpt);
- mDeferredDexOpt.clear();
- }
+ pkgs = mDeferredDexOpt;
+ mDeferredDexOpt = null;
}
if (pkgs != null) {
- for (int i=0; i<pkgs.size(); i++) {
+ int i = 0;
+ for (PackageParser.Package pkg : pkgs) {
if (!isFirstBoot()) {
+ i++;
try {
ActivityManagerNative.getDefault().showBootMessage(
mContext.getResources().getString(
com.android.internal.R.string.android_upgrading_apk,
- i+1, pkgs.size()), true);
+ i, pkgs.size()), true);
} catch (RemoteException e) {
}
}
- PackageParser.Package p = pkgs.get(i);
+ PackageParser.Package p = pkg;
synchronized (mInstallLock) {
if (!p.mDidDexOpt) {
- performDexOptLI(p, false, false);
+ performDexOptLI(p, false, false, true);
}
}
}
@@ -3527,7 +3548,27 @@
}
}
synchronized (mInstallLock) {
- return performDexOptLI(p, false, false) == DEX_OPT_PERFORMED;
+ return performDexOptLI(p, false, false, true) == DEX_OPT_PERFORMED;
+ }
+ }
+
+ private void performDexOptLibsLI(ArrayList<String> libs, boolean forceDex, boolean defer,
+ HashSet<String> done) {
+ for (int i=0; i<libs.size(); i++) {
+ PackageParser.Package libPkg;
+ String libName;
+ synchronized (mPackages) {
+ libName = libs.get(i);
+ SharedLibraryEntry lib = mSharedLibraries.get(libName);
+ if (lib != null && lib.apk != null) {
+ libPkg = mPackages.get(lib.apk);
+ } else {
+ libPkg = null;
+ }
+ }
+ if (libPkg != null && !done.contains(libName)) {
+ performDexOptLI(libPkg, forceDex, defer, done);
+ }
}
}
@@ -3536,14 +3577,27 @@
static final int DEX_OPT_DEFERRED = 2;
static final int DEX_OPT_FAILED = -1;
- private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer) {
+ private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+ HashSet<String> done) {
boolean performed = false;
+ if (done != null) {
+ done.add(pkg.packageName);
+ if (pkg.usesLibraries != null) {
+ performDexOptLibsLI(pkg.usesLibraries, forceDex, defer, done);
+ }
+ if (pkg.usesOptionalLibraries != null) {
+ performDexOptLibsLI(pkg.usesOptionalLibraries, forceDex, defer, done);
+ }
+ }
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
String path = pkg.mScanPath;
int ret = 0;
try {
if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
if (!forceDex && defer) {
+ if (mDeferredDexOpt == null) {
+ mDeferredDexOpt = new HashSet<PackageParser.Package>();
+ }
mDeferredDexOpt.add(pkg);
return DEX_OPT_DEFERRED;
} else {
@@ -3576,6 +3630,19 @@
return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
}
+ private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+ boolean inclDependencies) {
+ HashSet<String> done;
+ boolean performed = false;
+ if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
+ done = new HashSet<String>();
+ done.add(pkg.packageName);
+ } else {
+ done = null;
+ }
+ return performDexOptLI(pkg, forceDex, defer, done);
+ }
+
private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) {
if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
Slog.w(TAG, "Unable to update from " + oldPkg.name
@@ -3646,6 +3713,113 @@
return res;
}
+ private int addSharedLibraryLPw(final SharedLibraryEntry file, int num,
+ PackageParser.Package changingLib) {
+ if (file.path != null) {
+ mTmpSharedLibraries[num] = file.path;
+ return num+1;
+ }
+ PackageParser.Package p = mPackages.get(file.apk);
+ if (changingLib != null && changingLib.packageName.equals(file.apk)) {
+ // If we are doing this while in the middle of updating a library apk,
+ // then we need to make sure to use that new apk for determining the
+ // dependencies here. (We haven't yet finished committing the new apk
+ // to the package manager state.)
+ if (p == null || p.packageName.equals(changingLib.packageName)) {
+ p = changingLib;
+ }
+ }
+ if (p != null) {
+ String path = p.mPath;
+ for (int i=0; i<num; i++) {
+ if (mTmpSharedLibraries[i].equals(path)) {
+ return num;
+ }
+ }
+ mTmpSharedLibraries[num] = p.mPath;
+ return num+1;
+ }
+ return num;
+ }
+
+ private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
+ PackageParser.Package changingLib) {
+ if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
+ if (mTmpSharedLibraries == null ||
+ mTmpSharedLibraries.length < mSharedLibraries.size()) {
+ mTmpSharedLibraries = new String[mSharedLibraries.size()];
+ }
+ int num = 0;
+ int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
+ for (int i=0; i<N; i++) {
+ final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
+ if (file == null) {
+ Slog.e(TAG, "Package " + pkg.packageName
+ + " requires unavailable shared library "
+ + pkg.usesLibraries.get(i) + "; failing!");
+ mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+ return false;
+ }
+ num = addSharedLibraryLPw(file, num, changingLib);
+ }
+ N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
+ for (int i=0; i<N; i++) {
+ final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
+ if (file == null) {
+ Slog.w(TAG, "Package " + pkg.packageName
+ + " desires unavailable shared library "
+ + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
+ } else {
+ num = addSharedLibraryLPw(file, num, changingLib);
+ }
+ }
+ if (num > 0) {
+ pkg.usesLibraryFiles = new String[num];
+ System.arraycopy(mTmpSharedLibraries, 0,
+ pkg.usesLibraryFiles, 0, num);
+ } else {
+ pkg.usesLibraryFiles = null;
+ }
+ }
+ return true;
+ }
+
+ private static boolean hasString(List<String> list, List<String> which) {
+ if (list == null) {
+ return false;
+ }
+ for (int i=list.size()-1; i>=0; i--) {
+ for (int j=which.size()-1; j>=0; j--) {
+ if (which.get(j).equals(list.get(i))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void updateAllSharedLibrariesLPw() {
+ for (PackageParser.Package pkg : mPackages.values()) {
+ updateSharedLibrariesLPw(pkg, null);
+ }
+ }
+
+ private ArrayList<PackageParser.Package> updateAllSharedLibrariesLPw(
+ PackageParser.Package changingPkg) {
+ ArrayList<PackageParser.Package> res = null;
+ for (PackageParser.Package pkg : mPackages.values()) {
+ if (hasString(pkg.usesLibraries, changingPkg.libraryNames)
+ || hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames)) {
+ if (res == null) {
+ res = new ArrayList<PackageParser.Package>();
+ }
+ res.add(pkg);
+ updateSharedLibrariesLPw(pkg, changingPkg);
+ }
+ }
+ return res;
+ }
+
private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
int parseFlags, int scanMode, long currentTime, UserHandle user) {
File scanFile = new File(pkg.mScanPath);
@@ -3725,42 +3899,14 @@
// writer
synchronized (mPackages) {
- // Check all shared libraries and map to their actual file path.
- if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
- if (mTmpSharedLibraries == null ||
- mTmpSharedLibraries.length < mSharedLibraries.size()) {
- mTmpSharedLibraries = new String[mSharedLibraries.size()];
- }
- int num = 0;
- int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
- for (int i=0; i<N; i++) {
- final String file = mSharedLibraries.get(pkg.usesLibraries.get(i));
- if (file == null) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " requires unavailable shared library "
- + pkg.usesLibraries.get(i) + "; failing!");
- mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
- return null;
- }
- mTmpSharedLibraries[num] = file;
- num++;
- }
- N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
- for (int i=0; i<N; i++) {
- final String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
- if (file == null) {
- Slog.w(TAG, "Package " + pkg.packageName
- + " desires unavailable shared library "
- + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
- } else {
- mTmpSharedLibraries[num] = file;
- num++;
- }
- }
- if (num > 0) {
- pkg.usesLibraryFiles = new String[num];
- System.arraycopy(mTmpSharedLibraries, 0,
- pkg.usesLibraryFiles, 0, num);
+ if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ // Check all shared libraries and map to their actual file path.
+ // We only do this here for apps not on a system dir, because those
+ // are the only ones that can fail an install due to this. We
+ // will take care of the system apps by updating all of their
+ // library paths after the scan is done.
+ if (!updateSharedLibrariesLPw(pkg, null)) {
+ return null;
}
}
@@ -4166,7 +4312,7 @@
pkg.mScanPath = path;
if ((scanMode&SCAN_NO_DEX) == 0) {
- if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
+ if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
== DEX_OPT_FAILED) {
mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
return null;
@@ -4178,6 +4324,80 @@
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
}
+ ArrayList<PackageParser.Package> clientLibPkgs = null;
+
+ // writer
+ synchronized (mPackages) {
+ if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Only system apps can add new shared libraries.
+ if (pkg.libraryNames != null) {
+ for (int i=0; i<pkg.libraryNames.size(); i++) {
+ String name = pkg.libraryNames.get(i);
+ boolean allowed = false;
+ if (isUpdatedSystemApp(pkg)) {
+ // New library entries can only be added through the
+ // system image. This is important to get rid of a lot
+ // of nasty edge cases: for example if we allowed a non-
+ // system update of the app to add a library, then uninstalling
+ // the update would make the library go away, and assumptions
+ // we made such as through app install filtering would now
+ // have allowed apps on the device which aren't compatible
+ // with it. Better to just have the restriction here, be
+ // conservative, and create many fewer cases that can negatively
+ // impact the user experience.
+ final PackageSetting sysPs = mSettings
+ .getDisabledSystemPkgLPr(pkg.packageName);
+ if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) {
+ for (int j=0; j<sysPs.pkg.libraryNames.size(); j++) {
+ if (name.equals(sysPs.pkg.libraryNames.get(j))) {
+ allowed = true;
+ allowed = true;
+ break;
+ }
+ }
+ }
+ } else {
+ allowed = true;
+ }
+ if (allowed) {
+ if (!mSharedLibraries.containsKey(name)) {
+ mSharedLibraries.put(name, new SharedLibraryEntry(null,
+ pkg.packageName));
+ } else if (!name.equals(pkg.packageName)) {
+ Slog.w(TAG, "Package " + pkg.packageName + " library "
+ + name + " already exists; skipping");
+ }
+ } else {
+ Slog.w(TAG, "Package " + pkg.packageName + " declares lib "
+ + name + " that is not declared on system image; skipping");
+ }
+ }
+ if ((scanMode&SCAN_BOOTING) == 0) {
+ // If we are not booting, we need to update any applications
+ // that are clients of our shared library. If we are booting,
+ // this will all be done once the scan is complete.
+ clientLibPkgs = updateAllSharedLibrariesLPw(pkg);
+ }
+ }
+ }
+ }
+
+ // We also need to dexopt any apps that are dependent on this library. Note that
+ // if these fail, we should abort the install since installing the library will
+ // result in some apps being broken.
+ if (clientLibPkgs != null) {
+ if ((scanMode&SCAN_NO_DEX) == 0) {
+ for (int i=0; i<clientLibPkgs.size(); i++) {
+ PackageParser.Package clientPkg = clientLibPkgs.get(i);
+ if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+ == DEX_OPT_FAILED) {
+ mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
+ return null;
+ }
+ }
+ }
+ }
+
// Request the ActivityManager to kill the process(only for existing packages)
// so that we do not end up in a confused state while the user is still using the older
// version of the application while the new one gets installed.
@@ -4186,6 +4406,15 @@
pkg.applicationInfo.uid);
}
+ // Also need to kill any apps that are dependent on the library.
+ if (clientLibPkgs != null) {
+ for (int i=0; i<clientLibPkgs.size(); i++) {
+ PackageParser.Package clientPkg = clientLibPkgs.get(i);
+ killApplication(clientPkg.applicationInfo.packageName,
+ clientPkg.applicationInfo.uid);
+ }
+ }
+
// writer
synchronized (mPackages) {
// We don't expect installation to fail beyond this point,
@@ -4591,7 +4820,7 @@
}
}
}
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4627,7 +4856,7 @@
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.receivers.get(i);
mReceivers.removeActivity(a, "receiver");
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4645,7 +4874,7 @@
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.activities.get(i);
mActivities.removeActivity(a, "activity");
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4668,7 +4897,7 @@
}
if (bp != null && bp.perm == p) {
bp.perm = null;
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4687,7 +4916,7 @@
for (i=0; i<N; i++) {
PackageParser.Instrumentation a = pkg.instrumentation.get(i);
mInstrumentation.remove(a.getComponentName());
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4699,6 +4928,31 @@
if (r != null) {
if (DEBUG_REMOVE) Log.d(TAG, " Instrumentation: " + r);
}
+
+ r = null;
+ if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Only system apps can hold shared libraries.
+ if (pkg.libraryNames != null) {
+ for (i=0; i<pkg.libraryNames.size(); i++) {
+ String name = pkg.libraryNames.get(i);
+ SharedLibraryEntry cur = mSharedLibraries.get(name);
+ if (cur != null && cur.apk != null && cur.apk.equals(pkg.packageName)) {
+ mSharedLibraries.remove(name);
+ if (DEBUG_REMOVE && chatty) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append(name);
+ }
+ }
+ }
+ }
+ }
+ if (r != null) {
+ if (DEBUG_REMOVE) Log.d(TAG, " Libraries: " + r);
+ }
}
private static final boolean isPackageFilename(String name) {
@@ -4860,7 +5114,23 @@
if (origGp.grantedPermissions.contains(perm)) {
allowed = true;
} else {
+ // The system apk may have been updated with an older
+ // version of the one on the data partition, but which
+ // granted a new system permission that it didn't have
+ // before. In this case we do want to allow the app to
+ // now get the new permission, because it is allowed by
+ // the system image.
allowed = false;
+ if (sysPs.pkg != null) {
+ for (int j=0;
+ j<sysPs.pkg.requestedPermissions.size(); j++) {
+ if (perm.equals(
+ sysPs.pkg.requestedPermissions.get(j))) {
+ allowed = true;
+ break;
+ }
+ }
+ }
}
} else {
allowed = true;
@@ -5557,6 +5827,7 @@
}
if ((event&REMOVE_EVENTS) != 0) {
if (ps != null) {
+ if (DEBUG_REMOVE) Slog.d(TAG, "Package disappeared: " + ps);
removePackageLI(ps, true);
removedPackage = ps.name;
removedAppId = ps.appId;
@@ -5565,6 +5836,7 @@
if ((event&ADD_EVENTS) != 0) {
if (p == null) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "New file appeared: " + fullPath);
p = scanPackageLI(fullPath,
(mIsRom ? PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR: 0) |
@@ -6126,7 +6398,7 @@
final boolean startCopy() {
boolean res;
try {
- if (DEBUG_INSTALL) Slog.i(TAG, "startCopy");
+ if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
if (++mRetries > MAX_RETRIES) {
Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
@@ -6170,6 +6442,13 @@
}
@Override
+ public String toString() {
+ return "MeasureParams{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + mStats.packageName + "}";
+ }
+
+ @Override
void handleStartCopy() throws RemoteException {
synchronized (mInstallLock) {
mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats.userHandle, mStats);
@@ -6258,6 +6537,13 @@
this.encryptionParams = encryptionParams;
}
+ @Override
+ public String toString() {
+ return "InstallParams{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + mPackageURI + "}";
+ }
+
public ManifestDigest getManifestDigest() {
if (verificationParams == null) {
return null;
@@ -6671,6 +6957,13 @@
}
}
+ @Override
+ public String toString() {
+ return "MoveParams{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
public void handleStartCopy() throws RemoteException {
mRet = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
// Check for storage space on target medium
@@ -7582,6 +7875,7 @@
// Remember this for later, in case we need to rollback this install
String pkgName = pkg.packageName;
+ if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
synchronized(mPackages) {
if (mSettings.mRenamedPackages.containsKey(pkgName)) {
@@ -7638,6 +7932,7 @@
// First find the old package info and check signatures
synchronized(mPackages) {
oldPackage = mPackages.get(pkgName);
+ if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage);
if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
Slog.w(TAG, "New package has a different signature: " + pkgName);
@@ -7663,6 +7958,8 @@
boolean deletedPkg = true;
boolean updatedSettings = false;
+ if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old="
+ + deletedPackage);
long origUpdateTime;
if (pkg.mExtras != null) {
origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime;
@@ -7700,6 +7997,7 @@
// scanPackageLocked, unless those directories existed before we even tried to
// install.
if(updatedSettings) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, rolling pack: " + pkgName);
deletePackageLI(
pkgName, null, true,
PackageManager.DELETE_KEEP_DATA,
@@ -7708,6 +8006,7 @@
// Since we failed to install the new package we need to restore the old
// package that we deleted.
if(deletedPkg) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage);
File restoreFile = new File(deletedPackage.mPath);
// Parse old package
boolean oldOnSd = isExternal(deletedPackage);
@@ -7737,6 +8036,8 @@
private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
String installerPackageName, PackageInstalledInfo res) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
+ + ", old=" + deletedPackage);
PackageParser.Package newPackage = null;
boolean updatedSettings = false;
parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
@@ -7859,7 +8160,7 @@
return;
}
- Log.d(TAG, "New package installed in " + newPackage.mPath);
+ if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath);
synchronized (mPackages) {
updatePermissionsLPw(newPackage.packageName, newPackage,
@@ -7889,6 +8190,7 @@
// Result object to be returned
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
+ if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
// Retrieve PackageSettings and parse package
int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
@@ -7950,14 +8252,18 @@
pkg.setPackageName(oldName);
pkgName = pkg.packageName;
replace = true;
+ if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName="
+ + oldName + " pkgName=" + pkgName);
} else if (mPackages.containsKey(pkgName)) {
// This package, under its official name, already exists
// on the device; we should replace it.
replace = true;
+ if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName);
}
}
PackageSetting ps = mSettings.mPackages.get(pkgName);
if (ps != null) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
if (ps.pkg != null && ps.pkg.applicationInfo != null) {
systemApp = (ps.pkg.applicationInfo.flags &
@@ -8104,6 +8410,7 @@
return;
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId);
// Queue up an async operation since the package deletion may take a little while.
mHandler.post(new Runnable() {
public void run() {
@@ -8151,6 +8458,7 @@
boolean removedForAllUsers = false;
boolean systemUpdate = false;
synchronized (mInstallLock) {
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
res = deletePackageLI(packageName,
(flags & PackageManager.DELETE_ALL_USERS) != 0
? UserHandle.ALL : new UserHandle(userId),
@@ -8159,6 +8467,8 @@
if (res && !systemUpdate && mPackages.get(packageName) == null) {
removedForAllUsers = true;
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate
+ + " removedForAllUsers=" + removedForAllUsers);
}
if (res) {
@@ -8234,6 +8544,7 @@
private void removePackageDataLI(PackageSetting ps, PackageRemovedInfo outInfo,
int flags, boolean writeSettings) {
String packageName = ps.name;
+ if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
// Retrieve object to delete permissions for shared user later on
final PackageSetting deletedPs;
@@ -8289,11 +8600,13 @@
synchronized (mPackages) {
disabledPs = mSettings.getDisabledSystemPkgLPr(newPs.name);
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "deleteSystemPackageLI: newPs=" + newPs
+ + " disabledPs=" + disabledPs);
if (disabledPs == null) {
Slog.w(TAG, "Attempt to delete unknown system package "+ newPs.name);
return false;
- } else {
- Log.i(TAG, "Deleting system pkg from data partition");
+ } else if (DEBUG_REMOVE) {
+ Slog.d(TAG, "Deleting system pkg from data partition");
}
// Delete the updated package
outInfo.isRemovedPackageSystemUpdate = true;
@@ -8317,6 +8630,7 @@
NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString);
}
// Install the system package
+ if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM,
SCAN_MONITOR | SCAN_NO_PATHS, 0, null);
@@ -8366,6 +8680,7 @@
Slog.w(TAG, "Attempt to delete null packageName.");
return false;
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
PackageSetting ps;
boolean dataOnly = false;
int removeUser = -1;
@@ -8376,11 +8691,14 @@
Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
return false;
}
- if (user != null
+ if ((!isSystemApp(ps) || (flags&PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
&& user.getIdentifier() != UserHandle.USER_ALL) {
// The caller is asking that the package only be deleted for a single
// user. To do this, we just mark its uninstalled state and delete
- // its data.
+ // its data. If this is a system app, we only allow this to happen if
+ // they have set the special DELETE_SYSTEM_APP which requests different
+ // semantics than normal for uninstalling system apps.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Only deleting for single user");
ps.setUserState(user.getIdentifier(),
COMPONENT_ENABLED_STATE_DEFAULT,
false, //installed
@@ -8392,12 +8710,14 @@
// Other user still have this package installed, so all
// we need to do is clear this user's data and save that
// it is uninstalled.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
removeUser = user.getIdentifier();
appId = ps.appId;
mSettings.writePackageRestrictionsLPr(removeUser);
} else {
// We need to set it back to 'installed' so the uninstall
// broadcasts will be sent correctly.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
ps.setInstalled(true, user.getIdentifier());
}
} else {
@@ -8405,6 +8725,7 @@
// other users still have this package installed, so all
// we need to do is clear this user's data and save that
// it is uninstalled.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
removeUser = user.getIdentifier();
appId = ps.appId;
mSettings.writePackageRestrictionsLPr(removeUser);
@@ -8415,6 +8736,7 @@
if (removeUser >= 0) {
// From above, we determined that we are deleting this only
// for a single user. Continue the work here.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Updating install state for user: " + removeUser);
if (outInfo != null) {
outInfo.removedPackage = packageName;
outInfo.removedAppId = appId;
@@ -8427,17 +8749,18 @@
if (dataOnly) {
// Delete application data first
+ if (DEBUG_REMOVE) Slog.d(TAG, "Removing package data only");
removePackageDataLI(ps, outInfo, flags, writeSettings);
return true;
}
boolean ret = false;
if (isSystemApp(ps)) {
- Log.i(TAG, "Removing system package:" + ps.name);
+ if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package:" + ps.name);
// When an updated system application is deleted we delete the existing resources as well and
// fall back to existing code in system partition
ret = deleteSystemPackageLI(ps, flags, outInfo, writeSettings);
} else {
- Log.i(TAG, "Removing non-system package:" + ps.name);
+ if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package:" + ps.name);
// Kill application pre-emptively especially for apps on sd.
killApplication(packageName, ps.appId);
ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags, outInfo,
@@ -9408,7 +9731,15 @@
pw.print(" ");
pw.print(name);
pw.print(" -> ");
- pw.println(mSharedLibraries.get(name));
+ SharedLibraryEntry ent = mSharedLibraries.get(name);
+ if (ent.path != null) {
+ pw.print("(jar) ");
+ pw.print(ent.path);
+ } else {
+ pw.print("(apk) ");
+ pw.print(ent.apk);
+ }
+ pw.println();
}
}
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 13f514b..e645078 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -32,7 +32,6 @@
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
-import com.android.server.IntentResolver;
import com.android.server.pm.PackageManagerService.DumpState;
import org.xmlpull.v1.XmlPullParser;
@@ -2657,6 +2656,162 @@
ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
};
+ void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf,
+ Date date, List<UserInfo> users) {
+ pw.print(prefix); pw.print("Package [");
+ pw.print(ps.realName != null ? ps.realName : ps.name);
+ pw.print("] (");
+ pw.print(Integer.toHexString(System.identityHashCode(ps)));
+ pw.println("):");
+
+ if (ps.realName != null) {
+ pw.print(prefix); pw.print(" compat name=");
+ pw.println(ps.name);
+ }
+
+ pw.print(prefix); pw.print(" userId="); pw.print(ps.appId);
+ pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
+ if (ps.sharedUser != null) {
+ pw.print(prefix); pw.print(" sharedUser="); pw.println(ps.sharedUser);
+ }
+ pw.print(prefix); pw.print(" pkg="); pw.println(ps.pkg);
+ pw.print(prefix); pw.print(" codePath="); pw.println(ps.codePathString);
+ pw.print(prefix); pw.print(" resourcePath="); pw.println(ps.resourcePathString);
+ pw.print(prefix); pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
+ pw.print(prefix); pw.print(" versionCode="); pw.print(ps.versionCode);
+ if (ps.pkg != null) {
+ pw.print(" targetSdk="); pw.print(ps.pkg.applicationInfo.targetSdkVersion);
+ }
+ pw.println();
+ if (ps.pkg != null) {
+ pw.print(prefix); pw.print(" versionName="); pw.println(ps.pkg.mVersionName);
+ pw.print(prefix); pw.print(" applicationInfo=");
+ pw.println(ps.pkg.applicationInfo.toString());
+ pw.print(prefix); pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags,
+ FLAG_DUMP_SPEC); pw.println();
+ pw.print(prefix); pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
+ if (ps.pkg.mOperationPending) {
+ pw.print(prefix); pw.println(" mOperationPending=true");
+ }
+ pw.print(prefix); pw.print(" supportsScreens=[");
+ boolean first = true;
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("small");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("medium");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("large");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("xlarge");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("resizeable");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("anyDensity");
+ }
+ pw.println("]");
+ if (ps.pkg.libraryNames != null && ps.pkg.libraryNames.size() > 0) {
+ pw.print(prefix); pw.println(" libraries:");
+ for (int i=0; i<ps.pkg.libraryNames.size(); i++) {
+ pw.print(prefix); pw.print(" "); pw.println(ps.pkg.libraryNames.get(i));
+ }
+ }
+ if (ps.pkg.usesLibraries != null && ps.pkg.usesLibraries.size() > 0) {
+ pw.print(prefix); pw.println(" usesLibraries:");
+ for (int i=0; i<ps.pkg.usesLibraries.size(); i++) {
+ pw.print(prefix); pw.print(" "); pw.println(ps.pkg.usesLibraries.get(i));
+ }
+ }
+ if (ps.pkg.usesOptionalLibraries != null
+ && ps.pkg.usesOptionalLibraries.size() > 0) {
+ pw.print(prefix); pw.println(" usesOptionalLibraries:");
+ for (int i=0; i<ps.pkg.usesOptionalLibraries.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.println(ps.pkg.usesOptionalLibraries.get(i));
+ }
+ }
+ if (ps.pkg.usesLibraryFiles != null
+ && ps.pkg.usesLibraryFiles.length > 0) {
+ pw.print(prefix); pw.println(" usesLibraryFiles:");
+ for (int i=0; i<ps.pkg.usesLibraryFiles.length; i++) {
+ pw.print(prefix); pw.print(" "); pw.println(ps.pkg.usesLibraryFiles[i]);
+ }
+ }
+ }
+ pw.print(prefix); pw.print(" timeStamp=");
+ date.setTime(ps.timeStamp);
+ pw.println(sdf.format(date));
+ pw.print(prefix); pw.print(" firstInstallTime=");
+ date.setTime(ps.firstInstallTime);
+ pw.println(sdf.format(date));
+ pw.print(prefix); pw.print(" lastUpdateTime=");
+ date.setTime(ps.lastUpdateTime);
+ pw.println(sdf.format(date));
+ if (ps.installerPackageName != null) {
+ pw.print(prefix); pw.print(" installerPackageName=");
+ pw.println(ps.installerPackageName);
+ }
+ pw.print(prefix); pw.print(" signatures="); pw.println(ps.signatures);
+ pw.print(prefix); pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed);
+ pw.print(" haveGids="); pw.print(ps.haveGids);
+ pw.print(" installStatus="); pw.println(ps.installStatus);
+ pw.print(prefix); pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
+ pw.println();
+ for (UserInfo user : users) {
+ pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": ");
+ pw.print(" installed=");
+ pw.print(ps.getInstalled(user.id));
+ pw.print(" stopped=");
+ pw.print(ps.getStopped(user.id));
+ pw.print(" notLaunched=");
+ pw.print(ps.getNotLaunched(user.id));
+ pw.print(" enabled=");
+ pw.println(ps.getEnabled(user.id));
+ HashSet<String> cmp = ps.getDisabledComponents(user.id);
+ if (cmp != null && cmp.size() > 0) {
+ pw.print(prefix); pw.println(" disabledComponents:");
+ for (String s : cmp) {
+ pw.print(prefix); pw.print(" "); pw.println(s);
+ }
+ }
+ cmp = ps.getEnabledComponents(user.id);
+ if (cmp != null && cmp.size() > 0) {
+ pw.print(prefix); pw.println(" enabledComponents:");
+ for (String s : cmp) {
+ pw.print(prefix); pw.print(" "); pw.println(s);
+ }
+ }
+ }
+ if (ps.grantedPermissions.size() > 0) {
+ pw.print(prefix); pw.println(" grantedPermissions:");
+ for (String s : ps.grantedPermissions) {
+ pw.print(prefix); pw.print(" "); pw.println(s);
+ }
+ }
+ }
+
void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final Date date = new Date();
@@ -2678,123 +2833,7 @@
pw.println("Packages:");
printedSomething = true;
}
- pw.print(" Package [");
- pw.print(ps.realName != null ? ps.realName : ps.name);
- pw.print("] (");
- pw.print(Integer.toHexString(System.identityHashCode(ps)));
- pw.println("):");
-
- if (ps.realName != null) {
- pw.print(" compat name=");
- pw.println(ps.name);
- }
-
- pw.print(" userId="); pw.print(ps.appId);
- pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
- pw.print(" sharedUser="); pw.println(ps.sharedUser);
- pw.print(" pkg="); pw.println(ps.pkg);
- pw.print(" codePath="); pw.println(ps.codePathString);
- pw.print(" resourcePath="); pw.println(ps.resourcePathString);
- pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
- pw.print(" versionCode="); pw.println(ps.versionCode);
- if (ps.pkg != null) {
- pw.print(" applicationInfo="); pw.println(ps.pkg.applicationInfo.toString());
- pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println();
- pw.print(" versionName="); pw.println(ps.pkg.mVersionName);
- pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
- pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion);
- if (ps.pkg.mOperationPending) {
- pw.println(" mOperationPending=true");
- }
- pw.print(" supportsScreens=[");
- boolean first = true;
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("small");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("medium");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("large");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("xlarge");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("resizeable");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("anyDensity");
- }
- pw.println("]");
- }
- pw.print(" timeStamp=");
- date.setTime(ps.timeStamp);
- pw.println(sdf.format(date));
- pw.print(" firstInstallTime=");
- date.setTime(ps.firstInstallTime);
- pw.println(sdf.format(date));
- pw.print(" lastUpdateTime=");
- date.setTime(ps.lastUpdateTime);
- pw.println(sdf.format(date));
- if (ps.installerPackageName != null) {
- pw.print(" installerPackageName="); pw.println(ps.installerPackageName);
- }
- pw.print(" signatures="); pw.println(ps.signatures);
- pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed);
- pw.print(" haveGids="); pw.print(ps.haveGids);
- pw.print(" installStatus="); pw.println(ps.installStatus);
- pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
- pw.println();
- for (UserInfo user : users) {
- pw.print(" User "); pw.print(user.id); pw.print(": ");
- pw.print(" installed=");
- pw.print(ps.getInstalled(user.id));
- pw.print(" stopped=");
- pw.print(ps.getStopped(user.id));
- pw.print(" notLaunched=");
- pw.print(ps.getNotLaunched(user.id));
- pw.print(" enabled=");
- pw.println(ps.getEnabled(user.id));
- HashSet<String> cmp = ps.getDisabledComponents(user.id);
- if (cmp != null && cmp.size() > 0) {
- pw.println(" disabledComponents:");
- for (String s : cmp) {
- pw.print(" "); pw.println(s);
- }
- }
- cmp = ps.getEnabledComponents(user.id);
- if (cmp != null && cmp.size() > 0) {
- pw.println(" enabledComponents:");
- for (String s : cmp) {
- pw.print(" "); pw.println(s);
- }
- }
- }
- if (ps.grantedPermissions.size() > 0) {
- pw.println(" grantedPermissions:");
- for (String s : ps.grantedPermissions) {
- pw.print(" "); pw.println(s);
- }
- }
+ dumpPackageLPr(pw, " ", ps, sdf, date, users);
}
printedSomething = false;
@@ -2830,27 +2869,7 @@
pw.println("Hidden system packages:");
printedSomething = true;
}
- pw.print(" Package [");
- pw.print(ps.realName != null ? ps.realName : ps.name);
- pw.print("] (");
- pw.print(Integer.toHexString(System.identityHashCode(ps)));
- pw.println("):");
- if (ps.realName != null) {
- pw.print(" compat name=");
- pw.println(ps.name);
- }
- if (ps.pkg != null && ps.pkg.applicationInfo != null) {
- pw.print(" applicationInfo=");
- pw.println(ps.pkg.applicationInfo.toString());
- }
- pw.print(" userId=");
- pw.println(ps.appId);
- pw.print(" sharedUser=");
- pw.println(ps.sharedUser);
- pw.print(" codePath=");
- pw.println(ps.codePathString);
- pw.print(" resourcePath=");
- pw.println(ps.resourcePathString);
+ dumpPackageLPr(pw, " ", ps, sdf, date, users);
}
}
}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
index ac8ab1f..9f97311 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
@@ -33,6 +33,7 @@
private Path mPath;
private final Random mRandom = new Random();
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private final ArrayList<Path> mPathList = new ArrayList<Path>();
@Override
@@ -58,6 +59,19 @@
path.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
}
+ private static Path makeLargePath() {
+ Path path = new Path();
+ buildLargePath(path);
+ return path;
+ }
+
+ private static void buildLargePath(Path path) {
+ path.moveTo(0.0f, 0.0f);
+ path.cubicTo(0.0f, 0.0f, 10000.0f, 15000.0f, 10000.0f, 20000.0f);
+ path.cubicTo(10000.0f, 20000.0f, 5000.0f, 30000.0f, -8000.0f, 20000.0f);
+ path.cubicTo(-8000.0f, 20000.0f, 10000.0f, 20000.0f, 20000.0f, 0.0f);
+ }
+
public class PathsView extends View {
private final Paint mMediumPaint;
@@ -97,6 +111,9 @@
int r = mRandom.nextInt(10);
if (r == 5 || r == 3) {
mPathList.add(path);
+ } else if (r == 7) {
+ path = makeLargePath();
+ mPathList.add(path);
}
canvas.save();
diff --git a/tests/SharedLibrary/client/Android.mk b/tests/SharedLibrary/client/Android.mk
new file mode 100644
index 0000000..60ef92a
--- /dev/null
+++ b/tests/SharedLibrary/client/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_APK_LIBRARIES := SharedLibrary
+
+LOCAL_PACKAGE_NAME := SharedLibraryClient
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SharedLibrary/client/AndroidManifest.xml b/tests/SharedLibrary/client/AndroidManifest.xml
new file mode 100644
index 0000000..c6a43c0
--- /dev/null
+++ b/tests/SharedLibrary/client/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.lib_client">
+ <application android:label="@string/app_title">
+ <uses-library android:name="android.test.runner" />
+ <uses-library android:name="com.google.android.test.shared_library" />
+ <activity android:name="ActivityMain">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/SharedLibrary/client/res/values/strings.xml b/tests/SharedLibrary/client/res/values/strings.xml
new file mode 100644
index 0000000..3757a25
--- /dev/null
+++ b/tests/SharedLibrary/client/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<resources>
+ <string name="app_title">SharedLibrary client</string>
+</resources>
diff --git a/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java
new file mode 100644
index 0000000..d6121a5
--- /dev/null
+++ b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 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.google.android.test.lib_client;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+import com.google.android.test.shared_library.SharedLibraryMain;
+
+public class ActivityMain extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ TextView content = new TextView(this);
+ content.setText("Library version: " + SharedLibraryMain.getVersion(this) + "!");
+
+ SharedLibraryMain.ensureVersion(this, SharedLibraryMain.VERSION_BASE);
+ setContentView(content);
+ }
+}
diff --git a/tests/SharedLibrary/lib/Android.mk b/tests/SharedLibrary/lib/Android.mk
new file mode 100644
index 0000000..c19e23a
--- /dev/null
+++ b/tests/SharedLibrary/lib/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SharedLibrary
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SharedLibrary/lib/AndroidManifest.xml b/tests/SharedLibrary/lib/AndroidManifest.xml
new file mode 100644
index 0000000..31fac20
--- /dev/null
+++ b/tests/SharedLibrary/lib/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.shared_library"
+ android:versionCode="2">
+ <application android:label="SharedLibrary">
+ <library android:name="com.google.android.test.shared_library" />
+ <activity android:name="ActivityMain">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/SharedLibrary/lib/res/values/strings.xml b/tests/SharedLibrary/lib/res/values/strings.xml
new file mode 100644
index 0000000..bbfb0b4
--- /dev/null
+++ b/tests/SharedLibrary/lib/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="upgrade_title">Upgrade required</string>
+ <string name="upgrade_body"><xliff:g id="app">%1$s</xliff:g> requires a newer version
+ of <xliff:g id="lib">%2$s</xliff:g> to run.</string>
+ <string name="upgrade_button">Upgrade</string>
+</resources>
diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java
new file mode 100644
index 0000000..895aced
--- /dev/null
+++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 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.google.android.test.shared_library;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class ActivityMain extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ TextView content = new TextView(this);
+ content.setText("Dummy main entry for this apk; not really needed...");
+ setContentView(content);
+ }
+}
diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java
new file mode 100644
index 0000000..c1cd925
--- /dev/null
+++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 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.google.android.test.shared_library;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+
+public class SharedLibraryMain {
+ private static String LIBRARY_PACKAGE = "com.google.android.test.shared_library";
+
+ /**
+ * Base version of the library.
+ */
+ public static int VERSION_BASE = 1;
+
+ /**
+ * The second version of the library.
+ */
+ public static int VERSION_SECOND = 2;
+
+ public static int getVersion(Context context) {
+ PackageInfo pi = null;
+ try {
+ pi = context.getPackageManager().getPackageInfo(LIBRARY_PACKAGE, 0);
+ return pi.versionCode;
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException("Can't find my package!", e);
+ }
+ }
+
+ public static void ensureVersion(Activity activity, int minVersion) {
+ if (getVersion(activity) >= minVersion) {
+ return;
+ }
+
+ // The current version of the library does not meet the required version. Show
+ // a dialog to inform the user and have them update to the current version.
+ // Note that updating the library will be necessity mean killing the current
+ // application (so it can be re-started with the new version, so there is no
+ // reason to return a result here.
+ final Context context;
+ try {
+ context = activity.createPackageContext(LIBRARY_PACKAGE, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException("Can't find my package!", e);
+ }
+
+ // Display the dialog. Note that we don't need to deal with activity lifecycle
+ // stuff because if the activity gets recreated, it will first call through to
+ // ensureVersion(), causing us to either re-display the dialog if needed or let
+ // it now proceed.
+ final Resources res = context.getResources();
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setTitle(res.getText(R.string.upgrade_title));
+ builder.setMessage(res.getString(R.string.upgrade_body,
+ activity.getApplicationInfo().loadLabel(activity.getPackageManager()),
+ context.getApplicationInfo().loadLabel(context.getPackageManager())));
+ builder.setPositiveButton(res.getText(R.string.upgrade_button),
+ new Dialog.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Launch play store.
+ }
+ });
+ builder.show();
+ }
+}