Merge "Implement limited shared libraries in apks." into jb-mr2-dev
diff --git a/api/current.txt b/api/current.txt
index 3132a91..4eb6a0b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10046,16 +10046,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 +10081,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 +10097,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 +10161,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/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..3c231e5 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>(
+ sFullSensorsList.size()*2);
+ sTriggerEventPool = new Pools.SynchronizedPool<TriggerEvent>(
+ sFullSensorsList.size()*2);
}
}
}
@@ -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..82b8907
--- /dev/null
+++ b/core/java/android/hardware/TriggerEventListener.java
@@ -0,0 +1,79 @@
+/*
+ * 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 {
+ * @Override
+ * 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/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/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/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/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/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 8576948b..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/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..afdc2c9 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -31,69 +31,32 @@
// 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 {
+ 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 +64,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 +127,18 @@
} 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();
+ texture->clearTask();
texture = NULL;
mCache.remove(entry);
}
@@ -189,6 +152,10 @@
}
void PathCache::precache(SkPath* path, SkPaint* paint) {
+ if (!Caches::getInstance().tasks.canRunTasks()) {
+ return;
+ }
+
path = getSourcePath(path);
PathCacheEntry entry(path, paint);
@@ -205,7 +172,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 +184,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..58fea08 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;
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/AudioService.java b/media/java/android/media/AudioService.java
index 882a635..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();
}
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/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 6ba5cff..01f7544 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1243,6 +1243,10 @@
unbindCurrentMethodLocked(false, false);
return;
}
+ // 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 (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
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();