Merge change 279 into donut

* changes:
  Add sketch gesture demo application.
diff --git a/location/java/android/location/ILocationProvider.aidl b/location/java/android/location/ILocationProvider.aidl
index 08b9246..d43fd6e 100644
--- a/location/java/android/location/ILocationProvider.aidl
+++ b/location/java/android/location/ILocationProvider.aidl
@@ -46,9 +46,9 @@
     void setMinTime(long minTime);
     void updateNetworkState(int state);
     boolean sendExtraCommand(String command, inout Bundle extras);
+    void addListener(int uid);
+    void removeListener(int uid);
 
-    /* the following are only used for NetworkLocationProvider */
+    /* the following is used only for NetworkLocationProvider */
     void updateCellLockStatus(boolean acquired);
-    void addListener(in String[] applications);
-    void removeListener(in String[] applications);
 }
diff --git a/location/java/android/location/LocationProviderImpl.java b/location/java/android/location/LocationProviderImpl.java
index 2a9199e..fa0cd2d 100644
--- a/location/java/android/location/LocationProviderImpl.java
+++ b/location/java/android/location/LocationProviderImpl.java
@@ -249,4 +249,20 @@
     public boolean sendExtraCommand(String command, Bundle extras) {
         return false;
     }
+
+    /**
+     * Informs the location provider when a new client is listening for location information
+     *
+     * @param uid the uid of the client proces
+     */
+    public void addListener(int uid) {
+    }
+
+    /**
+     * Informs the location provider when a client is no longerlistening for location information
+     *
+     * @param uid the uid of the client proces
+     */
+    public void removeListener(int uid) {
+    }
 }
diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java
index d56594e..f133b4f 100644
--- a/location/java/com/android/internal/location/GpsLocationProvider.java
+++ b/location/java/com/android/internal/location/GpsLocationProvider.java
@@ -33,10 +33,13 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.util.Config;
 import android.util.Log;
+import android.util.SparseIntArray;
 
+import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyIntents;
 
@@ -192,7 +195,10 @@
     private boolean mSetSuplServer;
     private String mSuplApn;
     private int mSuplDataConnectionState;
-    private ConnectivityManager mConnMgr;
+    private final ConnectivityManager mConnMgr;
+
+    private final IBatteryStats mBatteryStats;
+    private final SparseIntArray mClientUids = new SparseIntArray();
 
     // how often to request NTP time, in milliseconds
     // current setting 4 hours
@@ -241,6 +247,9 @@
 
         mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
 
+        // Battery statistics service to be notified when GPS turns on or off
+        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+
         mProperties = new Properties();
         try {
             File file = new File(PROPERTIES_FILE);
@@ -568,6 +577,30 @@
     }
 
     @Override
+    public void addListener(int uid) {
+        mClientUids.put(uid, 0);
+        if (mNavigating) {
+            try {
+                mBatteryStats.noteStartGps(uid);
+            } catch (RemoteException e) {
+                Log.w(TAG, "RemoteException in addListener");
+            }
+        }
+    }
+
+    @Override
+    public void removeListener(int uid) {
+        mClientUids.delete(uid);
+        if (mNavigating) {
+            try {
+                mBatteryStats.noteStopGps(uid);
+            } catch (RemoteException e) {
+                Log.w(TAG, "RemoteException in removeListener");
+            }
+        }
+    }
+
+    @Override
     public boolean sendExtraCommand(String command, Bundle extras) {
         
         if ("delete_aiding_data".equals(command)) {
@@ -746,6 +779,20 @@
                 }
             }
 
+            try {
+                // update battery stats
+                for (int i=mClientUids.size() - 1; i >= 0; i--) {
+                    int uid = mClientUids.keyAt(i);
+                    if (mNavigating) {
+                        mBatteryStats.noteStartGps(uid);
+                    } else {
+                        mBatteryStats.noteStopGps(uid);
+                    }
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, "RemoteException in reportStatus");
+            }
+
             // send an intent to notify that the GPS has been enabled or disabled.
             Intent intent = new Intent(GPS_ENABLED_CHANGE_ACTION);
             intent.putExtra(EXTRA_ENABLED, mNavigating);
diff --git a/location/java/com/android/internal/location/LocationProviderProxy.java b/location/java/com/android/internal/location/LocationProviderProxy.java
index 5b5b27a..84462b2 100644
--- a/location/java/com/android/internal/location/LocationProviderProxy.java
+++ b/location/java/com/android/internal/location/LocationProviderProxy.java
@@ -230,17 +230,17 @@
         }
     }
 
-    public void addListener(String[] applications) {
+    public void addListener(int uid) {
         try {
-            mProvider.addListener(applications);
+            mProvider.addListener(uid);
         } catch (RemoteException e) {
             Log.e(TAG, "addListener failed", e);
         }
     }
 
-    public void removeListener(String[] applications) {
+    public void removeListener(int uid) {
         try {
-            mProvider.removeListener(applications);
+            mProvider.removeListener(uid);
         } catch (RemoteException e) {
             Log.e(TAG, "removeListener failed", e);
         }
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 4196ef3..3cd841d 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -32,22 +32,24 @@
  * It allows to stream PCM audio buffers to the audio hardware for playback. This is
  * achieved by "pushing" the data to the AudioTrack object using one of the
  *  {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods.
- * <p>An AudioTrack instance can operate under two modes: static of streaming.<br>
- * The Streaming mode consists in continuously writing data to the AudioTrack, using one
- * of the write() methods. These are blocking and return when the data has been transferred
- * from the Java layer to the native layer, and is queued for playback. The streaming mode
+ *  
+ * <p>An AudioTrack instance can operate under two modes: static or streaming.<br>
+ * In Streaming mode, the application writes a continuous stream of data to the AudioTrack, using
+ * one of the write() methods. These are blocking and return when the data has been transferred
+ * from the Java layer to the native layer and queued for playback. The streaming mode
  *  is most useful when playing blocks of audio data that for instance are:
  * <ul>
  *   <li>too big to fit in memory because of the duration of the sound to play,</li>
  *   <li>too big to fit in memory because of the characteristics of the audio data
  *         (high sampling rate, bits per sample ...)</li>
- *   <li>chosen, received or generated as the audio keeps playing.</li>
+ *   <li>received or generated while previously queued audio is playing.</li>
  * </ul>
  * The static mode is to be chosen when dealing with short sounds that fit in memory and
- * that need to be played with the smallest latency possible. Static mode AudioTrack instances can
- * play the sound without the need to transfer the audio data from Java to the audio hardware
+ * that need to be played with the smallest latency possible. AudioTrack instances in static mode
+ * can play the sound without the need to transfer the audio data from Java to native layer
  * each time the sound is to be played. The static mode will therefore be preferred for UI and
  * game sounds that are played often, and with the smallest overhead possible.
+ * 
  * <p>Upon creation, an AudioTrack object initializes its associated audio buffer.
  * The size of this buffer, specified during the construction, determines how long an AudioTrack
  * can play before running out of data.<br>
@@ -66,11 +68,11 @@
     /** Maximum value for a channel volume */
     private static final float VOLUME_MAX = 1.0f;
 
-    /** state of an AudioTrack this is stopped */
+    /** indicates AudioTrack state is stopped */
     public static final int PLAYSTATE_STOPPED = 1;  // matches SL_PLAYSTATE_STOPPED
-    /** state of an AudioTrack this is paused */
+    /** indicates AudioTrack state is paused */
     public static final int PLAYSTATE_PAUSED  = 2;  // matches SL_PLAYSTATE_PAUSED
-    /** state of an AudioTrack this is playing */
+    /** indicates AudioTrack state is playing */
     public static final int PLAYSTATE_PLAYING = 3;  // matches SL_PLAYSTATE_PLAYING
 
     /**
@@ -85,7 +87,7 @@
     public static final int MODE_STREAM = 1;
 
     /**
-     * State of an AudioTrack that was not successfully initialized upon creation
+     * State of an AudioTrack that was not successfully initialized upon creation.
      */
     public static final int STATE_UNINITIALIZED = 0;
     /**
@@ -126,11 +128,11 @@
     // Events:
     // to keep in sync with frameworks/base/include/media/AudioTrack.h
     /**
-     * Event id for when the playback head has reached a previously set marker.
+     * Event id denotes when playback head has reached a previously set marker.
      */
     private static final int NATIVE_EVENT_MARKER  = 3;
     /**
-     * Event id for when the previously set update period has passed during playback.
+     * Event id denotes when previously set update period has elapsed during playback.
      */
     private static final int NATIVE_EVENT_NEW_POS = 4;
 
@@ -141,11 +143,11 @@
     // Member variables
     //--------------------
     /**
-     * Indicates the state of the AudioTrack instance
+     * Indicates the state of the AudioTrack instance.
      */
     private int mState = STATE_UNINITIALIZED;
     /**
-     * Indicates the play state of the AudioTrack instance
+     * Indicates the play state of the AudioTrack instance.
      */
     private int mPlayState = PLAYSTATE_STOPPED;
     /**
@@ -159,7 +161,7 @@
      */
     private OnPlaybackPositionUpdateListener mPositionListener = null;
     /**
-     * Lock to protect event listener updates against event notifications
+     * Lock to protect event listener updates against event notifications.
      */
     private final Object mPositionListenerLock = new Object();
     /**
@@ -167,11 +169,11 @@
      */
     private int mNativeBufferSizeInBytes = 0;
     /**
-     * Handler for marker events coming from the native code
+     * Handler for marker events coming from the native code.
      */
     private NativeEventHandlerDelegate mEventHandlerDelegate = null;
     /**
-     * Looper associated with the thread that creates the AudioTrack instance
+     * Looper associated with the thread that creates the AudioTrack instance.
      */
     private Looper mInitializationLooper = null;
     /**
@@ -179,7 +181,7 @@
      */
     private int mSampleRate = 22050;
     /**
-     * The number of input audio channels (1 is mono, 2 is stereo)
+     * The number of input audio channels (1 is mono, 2 is stereo).
      */
     private int mChannelCount = 1;
     /**
@@ -194,7 +196,7 @@
      */
     private int mDataLoadMode = MODE_STREAM;
     /**
-     * The current audio channel configuration
+     * The current audio channel configuration.
      */
     private int mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
     /**
@@ -209,7 +211,7 @@
     // Used exclusively by native code
     //--------------------
     /**
-     * Accessed by native methods: provides access to C++ AudioTrack object
+     * Accessed by native methods: provides access to C++ AudioTrack object.
      */
     @SuppressWarnings("unused")
     private int mNativeTrackInJavaObj;
@@ -227,17 +229,14 @@
     /**
      * Class constructor.
      * @param streamType the type of the audio stream. See
-
      *   {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM},
      *   {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC} and
      *   {@link AudioManager#STREAM_ALARM}
      * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but
      *   not limited to) 44100, 22050 and 11025.
      * @param channelConfig describes the configuration of the audio channels.
-
      *   See {@link AudioFormat#CHANNEL_CONFIGURATION_MONO} and
      *   {@link AudioFormat#CHANNEL_CONFIGURATION_STEREO}
-
      * @param audioFormat the format in which the audio data is represented.
      *   See {@link AudioFormat#ENCODING_PCM_16BIT} and
      *   {@link AudioFormat#ENCODING_PCM_8BIT}
@@ -245,6 +244,9 @@
      *   from for playback. If using the AudioTrack in streaming mode, you can write data into
      *   this buffer in smaller chunks than this size. If using the AudioTrack in static mode,
      *   this is the maximum size of the sound that will be played for this instance.
+     *   See {@link #getMinBufferSize(int, int, int)} to determine the minimum required buffer size
+     *   for the successful creation of an AudioTrack instance in streaming mode. Using values
+     *   smaller than getMinBufferSize() will result in an initialization failure.
      * @param mode streaming or static buffer. See {@link #MODE_STATIC} and {@link #MODE_STREAM}
      * @throws java.lang.IllegalArgumentException
      */
@@ -423,8 +425,8 @@
     }
     
     /**
-     * Returns the current playback rate in Hz. Note that this rate may differ from one set using
-     * {@link #setPlaybackRate(int)} as the value effectively set is implementation-dependent.
+     * Returns the current playback rate in Hz. Note that this rate may differ from the one set
+     * with {@link #setPlaybackRate(int)} as the value effectively used is implementation-dependent.
      */
     public int getPlaybackRate() {
         return native_get_playback_rate();
@@ -470,6 +472,9 @@
      * AudioTrack instance has been created to check if it was initialized
      * properly. This ensures that the appropriate hardware resources have been
      * acquired.
+     * @see #STATE_INITIALIZED
+     * @see #STATE_NO_STATIC_DATA
+     * @see #STATE_UNINITIALIZED
      */
     public int getState() {
         return mState;
@@ -486,28 +491,28 @@
     }
 
     /**
-     *  Returns the native frame count used by the hardware
+     *  Returns the native frame count used by the hardware.
      */
     protected int getNativeFrameCount() {
         return native_get_native_frame_count();
     }
 
     /**
-     * @return marker position in frames
+     * Returns marker position expressed in frames.
      */
     public int getNotificationMarkerPosition() {
         return native_get_marker_pos();
     }
 
     /**
-     * @return update period in frames
+     * Returns the notification update period expressed in frames.
      */
     public int getPositionNotificationPeriod() {
         return native_get_pos_update_period();
     }
 
     /**
-     * @return playback head position in frames
+     * Returns the playback head position expressed in frames
      */
     public int getPlaybackHeadPosition() {
         return native_get_position();
@@ -522,7 +527,9 @@
     
     /**
      * Returns the minimum buffer size required for the successful creation of an AudioTrack
-     * object to be created in the {@link #MODE_STREAM} mode.
+     * object to be created in the {@link #MODE_STREAM} mode. Note that this size doesn't
+     * guarantee a smooth playback under load, and higher values should be chosen according to
+     * the expected frequency at which the buffer will be refilled with additional data to play. 
      * @param sampleRateInHz the sample rate expressed in Hertz.
      * @param channelConfig describes the configuration of the audio channels. 
      *   See {@link AudioFormat#CHANNEL_CONFIGURATION_MONO} and
@@ -533,7 +540,7 @@
      * @return {@link #ERROR_BAD_VALUE} if an invalid parameter was passed,
      *   or {@link #ERROR} if the implementation was unable to query the hardware for its output 
      *     properties, 
-     *   or the minimum buffer size expressed  in number of bytes.
+     *   or the minimum buffer size expressed in bytes.
      */
     static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
         int channelCount = 0;
@@ -577,13 +584,22 @@
     /**
      * Sets the listener the AudioTrack notifies when a previously set marker is reached or
      * for each periodic playback head position update.
+     * Notifications will be received in the same thread as the one in which the AudioTrack
+     * instance was created.
      * @param listener
      */
     public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener listener) {
         setPlaybackPositionUpdateListener(listener, null);
     }
     
-
+    /**
+     * Sets the listener the AudioTrack notifies when a previously set marker is reached or
+     * for each periodic playback head position update.
+     * Use this method to receive AudioTrack events in the Handler associated with another
+     * thread than the one in which you created the AudioTrack instance.
+     * @param listener
+     * @param handler the Handler that will receive the event notification messages.
+     */
     public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener listener, 
                                                     Handler handler) {
         synchronized (mPositionListenerLock) {
@@ -636,13 +652,17 @@
      * the audio data will be consumed and played back, not the original sampling rate of the
      * content. Setting it to half the sample rate of the content will cause the playback to
      * last twice as long, but will also result result in a negative pitch shift.
-     * The current implementation supports a maximum sample rate of twice the hardware output
-     * sample rate (see {@link #getNativeOutputSampleRate(int)}). Use {@link #getSampleRate()} to
-     * check the rate actually used in hardware after potential clamping.
-     * @param sampleRateInHz
+     * The current implementation supports a maximum sample rate of 64kHz.
+     * Use {@link #getSampleRate()} to check the rate actually used in hardware after 
+     * potential clamping.
+     * @param sampleRateInHz the sample rate expressed in Hz
      * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
      *    {@link #ERROR_INVALID_OPERATION}
      */
+    // FIXME: the implementation should support twice the hardware output sample rate
+    //   (see {@link #getNativeOutputSampleRate(int)}), but currently
+    //  due to the representation of the sample rate in the native layer, the sample rate
+    //  is limited to 65535Hz
     public int setPlaybackRate(int sampleRateInHz) {
         if (mState != STATE_INITIALIZED) {
             return ERROR_INVALID_OPERATION;
@@ -656,7 +676,7 @@
 
 
     /**
-     *
+     * Sets the position of the notification marker.
      * @param markerInFrames marker in frames
      * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
      *  {@link #ERROR_INVALID_OPERATION}
@@ -670,7 +690,8 @@
 
 
     /**
-     * @param periodInFrames update period in frames
+     * Sets the period for the periodic notification event.
+     * @param periodInFrames update period expressed in frames
      * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_INVALID_OPERATION}
      */
     public int setPositionNotificationPeriod(int periodInFrames) {
@@ -683,7 +704,7 @@
 
     /**
      * Sets the playback head position. The track must be stopped for the position to be changed.
-     * @param positionInFrames playback head position in frames
+     * @param positionInFrames playback head position expressed in frames
      * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
      *    {@link #ERROR_INVALID_OPERATION}
      */
@@ -699,8 +720,8 @@
 
     /**
      * Sets the loop points and the loop count. The loop can be infinite.
-     * @param startInFrames loop start marker in frames
-     * @param endInFrames loop end marker in frames
+     * @param startInFrames loop start marker expressed in frames
+     * @param endInFrames loop end marker expressed in frames
      * @param loopCount the number of times the loop is looped.
      *    A value of -1 means infinite looping.
      * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
@@ -797,7 +818,8 @@
     /**
      * Writes the audio data to the audio hardware for playback.
      * @param audioData the array that holds the data to play.
-     * @param offsetInBytes the offset in audioData where the data to play starts.
+     * @param offsetInBytes the offset expressed in bytes in audioData where the data to play 
+     *    starts.
      * @param sizeInBytes the number of bytes to read in audioData after the offset.
      * @return the number of bytes that were written or {@link #ERROR_INVALID_OPERATION}
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
@@ -827,7 +849,8 @@
     /**
      * Writes the audio data to the audio hardware for playback.
      * @param audioData the array that holds the data to play.
-     * @param offsetInShorts the offset in audioData where the data to play starts.
+     * @param offsetInShorts the offset expressed in shorts in audioData where the data to play
+     *     starts.
      * @param sizeInShorts the number of bytes to read in audioData after the offset.
      * @return the number of shorts that were written or {@link #ERROR_INVALID_OPERATION}
       *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index ab3274b..b13c2e6 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -46,6 +46,19 @@
  * number of streams helps to cap CPU loading and reducing the likelihood that
  * audio mixing will impact visuals or UI performance.</p> 
  *
+ * <p>Sounds can be looped by setting a non-zero loop value. A value of -1
+ * causes the sound to loop forever. In this case, the application must 
+ * explicitly call the stop() function to stop the sound. Any other non-zero
+ * value will cause the sound to repeat the specified number of times, e.g.
+ * a value of 3 causes the sound to play a total of 4 times.</p>
+ *
+ * <p>The playback rate can also be changed. A playback rate of 1.0 causes
+ * the sound to play at its original frequency (resampled, if necessary,
+ * to the hardware output frequency). A playback rate of 2.0 causes the
+ * sound to play at twice its original frequency, and a playback rate of
+ * 0.5 causes it to play at half its original frequency. The playback
+ * rate range is 0.5 to 2.0.</p>
+ *
  * <p>Priority runs low to high, i.e. higher numbers are higher priority.
  * Priority is used when a call to play() would cause the number of active
  * streams to exceed the value established by the maxStreams parameter when
@@ -72,6 +85,13 @@
  * adjusting the playback rate in real-time for doppler or synthesis
  * effects.</p>
  *
+ * <p>Note that since streams can be stopped due to resource constraints, the
+ * streamID is a reference to a particular instance of a stream. If the stream
+ * is stopped to allow a higher priority stream to play, the stream is no
+ * longer be valid. However, the application is allowed to call methods on
+ * the streamID without error. This may help simplify program logic since
+ * the application need not concern itself with the stream lifecycle.</p>
+ *
  * <p>In our example, when the player has completed the level, the game
  * logic should call SoundPool.release() to release all the native resources
  * in use and then set the SoundPool reference to null. If the player starts
@@ -104,10 +124,11 @@
     }
 
     /**
-     * Load the sound from the specified path
-     * 
+     * Load the sound from the specified path.
+     *
      * @param path the path to the audio file
-     * @param priority the priority of the sound. Currently has no effect.
+     * @param priority the priority of the sound. Currently has no effect. Use
+     *                 a value of 1 for future compatibility.
      * @return a sound ID. This value can be used to play or unload the sound.
      */
     public int load(String path, int priority)
@@ -133,17 +154,18 @@
     }
 
     /**
-     * Load the sound from the specified APK resource
+     * Load the sound from the specified APK resource.
      *
-     * <p>Note that the extension is dropped. For example, if you want to load
+     * Note that the extension is dropped. For example, if you want to load
      * a sound from the raw resource file "explosion.mp3", you would specify
      * "R.raw.explosion" as the resource ID. Note that this means you cannot
      * have both an "explosion.wav" and an "explosion.mp3" in the res/raw
-     * directory.</p>
+     * directory.
      * 
      * @param context the application context
      * @param resId the resource ID
-     * @param priority the priority of the sound. Currently has no effect.
+     * @param priority the priority of the sound. Currently has no effect. Use
+     *                 a value of 1 for future compatibility.
      * @return a sound ID. This value can be used to play or unload the sound.
      */
     public int load(Context context, int resId, int priority) {
@@ -162,10 +184,11 @@
     }
 
     /**
-     * Load the sound from an asset file descriptor
+     * Load the sound from an asset file descriptor.
      *
      * @param afd an asset file descriptor
-     * @param priority the priority of the sound. Currently has no effect.
+     * @param priority the priority of the sound. Currently has no effect. Use
+     *                 a value of 1 for future compatibility.
      * @return a sound ID. This value can be used to play or unload the sound.
      */
     public int load(AssetFileDescriptor afd, int priority) {
@@ -181,16 +204,17 @@
     }
 
     /**
-     * Load the sound from a FileDescriptor
+     * Load the sound from a FileDescriptor.
      *
-     * <p>This version is useful if you store multiple sounds in a single
+     * This version is useful if you store multiple sounds in a single
      * binary. The offset specifies the offset from the start of the file
-     * and the length specifies the length of the sound within the file.</p>
+     * and the length specifies the length of the sound within the file.
      *
      * @param fd a FileDescriptor object
      * @param offset offset to the start of the sound
      * @param length length of the sound
-     * @param priority the priority of the sound. Currently has no effect.
+     * @param priority the priority of the sound. Currently has no effect. Use
+     *                 a value of 1 for future compatibility.
      * @return a sound ID. This value can be used to play or unload the sound.
      */
     public int load(FileDescriptor fd, long offset, long length, int priority) {
@@ -202,11 +226,11 @@
     private native final int _load(FileDescriptor fd, long offset, long length, int priority);
 
     /**
-     * Unload a sound from a sound ID
+     * Unload a sound from a sound ID.
      *
-     * <p>Unloads the sound specified by the soundID. This is the value
+     * Unloads the sound specified by the soundID. This is the value
      * returned by the load() function. Returns true if the sound is
-     * successfully unloaded, false if the sound was already unloaded.</p>
+     * successfully unloaded, false if the sound was already unloaded.
      *
      * @param soundID a soundID returned by the load() function
      * @return true if just unloaded, false if previously unloaded
@@ -214,66 +238,77 @@
     public native final boolean unload(int soundID);
 
     /**
-     * Play a sound from a sound ID
+     * Play a sound from a sound ID.
      *
-     * <p>Play the sound specified by the soundID. This is the value 
+     * Play the sound specified by the soundID. This is the value 
      * returned by the load() function. Returns a non-zero streamID
      * if successful, zero if it fails. The streamID can be used to
      * further control playback. Note that calling play() may cause
      * another sound to stop playing if the maximum number of active
-     * streams is exceeded.</p>
+     * streams is exceeded. A loop value of -1 means loop forever,
+     * a value of 0 means don't loop, other values indicate the
+     * number of repeats, e.g. a value of 1 plays the audio twice.
+     * The playback rate allows the application to vary the playback
+     * rate (pitch) of the sound. A value of 1.0 means play back at
+     * the original frequency. A value of 2.0 means play back twice
+     * as fast, and a value of 0.5 means playback at half speed.
      *
      * @param soundID a soundID returned by the load() function
+     * @param leftVolume left volume value (range = 0.0 to 1.0)
+     * @param rightVolume right volume value (range = 0.0 to 1.0)
+     * @param priority stream priority (0 = lowest priority)
+     * @param loop loop mode (0 = no loop, -1 = loop forever)
+     * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
      * @return non-zero streamID if successful, zero if failed
      */
     public native final int play(int soundID, float leftVolume, float rightVolume,
             int priority, int loop, float rate);
 
     /**
-     * Pause a playback stream
+     * Pause a playback stream.
      *
-     * <p>Pause the stream specified by the streamID. This is the
+     * Pause the stream specified by the streamID. This is the
      * value returned by the play() function. If the stream is
      * playing, it will be paused. If the stream is not playing
      * (e.g. is stopped or was previously paused), calling this
-     * function will have no effect.</p>
+     * function will have no effect.
      *
      * @param streamID a streamID returned by the play() function
      */
     public native final void pause(int streamID);
 
     /**
-     * Resume a playback stream
+     * Resume a playback stream.
      *
-     * <p>Resume the stream specified by the streamID. This
+     * Resume the stream specified by the streamID. This
      * is the value returned by the play() function. If the stream
      * is paused, this will resume playback. If the stream was not
-     * previously paused, calling this function will have no effect.</p>
+     * previously paused, calling this function will have no effect.
      *
      * @param streamID a streamID returned by the play() function
      */
     public native final void resume(int streamID);
 
     /**
-     * Stop a playback stream
+     * Stop a playback stream.
      *
-     * <p>Stop the stream specified by the streamID. This
+     * Stop the stream specified by the streamID. This
      * is the value returned by the play() function. If the stream
      * is playing, it will be stopped. It also releases any native
      * resources associated with this stream. If the stream is not
-     * playing, it will have no effect.</p>
+     * playing, it will have no effect.
      *
      * @param streamID a streamID returned by the play() function
      */
     public native final void stop(int streamID);
 
     /**
-     * Set stream volume
+     * Set stream volume.
      *
-     * <p>Sets the volume on the stream specified by the streamID.
+     * Sets the volume on the stream specified by the streamID.
      * This is the value returned by the play() function. The
      * value must be in the range of 0.0 to 1.0. If the stream does
-     * not exist, it will have no effect.</p>
+     * not exist, it will have no effect.
      *
      * @param streamID a streamID returned by the play() function
      * @param leftVolume left volume value (range = 0.0 to 1.0)
@@ -283,29 +318,51 @@
             float leftVolume, float rightVolume);
 
     /**
-     * Change stream priority
+     * Change stream priority.
      *
-     * <p>Change the priority of the stream specified by the streamID.
+     * Change the priority of the stream specified by the streamID.
      * This is the value returned by the play() function. Affects the
-     * order in which streams are re-used to play new sounds.
+     * order in which streams are re-used to play new sounds. If the
+     * stream does not exist, it will have no effect.
      *
      * @param streamID a streamID returned by the play() function
      */
     public native final void setPriority(int streamID, int priority);
 
     /**
-     * Change stream priority
+     * Set loop mode.
      *
-     * <p>Change the priority of the stream specified by the streamID.
-     * This is the value returned by the play() function. Affects the
-     * order in which streams are re-used to play new sounds.
+     * Change the loop mode. A loop value of -1 means loop forever,
+     * a value of 0 means don't loop, other values indicate the
+     * number of repeats, e.g. a value of 1 plays the audio twice.
+     * If the stream does not exist, it will have no effect.
      *
      * @param streamID a streamID returned by the play() function
+     * @param loop loop mode (0 = no loop, -1 = loop forever)
      */
     public native final void setLoop(int streamID, int loop);
 
+    /**
+     * Change playback rate.
+     *
+     * The playback rate allows the application to vary the playback
+     * rate (pitch) of the sound. A value of 1.0 means playback at
+     * the original frequency. A value of 2.0 means playback twice
+     * as fast, and a value of 0.5 means playback at half speed.
+     * If the stream does not exist, it will have no effect.
+     *
+     * @param streamID a streamID returned by the play() function
+     * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
+     */
     public native final void setRate(int streamID, float rate);
 
+    /**
+     * Release the SoundPool resources.
+     *
+     * Release all memory and native resources used by the SoundPool
+     * object. The SoundPool can no longer be used and the reference
+     * should be set to null.
+     */
     public native final void release();
 
     private native final void native_setup(Object mediaplayer_this,
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 75b59ee..c91e21c 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -73,7 +73,6 @@
 import android.util.PrintWriterPrinter;
 import android.util.SparseIntArray;
 
-import com.android.internal.app.IBatteryStats;
 import com.android.internal.location.GpsLocationProvider;
 import com.android.internal.location.LocationProviderProxy;
 import com.android.internal.location.MockProvider;
@@ -152,34 +151,15 @@
     private boolean mWifiWakeLockAcquired = false;
     private boolean mCellWakeLockAcquired = false;
     
-    private final IBatteryStats mBatteryStats;
-    
     /**
-     * Mapping from listener IBinder/PendingIntent to local Listener wrappers.
+     * List of all receivers.
      */
-    private final ArrayList<Receiver> mListeners = new ArrayList<Receiver>();
+    private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>();
 
     /**
-     * Used for reporting which UIDs are causing the GPS to run.
+     * Object used internally for synchronization
      */
-    private final SparseIntArray mReportedGpsUids = new SparseIntArray();
-    private int mReportedGpsSeq = 0;
-    
-    /**
-     * Mapping from listener IBinder/PendingIntent to a map from provider name to UpdateRecord.
-     * This also serves as the lock for our state.
-     */
-    private final HashMap<Receiver,HashMap<String,UpdateRecord>> mLocationListeners =
-        new HashMap<Receiver,HashMap<String,UpdateRecord>>();
-
-    /**
-     * Mapping from listener IBinder/PendingIntent to a map from provider name to last broadcast
-     * location.
-     */
-    private final HashMap<Receiver,HashMap<String,Location>> mLastFixBroadcast =
-        new HashMap<Receiver,HashMap<String,Location>>();
-    private final HashMap<Receiver,HashMap<String,Long>> mLastStatusBroadcast =
-        new HashMap<Receiver,HashMap<String,Long>>();
+    private final Object mLock = new Object();
 
     /**
      * Mapping from provider name to all its UpdateRecords
@@ -219,20 +199,18 @@
     private final class Receiver implements IBinder.DeathRecipient {
         final ILocationListener mListener;
         final PendingIntent mPendingIntent;
-        final int mUid;
         final Object mKey;
+        final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>();
 
-        Receiver(ILocationListener listener, int uid) {
+        Receiver(ILocationListener listener) {
             mListener = listener;
             mPendingIntent = null;
-            mUid = uid;
             mKey = listener.asBinder();
         }
 
-        Receiver(PendingIntent intent, int uid) {
+        Receiver(PendingIntent intent) {
             mPendingIntent = intent;
             mListener = null;
-            mUid = uid;
             mKey = intent;
         }
 
@@ -256,11 +234,11 @@
             if (mListener != null) {
                 return "Receiver{"
                         + Integer.toHexString(System.identityHashCode(this))
-                        + " uid " + mUid + " Listener " + mKey + "}";
+                        + " Listener " + mKey + "}";
             } else {
                 return "Receiver{"
                         + Integer.toHexString(System.identityHashCode(this))
-                        + " uid " + mUid + " Intent " + mKey + "}";
+                        + " Intent " + mKey + "}";
             }
         }
 
@@ -329,7 +307,7 @@
             if (LOCAL_LOGV) {
                 Log.v(TAG, "Location listener died");
             }
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                 removeUpdatesLocked(this);
             }
         }
@@ -337,7 +315,7 @@
 
     private final class SettingsObserver implements Observer {
         public void update(Observable o, Object arg) {
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                 updateProvidersLocked();
             }
         }
@@ -444,7 +422,7 @@
      *                                                          properties
      */
     private void loadProviders() {
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             if (sProvidersLoaded) {
                 return;
             }
@@ -558,9 +536,6 @@
         PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
         
-        // Battery statistics service to be notified when GPS turns on or off
-        mBatteryStats = BatteryStatsService.getService();
-
         // Load providers
         loadProviders();
 
@@ -607,10 +582,9 @@
                 "Installing location providers outside of the system is not supported");
         }
 
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             mNetworkLocationProvider =
                     new LocationProviderProxy(LocationManager.NETWORK_PROVIDER, this, provider);
-            mNetworkLocationProvider.addListener(getPackageNames());
             LocationProviderImpl.addProvider(mNetworkLocationProvider);
             updateProvidersLocked();
             
@@ -626,7 +600,7 @@
                 "Installing location collectors outside of the system is not supported");
         }
 
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             mCollector = collector;
             if (mGpsLocationProvider != null) {
                 mGpsLocationProvider.setLocationCollector(mCollector);
@@ -699,15 +673,9 @@
         return true;
     }
 
-    private String[] getPackageNames() {
-        // Since a single UID may correspond to multiple packages, this can only be used as an
-        // approximation for tracking
-        return mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
-    }
-
     public List<String> getAllProviders() {
         try {
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                 return _getAllProvidersLocked();
             }
         } catch (SecurityException se) {
@@ -733,7 +701,7 @@
 
     public List<String> getProviders(boolean enabledOnly) {
         try {
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                 return _getProvidersLocked(enabledOnly);
             }
         } catch (SecurityException se) {
@@ -839,7 +807,6 @@
             p.enableLocationTracking(false);
             if (p == mGpsLocationProvider) {
                 mGpsNavigating = false;
-                reportStopGpsLocked();
             }
             p.disable();
             updateWakelockStatusLocked(mScreenOn);
@@ -863,19 +830,19 @@
         final long mMinTime;
         final float mMinDistance;
         final int mUid;
-        final String[] mPackages;
+        Location mLastFixBroadcast;
+        long mLastStatusBroadcast;
 
         /**
          * Note: must be constructed with lock held.
          */
         UpdateRecord(String provider, long minTime, float minDistance,
-            Receiver receiver, int uid, String[] packages) {
+            Receiver receiver, int uid) {
             mProvider = provider;
             mReceiver = receiver;
             mMinTime = minTime;
             mMinDistance = minDistance;
             mUid = uid;
-            mPackages = packages;
 
             ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
             if (records == null) {
@@ -907,33 +874,77 @@
             pw.println(prefix + this);
             pw.println(prefix + "mProvider=" + mProvider + " mReceiver=" + mReceiver);
             pw.println(prefix + "mMinTime=" + mMinTime + " mMinDistance=" + mMinDistance);
-            StringBuilder sb = new StringBuilder();
-            if (mPackages != null) {
-                for (int i=0; i<mPackages.length; i++) {
-                    if (i > 0) sb.append(", ");
-                    sb.append(mPackages[i]);
-                }
-            }
-            pw.println(prefix + "mUid=" + mUid + " mPackages=" + sb);
+            pw.println(prefix + "mUid=" + mUid);
+            pw.println(prefix + "mLastFixBroadcast:");
+            mLastFixBroadcast.dump(new PrintWriterPrinter(pw), prefix + "  ");
+            pw.println(prefix + "mLastStatusBroadcast=" + mLastStatusBroadcast);
         }
         
         /**
          * Calls dispose().
          */
         @Override protected void finalize() {
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                 disposeLocked();
             }
         }
     }
 
+    private Receiver getReceiver(ILocationListener listener) {
+        IBinder binder = listener.asBinder();
+        Receiver receiver = mReceivers.get(binder);
+        if (receiver == null) {
+            receiver = new Receiver(listener);
+            mReceivers.put(binder, receiver);
+
+            try {
+                if (receiver.isListener()) {
+                    receiver.getListener().asBinder().linkToDeath(receiver, 0);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "linkToDeath failed:", e);
+                return null;
+            }
+        }
+        return receiver;
+    }
+
+    private Receiver getReceiver(PendingIntent intent) {
+        Receiver receiver = mReceivers.get(intent);
+        if (receiver == null) {
+            receiver = new Receiver(intent);
+            mReceivers.put(intent, receiver);
+        }
+        return receiver;
+    }
+
+    private boolean providerHasListener(String provider, int uid, Receiver excludedReceiver) {
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+        if (records != null) {
+            for (int i = records.size() - 1; i >= 0; i--) {
+                UpdateRecord record = records.get(i);
+                if (record.mUid == uid && record.mReceiver != excludedReceiver) {
+                    return true;
+                }
+           }
+        }
+        if (LocationManager.GPS_PROVIDER.equals(provider) ||
+                LocationManager.NETWORK_PROVIDER.equals(provider)) {
+            for (ProximityAlert alert : mProximityAlerts.values()) {
+                if (alert.mUid == uid) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     public void requestLocationUpdates(String provider,
         long minTime, float minDistance, ILocationListener listener) {
 
         try {
-            synchronized (mLocationListeners) {
-                requestLocationUpdatesLocked(provider, minTime, minDistance,
-                    new Receiver(listener, Binder.getCallingUid()));
+            synchronized (mLock) {
+                requestLocationUpdatesLocked(provider, minTime, minDistance, getReceiver(listener));
             }
         } catch (SecurityException se) {
             throw se;
@@ -945,9 +956,8 @@
     public void requestLocationUpdatesPI(String provider,
             long minTime, float minDistance, PendingIntent intent) {
         try {
-            synchronized (mLocationListeners) {
-                requestLocationUpdatesLocked(provider, minTime, minDistance,
-                        new Receiver(intent, Binder.getCallingUid()));
+            synchronized (mLock) {
+                requestLocationUpdatesLocked(provider, minTime, minDistance, getReceiver(intent));
             }
         } catch (SecurityException se) {
             throw se;
@@ -969,47 +979,27 @@
 
         checkPermissionsSafe(provider);
 
-        String[] packages = getPackageNames();
-
         // so wakelock calls will succeed
         final int callingUid = Binder.getCallingUid();
+        boolean newUid = !providerHasListener(provider, callingUid, null);
         long identity = Binder.clearCallingIdentity();
         try {
-            UpdateRecord r = new UpdateRecord(provider, minTime, minDistance,
-                    receiver, callingUid, packages);
-            if (!mListeners.contains(receiver)) {
-                try {
-                    if (receiver.isListener()) {
-                        receiver.getListener().asBinder().linkToDeath(receiver, 0);
-                    }
-                    mListeners.add(receiver);
-                } catch (RemoteException e) {
-                    return;
-                }
-            }
-
-            HashMap<String,UpdateRecord> records = mLocationListeners.get(receiver);
-            if (records == null) {
-                records = new HashMap<String,UpdateRecord>();
-                mLocationListeners.put(receiver, records);
-            }
-            UpdateRecord oldRecord = records.put(provider, r);
+            UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, receiver, callingUid);
+            UpdateRecord oldRecord = receiver.mUpdateRecords.put(provider, r);
             if (oldRecord != null) {
                 oldRecord.disposeLocked();
             }
 
+            if (newUid) {
+                impl.addListener(callingUid);
+            }
+
             boolean isProviderEnabled = isAllowedBySettingsLocked(provider);
             if (isProviderEnabled) {
                 long minTimeForProvider = getMinTimeLocked(provider);
                 impl.setMinTime(minTimeForProvider);
                 impl.enableLocationTracking(true);
                 updateWakelockStatusLocked(mScreenOn);
-
-                if (provider.equals(LocationManager.GPS_PROVIDER)) {
-                    if (mGpsNavigating) {
-                        updateReportedGpsLocked();
-                    }
-                }
             } else {
                 try {
                     // Notify the listener that updates are currently disabled
@@ -1028,8 +1018,8 @@
 
     public void removeUpdates(ILocationListener listener) {
         try {
-            synchronized (mLocationListeners) {
-                removeUpdatesLocked(new Receiver(listener, Binder.getCallingUid()));
+            synchronized (mLock) {
+                removeUpdatesLocked(getReceiver(listener));
             }
         } catch (SecurityException se) {
             throw se;
@@ -1040,8 +1030,8 @@
 
     public void removeUpdatesPI(PendingIntent intent) {
         try {
-            synchronized (mLocationListeners) {
-                removeUpdatesLocked(new Receiver(intent, Binder.getCallingUid()));
+            synchronized (mLock) {
+                removeUpdatesLocked(getReceiver(intent));
             }
         } catch (SecurityException se) {
             throw se;
@@ -1059,23 +1049,21 @@
         final int callingUid = Binder.getCallingUid();
         long identity = Binder.clearCallingIdentity();
         try {
-            int idx = mListeners.indexOf(receiver);
-            if (idx >= 0) {
-                Receiver myReceiver = mListeners.remove(idx);
-                if (myReceiver.isListener()) {
-                    myReceiver.getListener().asBinder().unlinkToDeath(myReceiver, 0);
-                }
+            if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) {
+                receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
             }
 
             // Record which providers were associated with this listener
             HashSet<String> providers = new HashSet<String>();
-            HashMap<String,UpdateRecord> oldRecords = mLocationListeners.get(receiver);
+            HashMap<String,UpdateRecord> oldRecords = receiver.mUpdateRecords;
             if (oldRecords != null) {
                 // Call dispose() on the obsolete update records.
                 for (UpdateRecord record : oldRecords.values()) {
-                    if (record.mProvider.equals(LocationManager.NETWORK_PROVIDER)) {
-                        if (mNetworkLocationProvider != null) {
-                            mNetworkLocationProvider.removeListener(record.mPackages);
+                    if (!providerHasListener(record.mProvider, callingUid, receiver)) {
+                        LocationProviderImpl impl =
+                                LocationProviderImpl.getProvider(record.mProvider);
+                        if (impl != null) {
+                            impl.removeListener(callingUid);
                         }
                     }
                     record.disposeLocked();
@@ -1083,10 +1071,6 @@
                 // Accumulate providers
                 providers.addAll(oldRecords.keySet());
             }
-            
-            mLocationListeners.remove(receiver);
-            mLastFixBroadcast.remove(receiver);
-            mLastStatusBroadcast.remove(receiver);
 
             // See if the providers associated with this listener have any
             // other listeners; if one does, inform it of the new smallest minTime
@@ -1110,10 +1094,6 @@
                     } else {
                         p.enableLocationTracking(false);
                     }
-                    
-                    if (p == mGpsLocationProvider && mGpsNavigating) {
-                        updateReportedGpsLocked();
-                    }
                 }
             }
 
@@ -1142,7 +1122,7 @@
     }
 
     public void removeGpsStatusListener(IGpsStatusListener listener) {
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             mGpsLocationProvider.removeGpsStatusListener(listener);
         }
     }
@@ -1156,7 +1136,7 @@
             throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
         }
 
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             LocationProviderImpl impl = LocationProviderImpl.getProvider(provider);
             if (provider == null) {
                 return false;
@@ -1337,7 +1317,7 @@
     public void addProximityAlert(double latitude, double longitude,
         float radius, long expiration, PendingIntent intent) {
         try {
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                 addProximityAlertLocked(latitude, longitude, radius, expiration, intent);
             }
         } catch (SecurityException se) {
@@ -1370,7 +1350,7 @@
         mProximityAlerts.put(intent, alert);
 
         if (mProximityListener == null) {
-            mProximityListener = new Receiver(new ProximityListener(), -1);
+            mProximityListener = new Receiver(new ProximityListener());
 
             LocationProvider provider = LocationProviderImpl.getProvider(
                 LocationManager.GPS_PROVIDER);
@@ -1383,14 +1363,12 @@
             if (provider != null) {
                 requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityListener);
             }
-        } else if (mGpsNavigating) {
-            updateReportedGpsLocked();
         }
     }
 
     public void removeProximityAlert(PendingIntent intent) {
         try {
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                removeProximityAlertLocked(intent);
             }
         } catch (SecurityException se) {
@@ -1409,8 +1387,6 @@
         if (mProximityAlerts.size() == 0) {
             removeUpdatesLocked(mProximityListener);
             mProximityListener = null;
-        } else if (mGpsNavigating) {
-            updateReportedGpsLocked();
         }
      }
 
@@ -1421,7 +1397,7 @@
      */
     public Bundle getProviderInfo(String provider) {
         try {
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                 return _getProviderInfoLocked(provider);
             }
         } catch (SecurityException se) {
@@ -1456,7 +1432,7 @@
 
     public boolean isProviderEnabled(String provider) {
         try {
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                 return _isProviderEnabledLocked(provider);
             }
         } catch (SecurityException se) {
@@ -1485,7 +1461,7 @@
 
     public Location getLastKnownLocation(String provider) {
         try {
-            synchronized (mLocationListeners) {
+            synchronized (mLock) {
                 return _getLastKnownLocationLocked(provider);
             }
         } catch (SecurityException se) {
@@ -1585,16 +1561,11 @@
             UpdateRecord r = records.get(i);
             Receiver receiver = r.mReceiver;
 
-            HashMap<String,Location> map = mLastFixBroadcast.get(receiver);
-            if (map == null) {
-                map = new HashMap<String,Location>();
-                mLastFixBroadcast.put(receiver, map);
-            }
-            Location lastLoc = map.get(provider);
+            Location lastLoc = r.mLastFixBroadcast;
             if ((lastLoc == null) || shouldBroadcastSafe(location, lastLoc, r)) {
                 if (lastLoc == null) {
                     lastLoc = new Location(location);
-                    map.put(provider, lastLoc);
+                    r.mLastFixBroadcast = lastLoc;
                 } else {
                     lastLoc.set(location);
                 }
@@ -1607,19 +1578,11 @@
                 }
             }
 
-            // Broadcast status message
-            HashMap<String,Long> statusMap = mLastStatusBroadcast.get(receiver);
-            if (statusMap == null) {
-                statusMap = new HashMap<String,Long>();
-                mLastStatusBroadcast.put(receiver, statusMap);
-            }
-            long prevStatusUpdateTime =
-                (statusMap.get(provider) != null) ? statusMap.get(provider) : 0;
-
+            long prevStatusUpdateTime = r.mLastStatusBroadcast;
             if ((newStatusUpdateTime > prevStatusUpdateTime) &&
                 (prevStatusUpdateTime != 0 || status != LocationProvider.AVAILABLE)) {
 
-                statusMap.put(provider, newStatusUpdateTime);
+                r.mLastStatusBroadcast = newStatusUpdateTime;
                 if (!receiver.callStatusChangedLocked(provider, status, extras)) {
                     Log.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
                     if (deadReceivers == null) {
@@ -1647,7 +1610,7 @@
                 if (msg.what == MESSAGE_LOCATION_CHANGED) {
                     // log("LocationWorkerHandler: MESSAGE_LOCATION_CHANGED!");
 
-                    synchronized (mLocationListeners) {
+                    synchronized (mLock) {
                         Location location = (Location) msg.obj;
                         String provider = location.getProvider();
                         if (!isAllowedBySettingsLocked(provider)) {
@@ -1685,14 +1648,14 @@
 
                 } else if (msg.what == MESSAGE_ACQUIRE_WAKE_LOCK) {
                     log("LocationWorkerHandler: Acquire");
-                    synchronized (mLocationListeners) {
+                    synchronized (mLock) {
                         acquireWakeLockLocked();
                     }
                 } else if (msg.what == MESSAGE_RELEASE_WAKE_LOCK) {
                     log("LocationWorkerHandler: Release");
 
                     // Update wakelock status so the next alarm is set before releasing wakelock
-                    synchronized (mLocationListeners) {
+                    synchronized (mLock) {
                         updateWakelockStatusLocked(mScreenOn);
                         releaseWakeLockLocked();
                     }
@@ -1709,7 +1672,7 @@
             String action = intent.getAction();
 
             if (action.equals(ALARM_INTENT)) {
-                synchronized (mLocationListeners) {
+                synchronized (mLock) {
                     log("PowerStateBroadcastReceiver: Alarm received");
                     mLocationHandler.removeMessages(MESSAGE_ACQUIRE_WAKE_LOCK);
                     // Have to do this immediately, rather than posting a
@@ -1721,18 +1684,18 @@
 
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 log("PowerStateBroadcastReceiver: Screen off");
-                synchronized (mLocationListeners) {
+                synchronized (mLock) {
                     updateWakelockStatusLocked(false);
                 }
 
             } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
                 log("PowerStateBroadcastReceiver: Screen on");
-                synchronized (mLocationListeners) {
+                synchronized (mLock) {
                     updateWakelockStatusLocked(true);
                 }
             } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
                     || action.equals(Intent.ACTION_PACKAGE_RESTARTED)) {
-                synchronized (mLocationListeners) {
+                synchronized (mLock) {
                     int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                     if (uid >= 0) {
                         ArrayList<Receiver> removedRecs = null;
@@ -1790,7 +1753,7 @@
                 }
 
                 // Notify location providers of current network state
-                synchronized (mLocationListeners) {
+                synchronized (mLock) {
                     List<LocationProviderImpl> providers = LocationProviderImpl.getProviders();
                     for (LocationProviderImpl provider : providers) {
                         if (provider.requiresNetwork()) {
@@ -1803,12 +1766,10 @@
                 final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED,
                     false);
 
-                synchronized (mLocationListeners) {
+                synchronized (mLock) {
                     if (enabled) {
-                        updateReportedGpsLocked();
                         mGpsNavigating = true;
                     } else {
-                        reportStopGpsLocked();
                         mGpsNavigating = false;
                         // When GPS is disabled, we are OK to release wake-lock
                         mWakeLockGpsReceived = true;
@@ -1941,85 +1902,6 @@
         }
     }
 
-    private boolean reportGpsUidLocked(int curSeq, int nextSeq, int uid) {
-        int seq = mReportedGpsUids.get(uid, -1);
-        if (seq == curSeq) {
-            // Already reported; propagate to next sequence.
-            mReportedGpsUids.put(uid, nextSeq);
-            return true;
-        } else if (seq != nextSeq) {
-            try {
-                // New UID; report it.
-                mBatteryStats.noteStartGps(uid);
-                mReportedGpsUids.put(uid, nextSeq);
-                return true;
-            } catch (RemoteException e) {
-            }
-        }
-        return false;
-    }
-    
-    private void updateReportedGpsLocked() {
-        if (mGpsLocationProvider == null) {
-            return;
-        }
-        
-        final String name = mGpsLocationProvider.getName();
-        final int curSeq = mReportedGpsSeq;
-        final int nextSeq = (curSeq+1) >= 0 ? (curSeq+1) : 0;
-        mReportedGpsSeq = nextSeq;
-        
-        ArrayList<UpdateRecord> urs = mRecordsByProvider.get(name);
-        int num = 0;
-        final int N = urs.size();
-        for (int i=0; i<N; i++) {
-            UpdateRecord ur = urs.get(i);
-            if (ur.mReceiver == mProximityListener) {
-                // We don't want the system to take the blame for this one.
-                continue;
-            }
-            if (reportGpsUidLocked(curSeq, nextSeq, ur.mUid)) {
-                num++;
-            }
-        }
-        
-        for (ProximityAlert pe : mProximityAlerts.values()) {
-            if (reportGpsUidLocked(curSeq, nextSeq, pe.mUid)) {
-                num++;
-            }
-        }
-        
-        if (num != mReportedGpsUids.size()) {
-            // The number of uids is processed is different than the
-            // array; report any that are no longer active.
-            for (int i=mReportedGpsUids.size()-1; i>=0; i--) {
-                if (mReportedGpsUids.valueAt(i) != nextSeq) {
-                    try {
-                        mBatteryStats.noteStopGps(mReportedGpsUids.keyAt(i));
-                    } catch (RemoteException e) {
-                    }
-                    mReportedGpsUids.removeAt(i);
-                }
-            }
-        }
-    }
-    
-    private void reportStopGpsLocked() {
-        int curSeq = mReportedGpsSeq;
-        for (int i=mReportedGpsUids.size()-1; i>=0; i--) {
-            if (mReportedGpsUids.valueAt(i) == curSeq) {
-                try {
-                    mBatteryStats.noteStopGps(mReportedGpsUids.keyAt(i));
-                } catch (RemoteException e) {
-                }
-            }
-        }
-        curSeq++;
-        if (curSeq < 0) curSeq = 0;
-        mReportedGpsSeq = curSeq;
-        mReportedGpsUids.clear();
-    }
-    
     private void startGpsLocked() {
         boolean gpsActive = (mGpsLocationProvider != null)
                     && mGpsLocationProvider.isLocationTracking();
@@ -2135,7 +2017,7 @@
         boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) {
         checkMockPermissionsSafe();
 
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             MockProvider provider = new MockProvider(name, this,
                 requiresNetwork, requiresSatellite,
                 requiresCell, hasMonetaryCost, supportsAltitude,
@@ -2151,7 +2033,7 @@
 
     public void removeTestProvider(String provider) {
         checkMockPermissionsSafe();
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             MockProvider mockProvider = mMockProviders.get(provider);
             if (mockProvider == null) {
                 throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
@@ -2164,7 +2046,7 @@
 
     public void setTestProviderLocation(String provider, Location loc) {
         checkMockPermissionsSafe();
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             MockProvider mockProvider = mMockProviders.get(provider);
             if (mockProvider == null) {
                 throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
@@ -2175,7 +2057,7 @@
 
     public void clearTestProviderLocation(String provider) {
         checkMockPermissionsSafe();
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             MockProvider mockProvider = mMockProviders.get(provider);
             if (mockProvider == null) {
                 throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
@@ -2186,7 +2068,7 @@
 
     public void setTestProviderEnabled(String provider, boolean enabled) {
         checkMockPermissionsSafe();
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             MockProvider mockProvider = mMockProviders.get(provider);
             if (mockProvider == null) {
                 throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
@@ -2206,7 +2088,7 @@
 
     public void clearTestProviderEnabled(String provider) {
         checkMockPermissionsSafe();
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             MockProvider mockProvider = mMockProviders.get(provider);
             if (mockProvider == null) {
                 throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
@@ -2219,7 +2101,7 @@
 
     public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) {
         checkMockPermissionsSafe();
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             MockProvider mockProvider = mMockProviders.get(provider);
             if (mockProvider == null) {
                 throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
@@ -2230,7 +2112,7 @@
 
     public void clearTestProviderStatus(String provider) {
         checkMockPermissionsSafe();
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             MockProvider mockProvider = mMockProviders.get(provider);
             if (mockProvider == null) {
                 throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
@@ -2254,7 +2136,7 @@
             return;
         }
         
-        synchronized (mLocationListeners) {
+        synchronized (mLock) {
             pw.println("Current Location Manager state:");
             pw.println("  sProvidersLoaded=" + sProvidersLoaded);
             pw.println("  mGpsLocationProvider=" + mGpsLocationProvider);
@@ -2269,37 +2151,18 @@
             pw.println("  mWifiWakeLockAcquired=" + mWifiWakeLockAcquired
                     + " mCellWakeLockAcquired=" + mCellWakeLockAcquired);
             pw.println("  Listeners:");
-            int N = mListeners.size();
+            int N = mReceivers.size();
             for (int i=0; i<N; i++) {
-                pw.println("    " + mListeners.get(i));
+                pw.println("    " + mReceivers.get(i));
             }
             pw.println("  Location Listeners:");
-            for (Map.Entry<Receiver, HashMap<String,UpdateRecord>> i
-                    : mLocationListeners.entrySet()) {
-                pw.println("    " + i.getKey() + ":");
-                for (Map.Entry<String,UpdateRecord> j : i.getValue().entrySet()) {
+            for (Receiver i : mReceivers.values()) {
+                pw.println("    " + i + ":");
+                for (Map.Entry<String,UpdateRecord> j : i.mUpdateRecords.entrySet()) {
                     pw.println("      " + j.getKey() + ":");
                     j.getValue().dump(pw, "        ");
                 }
             }
-            pw.println("  Last Fix Broadcasts:");
-            for (Map.Entry<Receiver, HashMap<String,Location>> i
-                    : mLastFixBroadcast.entrySet()) {
-                pw.println("    " + i.getKey() + ":");
-                for (Map.Entry<String,Location> j : i.getValue().entrySet()) {
-                    pw.println("      " + j.getKey() + ":");
-                    j.getValue().dump(new PrintWriterPrinter(pw), "        ");
-                }
-            }
-            pw.println("  Last Status Broadcasts:");
-            for (Map.Entry<Receiver, HashMap<String,Long>> i
-                    : mLastStatusBroadcast.entrySet()) {
-                pw.println("    " + i.getKey() + ":");
-                for (Map.Entry<String,Long> j : i.getValue().entrySet()) {
-                    pw.println("      " + j.getKey() + " -> 0x"
-                            + Long.toHexString(j.getValue()));
-                }
-            }
             pw.println("  Records by Provider:");
             for (Map.Entry<String, ArrayList<UpdateRecord>> i
                     : mRecordsByProvider.entrySet()) {
@@ -2351,12 +2214,6 @@
                     i.getValue().dump(pw, "      ");
                 }
             }
-            pw.println("  Reported GPS UIDs @ seq " + mReportedGpsSeq + ":");
-            N = mReportedGpsUids.size();
-            for (int i=0; i<N; i++)  {
-                pw.println("    UID " + mReportedGpsUids.keyAt(i)
-                        + " seq=" + mReportedGpsUids.valueAt(i));
-            }
         }
     }
 }
diff --git a/test-runner/android/test/InstrumentationCoreTestRunner.java b/test-runner/android/test/InstrumentationCoreTestRunner.java
index 6b1a4e4..3f77a60 100644
--- a/test-runner/android/test/InstrumentationCoreTestRunner.java
+++ b/test-runner/android/test/InstrumentationCoreTestRunner.java
@@ -17,27 +17,158 @@
 package android.test;
 
 import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.List;
 
+import com.android.internal.util.Predicate;
+import com.android.internal.util.Predicates;
+
+import dalvik.annotation.BrokenTest;
+import dalvik.annotation.SideEffect;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestListener;
 import android.os.Bundle;
+import android.test.suitebuilder.TestMethod;
+import android.test.suitebuilder.annotation.HasAnnotation;
+import android.util.Log;
 
 /**
  * This test runner extends the default InstrumentationTestRunner. It overrides
  * the {@code onCreate(Bundle)} method and sets the system properties necessary
  * for many core tests to run. This is needed because there are some core tests
- * that need writing access to the filesystem.
+ * that need writing access to the file system. We also need to set the harness
+ * Thread's context ClassLoader. Otherwise some classes and resources will not
+ * be found. Finally, we add a means to free memory allocated by a TestCase
+ * after its execution.
  *
  * @hide
  */
 public class InstrumentationCoreTestRunner extends InstrumentationTestRunner {
 
+    private static final String TAG = "InstrumentationCoreTestRunner";
+    private boolean singleTest = false;
+    
     @Override
     public void onCreate(Bundle arguments) {
-        super.onCreate(arguments);
-        
+        // We might want to move this to /sdcard, if is is mounted/writable.
         File cacheDir = getTargetContext().getCacheDir();
 
         System.setProperty("user.language", "en");
         System.setProperty("user.region", "US");
+        
+        System.setProperty("java.home", cacheDir.getAbsolutePath());
+        System.setProperty("user.home", cacheDir.getAbsolutePath());
         System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
+        System.setProperty("javax.net.ssl.trustStore",
+                "/etc/security/cacerts.bks");
+        
+        if (arguments != null) {
+            String classArg = arguments.getString(ARGUMENT_TEST_CLASS);
+            singleTest = classArg != null && classArg.contains("#"); 
+        }
+        
+        super.onCreate(arguments);
+    }
+
+    protected AndroidTestRunner getAndroidTestRunner() {
+        AndroidTestRunner runner = super.getAndroidTestRunner();
+
+        runner.addTestListener(new TestListener() {
+            private Class<?> lastClass;
+            
+            public void startTest(Test test) {
+                if (test.getClass() != lastClass) {
+                    printMemory(test.getClass());
+                }
+                
+                Thread.currentThread().setContextClassLoader(
+                        test.getClass().getClassLoader());
+            }
+            
+            public void endTest(Test test) {
+                if (test instanceof TestCase) {
+                    if (lastClass == null) {
+                        lastClass = test.getClass();
+                    } else {
+                        if (test.getClass() != lastClass) {
+                            cleanup(lastClass);
+                            lastClass = test.getClass();
+                        }
+                    }
+                }
+            }
+            
+            public void addError(Test test, Throwable t) {
+            }
+            
+            public void addFailure(Test test, AssertionFailedError t) {
+            }
+            
+            /**
+             * Dumps some memory info.
+             */
+            private void printMemory(Class<? extends Test> testClass) {
+                Runtime runtime = Runtime.getRuntime();
+
+                long total = runtime.totalMemory();
+                long free = runtime.freeMemory();
+                long used = total - free;
+                
+                Log.d(TAG, "Total memory  : " + total);
+                Log.d(TAG, "Used memory   : " + used);
+                Log.d(TAG, "Free memory   : " + free);
+                Log.d(TAG, "Now executing : " + testClass.getName());
+            }
+
+            /**
+             * Nulls all static reference fields in the given test class. This
+             * method helps us with those test classes that don't have an
+             * explicit tearDown() method. Normally the garbage collector should
+             * take care of everything, but since JUnit keeps references to all
+             * test cases, a little help might be a good idea.
+             */
+            private void cleanup(Class<?> clazz) {
+                if (clazz != TestCase.class) {
+                    Field[] fields = clazz.getDeclaredFields();
+                    for (int i = 0; i < fields.length; i++) {
+                        Field f = fields[i];
+                        if (!f.getType().isPrimitive() &&
+                                Modifier.isStatic(f.getModifiers())) {
+                            try {
+                                f.setAccessible(true);
+                                f.set(null, null);
+                            } catch (Exception ignored) {
+                                // Nothing we can do about it.
+                            }
+                        }
+                    }
+                    
+                    // don't cleanup the superclass for now
+                    //cleanup(clazz.getSuperclass());
+                }
+            }
+            
+        });
+        
+        return runner;
+    }    
+
+    @Override
+    List<Predicate<TestMethod>> getBuilderRequirements() {
+        List<Predicate<TestMethod>> builderRequirements =
+                super.getBuilderRequirements();
+        Predicate<TestMethod> brokenTestPredicate =
+                Predicates.not(new HasAnnotation(BrokenTest.class));
+        builderRequirements.add(brokenTestPredicate);
+        if (!singleTest) {
+            Predicate<TestMethod> sideEffectPredicate =
+                    Predicates.not(new HasAnnotation(SideEffect.class));
+            builderRequirements.add(sideEffectPredicate);
+        }
+        return builderRequirements;
     }
 }
diff --git a/test-runner/android/test/InstrumentationTestRunner.java b/test-runner/android/test/InstrumentationTestRunner.java
index 044f555..d5e6459 100644
--- a/test-runner/android/test/InstrumentationTestRunner.java
+++ b/test-runner/android/test/InstrumentationTestRunner.java
@@ -43,6 +43,8 @@
 import java.io.PrintStream;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
 
 
 /**
@@ -315,6 +317,8 @@
         } else {
             parseTestClasses(testClassesArg, testSuiteBuilder);
         }
+        
+        testSuiteBuilder.addRequirements(getBuilderRequirements());
 
         mTestRunner = getAndroidTestRunner();
         mTestRunner.setContext(getTargetContext());
@@ -331,6 +335,10 @@
         start();
     }
 
+    List<Predicate<TestMethod>> getBuilderRequirements() {
+        return new ArrayList<Predicate<TestMethod>>();
+    }
+
     /**
      * Parses and loads the specified set of test classes 
      * @param testClassArg - comma-separated list of test classes and methods