Merge "use min app launch time in AppLaunch perf test"
diff --git a/Android.mk b/Android.mk
index a57b3fa..9b3436e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -260,12 +260,17 @@
 	media/java/android/media/IAudioService.aidl \
 	media/java/android/media/IAudioFocusDispatcher.aidl \
 	media/java/android/media/IAudioRoutesObserver.aidl \
+	media/java/android/media/IMediaController.aidl \
+	media/java/android/media/IMediaControllerCallback.aidl \
 	media/java/android/media/IMediaHTTPConnection.aidl \
 	media/java/android/media/IMediaHTTPService.aidl \
 	media/java/android/media/IMediaRouterClient.aidl \
 	media/java/android/media/IMediaRouterService.aidl \
 	media/java/android/media/IMediaScannerListener.aidl \
 	media/java/android/media/IMediaScannerService.aidl \
+	media/java/android/media/IMediaSession.aidl \
+	media/java/android/media/IMediaSessionCallback.aidl \
+	media/java/android/media/IMediaSessionManager.aidl \
 	media/java/android/media/IRemoteControlClient.aidl \
 	media/java/android/media/IRemoteControlDisplay.aidl \
 	media/java/android/media/IRemoteDisplayCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index 6728823..46ff80c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6317,6 +6317,7 @@
     field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
     field public static final java.lang.String LOCATION_SERVICE = "location";
     field public static final java.lang.String MEDIA_ROUTER_SERVICE = "media_router";
+    field public static final java.lang.String MEDIA_SESSION_SERVICE = "media_session";
     field public static final int MODE_APPEND = 32768; // 0x8000
     field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
     field public static final int MODE_MULTI_PROCESS = 4; // 0x4
@@ -11306,6 +11307,7 @@
     field public static final android.hardware.camera2.CameraMetadata.Key SENSOR_INFO_PHYSICAL_SIZE;
     field public static final android.hardware.camera2.CameraMetadata.Key SENSOR_INFO_PIXEL_ARRAY_SIZE;
     field public static final android.hardware.camera2.CameraMetadata.Key SENSOR_INFO_SENSITIVITY_RANGE;
+    field public static final android.hardware.camera2.CameraMetadata.Key SENSOR_INFO_WHITE_LEVEL;
     field public static final android.hardware.camera2.CameraMetadata.Key SENSOR_MAX_ANALOG_SENSITIVITY;
     field public static final android.hardware.camera2.CameraMetadata.Key SENSOR_ORIENTATION;
     field public static final android.hardware.camera2.CameraMetadata.Key SENSOR_PROFILE_HUE_SAT_MAP_DIMENSIONS;
@@ -13155,6 +13157,23 @@
     method public static final android.media.MediaCodecInfo getCodecInfoAt(int);
   }
 
+  public final class MediaController {
+    ctor public MediaController(android.media.MediaSessionToken);
+    method public void addCallback(android.media.MediaController.Callback);
+    method public void addCallback(android.media.MediaController.Callback, android.os.Handler);
+    method public void removeCallback(android.media.MediaController.Callback);
+    method public void sendCommand(java.lang.String, android.os.Bundle);
+    method public void sendMediaButton(int);
+  }
+
+  public static abstract class MediaController.Callback {
+    ctor public MediaController.Callback();
+    method public void onEvent(java.lang.String, android.os.Bundle);
+    method public void onMetadataUpdate(android.os.Bundle);
+    method public void onPlaybackStateChange(int);
+    method public void onRouteChanged(android.os.Bundle);
+  }
+
   public final class MediaCrypto {
     ctor public MediaCrypto(java.util.UUID, byte[]) throws android.media.MediaCryptoException;
     method public static final boolean isCryptoSchemeSupported(java.util.UUID);
@@ -13721,6 +13740,33 @@
     method public abstract void onScanCompleted(java.lang.String, android.net.Uri);
   }
 
+  public final class MediaSession {
+    method public void addCallback(android.media.MediaSession.Callback);
+    method public void addCallback(android.media.MediaSession.Callback, android.os.Handler);
+    method public android.media.MediaSessionToken getSessionToken();
+    method public void release();
+    method public void removeCallback(android.media.MediaSession.Callback);
+    method public void setPlaybackState(int);
+  }
+
+  public static abstract class MediaSession.Callback {
+    ctor public MediaSession.Callback();
+    method public void onCommand(java.lang.String, android.os.Bundle);
+    method public void onMediaButton(android.content.Intent);
+    method public void onRequestRouteChange(android.os.Bundle);
+  }
+
+  public final class MediaSessionManager {
+    method public android.media.MediaSession createSession(java.lang.String);
+    method public java.util.List<android.media.MediaController> getActiveSessions();
+  }
+
+  public class MediaSessionToken implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
   public class MediaSyncEvent {
     method public static android.media.MediaSyncEvent createEvent(int) throws java.lang.IllegalArgumentException;
     method public int getAudioSessionId();
@@ -17149,11 +17195,9 @@
     method public static void glGenTextures(int, java.nio.IntBuffer);
     method public static void glGenerateMipmap(int);
     method public static void glGetActiveAttrib(int, int, int, int[], int, int[], int, int[], int, byte[], int);
-    method public static void glGetActiveAttrib(int, int, int, java.nio.IntBuffer, java.nio.IntBuffer, java.nio.IntBuffer, byte);
     method public static java.lang.String glGetActiveAttrib(int, int, int[], int, int[], int);
     method public static java.lang.String glGetActiveAttrib(int, int, java.nio.IntBuffer, java.nio.IntBuffer);
     method public static void glGetActiveUniform(int, int, int, int[], int, int[], int, int[], int, byte[], int);
-    method public static void glGetActiveUniform(int, int, int, java.nio.IntBuffer, java.nio.IntBuffer, java.nio.IntBuffer, byte);
     method public static java.lang.String glGetActiveUniform(int, int, int[], int, int[], int);
     method public static java.lang.String glGetActiveUniform(int, int, java.nio.IntBuffer, java.nio.IntBuffer);
     method public static void glGetAttachedShaders(int, int, int[], int, int[], int);
@@ -17179,7 +17223,6 @@
     method public static void glGetShaderPrecisionFormat(int, int, int[], int, int[], int);
     method public static void glGetShaderPrecisionFormat(int, int, java.nio.IntBuffer, java.nio.IntBuffer);
     method public static void glGetShaderSource(int, int, int[], int, byte[], int);
-    method public static void glGetShaderSource(int, int, java.nio.IntBuffer, byte);
     method public static java.lang.String glGetShaderSource(int);
     method public static void glGetShaderiv(int, int, int[], int);
     method public static void glGetShaderiv(int, int, java.nio.IntBuffer);
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index a57de01..2efe4d3 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -141,7 +141,7 @@
 
     ScreenshotClient screenshot;
     sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId);
-    if (display != NULL && screenshot.update(display) == NO_ERROR) {
+    if (display != NULL && screenshot.update(display, false) == NO_ERROR) {
         base = screenshot.getPixels();
         w = screenshot.getWidth();
         h = screenshot.getHeight();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 8d127c6..873db8e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.os.Build;
+
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.util.Preconditions;
 
@@ -62,6 +63,7 @@
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.MediaRouter;
+import android.media.MediaSessionManager;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.INetworkPolicyManager;
@@ -587,6 +589,12 @@
             public Object createService(ContextImpl ctx) {
                 return new ConsumerIrManager(ctx);
             }});
+
+        registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() {
+            public Object createService(ContextImpl ctx) {
+                return new MediaSessionManager(ctx);
+            }
+        });
     }
 
     static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9f90de0..d05d1a1 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1840,7 +1840,7 @@
      * @hide like {@link #stopService(Intent)} but for a specific user.
      */
     public abstract boolean stopServiceAsUser(Intent service, UserHandle user);
-    
+
     /**
      * Connect to an application service, creating it if needed.  This defines
      * a dependency between your application and the service.  The given
@@ -1989,7 +1989,8 @@
             USER_SERVICE,
             //@hide: APP_OPS_SERVICE
             CAMERA_SERVICE,
-            PRINT_SERVICE
+            PRINT_SERVICE,
+            MEDIA_SESSION_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -2351,6 +2352,15 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
+     * {@link android.media.MediaSessionManager} for managing media Sessions.
+     *
+     * @see #getSystemService
+     * @see android.media.MediaSessionManager
+     */
+    public static final String MEDIA_SESSION_SERVICE = "media_session";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
      * {@link android.telephony.TelephonyManager} for handling management the
      * telephony features of the device.
      *
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index f06ffaa..d27485b 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -969,6 +969,23 @@
             new Key<android.hardware.camera2.Size>("android.sensor.info.pixelArraySize", android.hardware.camera2.Size.class);
 
     /**
+     * <p>Maximum raw value output by sensor.</p>
+     * <p>This specifies the fully-saturated encoding level for the raw
+     * sample values from the sensor.  This is typically caused by the
+     * sensor becoming highly non-linear or clipping. The minimum for
+     * each channel is specified by the offset in the
+     * {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern} tag.</p>
+     * <p>The white level is typically determined either by sensor bit depth
+     * (10-14 bits is expected), or by the point where the sensor response
+     * becomes too non-linear to be useful.  The default value for this is
+     * maximum representable value for a 16-bit raw sample (2^16 - 1).</p>
+     *
+     * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+     */
+    public static final Key<Integer> SENSOR_INFO_WHITE_LEVEL =
+            new Key<Integer>("android.sensor.info.whiteLevel", int.class);
+
+    /**
      * <p>Gain factor from electrons to raw units when
      * ISO=100</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
@@ -985,10 +1002,13 @@
      * <p>A fixed black level offset for each of the color filter arrangement
      * (CFA) mosaic channels.</p>
      * <p>This tag specifies the zero light value for each of the CFA mosaic
-     * channels in the camera sensor.</p>
+     * channels in the camera sensor.  The maximal value output by the
+     * sensor is represented by the value in {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}.</p>
      * <p>The values are given in row-column scan order, with the first value
      * corresponding to the element of the CFA in row=0, column=0.</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
      */
     public static final Key<int[]> SENSOR_BLACK_LEVEL_PATTERN =
             new Key<int[]>("android.sensor.blackLevelPattern", int[].class);
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
index 859468a..abb59e3 100644
--- a/core/java/android/view/GLRenderer.java
+++ b/core/java/android/view/GLRenderer.java
@@ -504,6 +504,10 @@
         if (mGlCanvas != null) {
             mGlCanvas.cancelLayerUpdate(layer);
         }
+        if (Looper.myLooper() == Looper.getMainLooper() && validate()) {
+            long backingLayer = layer.detachBackingLayer();
+            nDestroyLayer(backingLayer);
+        }
         mAttachedLayers.remove(layer);
     }
 
@@ -1449,6 +1453,8 @@
      */
     static native boolean isBackBufferPreserved();
 
+    static native void nDestroyLayer(long layerPtr);
+
     class DrawPerformanceDataProvider extends GraphDataProvider {
         private final int mGraphType;
 
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 30b1e52..7e745d8 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1158,25 +1158,25 @@
      * This mask is set if the device woke because of this key event.
      */
     public static final int FLAG_WOKE_HERE = 0x1;
-    
+
     /**
      * This mask is set if the key event was generated by a software keyboard.
      */
     public static final int FLAG_SOFT_KEYBOARD = 0x2;
-    
+
     /**
      * This mask is set if we don't want the key event to cause us to leave
      * touch mode.
      */
     public static final int FLAG_KEEP_TOUCH_MODE = 0x4;
-    
+
     /**
      * This mask is set if an event was known to come from a trusted part
      * of the system.  That is, the event is known to come from the user,
      * and could not have been spoofed by a third party component.
      */
     public static final int FLAG_FROM_SYSTEM = 0x8;
-    
+
     /**
      * This mask is used for compatibility, to identify enter keys that are
      * coming from an IME whose enter key has been auto-labelled "next" or
@@ -1185,7 +1185,7 @@
      * receiving them.
      */
     public static final int FLAG_EDITOR_ACTION = 0x10;
-    
+
     /**
      * When associated with up key events, this indicates that the key press
      * has been canceled.  Typically this is used with virtual touch screen
@@ -1194,29 +1194,29 @@
      * event and should not perform the action normally associated with the
      * key.  Note that for this to work, the application can not perform an
      * action for a key until it receives an up or the long press timeout has
-     * expired. 
+     * expired.
      */
     public static final int FLAG_CANCELED = 0x20;
-    
+
     /**
      * This key event was generated by a virtual (on-screen) hard key area.
      * Typically this is an area of the touchscreen, outside of the regular
      * display, dedicated to "hardware" buttons.
      */
     public static final int FLAG_VIRTUAL_HARD_KEY = 0x40;
-    
+
     /**
      * This flag is set for the first key repeat that occurs after the
      * long press timeout.
      */
     public static final int FLAG_LONG_PRESS = 0x80;
-    
+
     /**
      * Set when a key event has {@link #FLAG_CANCELED} set because a long
-     * press action was executed while it was down. 
+     * press action was executed while it was down.
      */
     public static final int FLAG_CANCELED_LONG_PRESS = 0x100;
-    
+
     /**
      * Set for {@link #ACTION_UP} when this event's key code is still being
      * tracked from its initial down.  That is, somebody requested that tracking
@@ -1273,7 +1273,7 @@
     public static int getDeadChar(int accent, int c) {
         return KeyCharacterMap.getDeadChar(accent, c);
     }
-    
+
     static final boolean DEBUG = false;
     static final String TAG = "KeyEvent";
 
@@ -1303,10 +1303,10 @@
          * KeyEvent.startTracking()} to have the framework track the event
          * through its {@link #onKeyUp(int, KeyEvent)} and also call your
          * {@link #onKeyLongPress(int, KeyEvent)} if it occurs.
-         * 
+         *
          * @param keyCode The value in event.getKeyCode().
          * @param event Description of the key event.
-         * 
+         *
          * @return If you handled the event, return true.  If you want to allow
          *         the event to be handled by the next receiver, return false.
          */
@@ -1319,10 +1319,10 @@
          * order to receive this callback, someone in the event change
          * <em>must</em> return true from {@link #onKeyDown} <em>and</em>
          * call {@link KeyEvent#startTracking()} on the event.
-         * 
+         *
          * @param keyCode The value in event.getKeyCode().
          * @param event Description of the key event.
-         * 
+         *
          * @return If you handled the event, return true.  If you want to allow
          *         the event to be handled by the next receiver, return false.
          */
@@ -1330,10 +1330,10 @@
 
         /**
          * Called when a key up event has occurred.
-         * 
+         *
          * @param keyCode The value in event.getKeyCode().
          * @param event Description of the key event.
-         * 
+         *
          * @return If you handled the event, return true.  If you want to allow
          *         the event to be handled by the next receiver, return false.
          */
@@ -1342,11 +1342,11 @@
         /**
          * Called when multiple down/up pairs of the same key have occurred
          * in a row.
-         * 
+         *
          * @param keyCode The value in event.getKeyCode().
          * @param count Number of pairs as returned by event.getRepeatCount().
          * @param event Description of the key event.
-         * 
+         *
          * @return If you handled the event, return true.  If you want to allow
          *         the event to be handled by the next receiver, return false.
          */
@@ -1362,7 +1362,7 @@
 
     /**
      * Create a new key event.
-     * 
+     *
      * @param action Action code: either {@link #ACTION_DOWN},
      * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
      * @param code The key code.
@@ -1376,7 +1376,7 @@
 
     /**
      * Create a new key event.
-     * 
+     *
      * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
      * at which this key code originally went down.
      * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1399,7 +1399,7 @@
 
     /**
      * Create a new key event.
-     * 
+     *
      * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
      * at which this key code originally went down.
      * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1424,7 +1424,7 @@
 
     /**
      * Create a new key event.
-     * 
+     *
      * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
      * at which this key code originally went down.
      * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1453,7 +1453,7 @@
 
     /**
      * Create a new key event.
-     * 
+     *
      * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
      * at which this key code originally went down.
      * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1484,7 +1484,7 @@
 
     /**
      * Create a new key event.
-     * 
+     *
      * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
      * at which this key code originally went down.
      * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1520,7 +1520,7 @@
      * action, repeat count and source will automatically be set to
      * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, 0, and
      * {@link InputDevice#SOURCE_KEYBOARD} for you.
-     * 
+     *
      * @param time The time (in {@link android.os.SystemClock#uptimeMillis})
      * at which this event occured.
      * @param characters The string of characters.
@@ -1558,10 +1558,10 @@
 
     /**
      * Copy an existing key event, modifying its time and repeat count.
-     * 
+     *
      * @deprecated Use {@link #changeTimeRepeat(KeyEvent, long, int)}
      * instead.
-     * 
+     *
      * @param origEvent The existing event to be copied.
      * @param eventTime The new event time
      * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
@@ -1677,7 +1677,7 @@
     /**
      * Create a new key event that is the same as the given one, but whose
      * event time and repeat count are replaced with the given value.
-     * 
+     *
      * @param event The existing event to be copied.  This is not modified.
      * @param eventTime The new event time
      * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
@@ -1687,11 +1687,11 @@
             int newRepeat) {
         return new KeyEvent(event, eventTime, newRepeat);
     }
-    
+
     /**
      * Create a new key event that is the same as the given one, but whose
      * event time and repeat count are replaced with the given value.
-     * 
+     *
      * @param event The existing event to be copied.  This is not modified.
      * @param eventTime The new event time
      * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
@@ -1707,10 +1707,10 @@
         ret.mFlags = newFlags;
         return ret;
     }
-    
+
     /**
      * Copy an existing key event, modifying its action.
-     * 
+     *
      * @param origEvent The existing event to be copied.
      * @param action The new action code of the event.
      */
@@ -1732,18 +1732,18 @@
     /**
      * Create a new key event that is the same as the given one, but whose
      * action is replaced with the given value.
-     * 
+     *
      * @param event The existing event to be copied.  This is not modified.
      * @param action The new action code of the event.
      */
     public static KeyEvent changeAction(KeyEvent event, int action) {
         return new KeyEvent(event, action);
     }
-    
+
     /**
      * Create a new key event that is the same as the given one, but whose
      * flags are replaced with the given value.
-     * 
+     *
      * @param event The existing event to be copied.  This is not modified.
      * @param flags The new flags constant.
      */
@@ -1768,7 +1768,7 @@
     /**
      * Don't use in new code, instead explicitly check
      * {@link #getAction()}.
-     * 
+     *
      * @return If the action is ACTION_DOWN, returns true; else false.
      *
      * @deprecated
@@ -1780,7 +1780,7 @@
 
     /**
      * Is this a system key?  System keys can not be used for menu shortcuts.
-     * 
+     *
      * TODO: this information should come from a table somewhere.
      * TODO: should the dpad keys be here?  arguably, because they also shouldn't be menu shortcuts
      */
@@ -1849,6 +1849,30 @@
         }
     }
 
+    /**
+     * Whether this key is a media key, which can be send to apps that are
+     * interested in media key events.
+     *
+     * @hide
+     */
+    public static final boolean isMediaKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MEDIA_PLAY:
+            case KeyEvent.KEYCODE_MEDIA_PAUSE:
+            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+            case KeyEvent.KEYCODE_MUTE:
+            case KeyEvent.KEYCODE_HEADSETHOOK:
+            case KeyEvent.KEYCODE_MEDIA_STOP:
+            case KeyEvent.KEYCODE_MEDIA_NEXT:
+            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+            case KeyEvent.KEYCODE_MEDIA_REWIND:
+            case KeyEvent.KEYCODE_MEDIA_RECORD:
+            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+                return true;
+        }
+        return false;
+    }
+
     /** {@inheritDoc} */
     @Override
     public final int getDeviceId() {
@@ -2318,7 +2342,7 @@
     /**
      * Retrieve the action of this key event.  May be either
      * {@link #ACTION_DOWN}, {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
-     * 
+     *
      * @return The event action: ACTION_DOWN, ACTION_UP, or ACTION_MULTIPLE.
      */
     public final int getAction() {
@@ -2332,7 +2356,7 @@
     public final boolean isCanceled() {
         return (mFlags&FLAG_CANCELED) != 0;
     }
-    
+
     /**
      * Call this during {@link Callback#onKeyDown} to have the system track
      * the key through its final up (possibly including a long press).  Note
@@ -2343,7 +2367,7 @@
     public final void startTracking() {
         mFlags |= FLAG_START_TRACKING;
     }
-    
+
     /**
      * For {@link #ACTION_UP} events, indicates that the event is still being
      * tracked from its initial down event as per
@@ -2352,7 +2376,7 @@
     public final boolean isTracking() {
         return (mFlags&FLAG_TRACKING) != 0;
     }
-    
+
     /**
      * For {@link #ACTION_DOWN} events, indicates that the event has been
      * canceled as per {@link #FLAG_LONG_PRESS}.
@@ -2360,11 +2384,11 @@
     public final boolean isLongPress() {
         return (mFlags&FLAG_LONG_PRESS) != 0;
     }
-    
+
     /**
      * Retrieve the key code of the key event.  This is the physical key that
      * was pressed, <em>not</em> the Unicode character.
-     * 
+     *
      * @return The key code of the event.
      */
     public final int getKeyCode() {
@@ -2375,14 +2399,14 @@
      * For the special case of a {@link #ACTION_MULTIPLE} event with key
      * code of {@link #KEYCODE_UNKNOWN}, this is a raw string of characters
      * associated with the event.  In all other cases it is null.
-     * 
+     *
      * @return Returns a String of 1 or more characters associated with
      * the event.
      */
     public final String getCharacters() {
         return mCharacters;
     }
-    
+
     /**
      * Retrieve the hardware key id of this key event.  These values are not
      * reliable and vary from device to device.
@@ -2399,7 +2423,7 @@
      * events, this is the number of times the key has repeated with the first
      * down starting at 0 and counting up from there.  For multiple key
      * events, this is the number of down/up pairs that have occurred.
-     * 
+     *
      * @return The number of times the key has repeated.
      */
     public final int getRepeatCount() {
@@ -2413,7 +2437,7 @@
      * Note that when chording keys, this value is the down time of the
      * most recently pressed key, which may <em>not</em> be the same physical
      * key of this event.
-     * 
+     *
      * @return Returns the most recent key down time, in the
      * {@link android.os.SystemClock#uptimeMillis} time base
      */
@@ -2425,7 +2449,7 @@
      * Retrieve the time this event occurred,
      * in the {@link android.os.SystemClock#uptimeMillis} time base.
      *
-     * @return Returns the time this event occurred, 
+     * @return Returns the time this event occurred,
      * in the {@link android.os.SystemClock#uptimeMillis} time base.
      */
     @Override
@@ -2454,7 +2478,7 @@
 
     /**
      * Renamed to {@link #getDeviceId}.
-     * 
+     *
      * @hide
      * @deprecated use {@link #getDeviceId()} instead.
      */
@@ -2486,7 +2510,7 @@
     public char getDisplayLabel() {
         return getKeyCharacterMap().getDisplayLabel(mKeyCode);
     }
-    
+
     /**
      * Gets the Unicode character generated by the specified key and meta
      * key state combination.
@@ -2509,7 +2533,7 @@
     public int getUnicodeChar() {
         return getUnicodeChar(mMetaState);
     }
-    
+
     /**
      * Gets the Unicode character generated by the specified key and meta
      * key state combination.
@@ -2533,7 +2557,7 @@
     public int getUnicodeChar(int metaState) {
         return getKeyCharacterMap().get(mKeyCode, metaState);
     }
-    
+
     /**
      * Get the character conversion data for a given key code.
      *
@@ -2548,7 +2572,7 @@
     public boolean getKeyData(KeyData results) {
         return getKeyCharacterMap().getKeyData(mKeyCode, results);
     }
-    
+
     /**
      * Gets the first character in the character array that can be generated
      * by the specified key code.
@@ -2563,7 +2587,7 @@
     public char getMatch(char[] chars) {
         return getMatch(chars, 0);
     }
-    
+
     /**
      * Gets the first character in the character array that can be generated
      * by the specified key code.  If there are multiple choices, prefers
@@ -2576,7 +2600,7 @@
     public char getMatch(char[] chars, int metaState) {
         return getKeyCharacterMap().getMatch(mKeyCode, chars, metaState);
     }
-    
+
     /**
      * Gets the number or symbol associated with the key.
      * <p>
@@ -2600,7 +2624,7 @@
     public char getNumber() {
         return getKeyCharacterMap().getNumber(mKeyCode);
     }
-    
+
     /**
      * Returns true if this key produces a glyph.
      *
@@ -2609,7 +2633,7 @@
     public boolean isPrintingKey() {
         return getKeyCharacterMap().isPrintingKey(mKeyCode);
     }
-    
+
     /**
      * @deprecated Use {@link #dispatch(Callback, DispatcherState, Object)} instead.
      */
@@ -2617,16 +2641,16 @@
     public final boolean dispatch(Callback receiver) {
         return dispatch(receiver, null, null);
     }
-    
+
     /**
      * Deliver this key event to a {@link Callback} interface.  If this is
      * an ACTION_MULTIPLE event and it is not handled, then an attempt will
      * be made to deliver a single normal event.
-     * 
+     *
      * @param receiver The Callback that will be given the event.
      * @param state State information retained across events.
      * @param target The target of the dispatch, for use in tracking.
-     * 
+     *
      * @return The return value from the Callback method that was called.
      */
     public final boolean dispatch(Callback receiver, DispatcherState state,
@@ -2692,7 +2716,7 @@
         int mDownKeyCode;
         Object mDownTarget;
         SparseIntArray mActiveLongPresses = new SparseIntArray();
-        
+
         /**
          * Reset back to initial state.
          */
@@ -2702,7 +2726,7 @@
             mDownTarget = null;
             mActiveLongPresses.clear();
         }
-        
+
         /**
          * Stop any tracking associated with this target.
          */
@@ -2713,14 +2737,14 @@
                 mDownTarget = null;
             }
         }
-        
+
         /**
          * Start tracking the key code associated with the given event.  This
          * can only be called on a key down.  It will allow you to see any
          * long press associated with the key, and will result in
          * {@link KeyEvent#isTracking} return true on the long press and up
          * events.
-         * 
+         *
          * <p>This is only needed if you are directly dispatching events, rather
          * than handling them in {@link Callback#onKeyDown}.
          */
@@ -2733,7 +2757,7 @@
             mDownKeyCode = event.getKeyCode();
             mDownTarget = target;
         }
-        
+
         /**
          * Return true if the key event is for a key code that is currently
          * being tracked by the dispatcher.
@@ -2741,7 +2765,7 @@
         public boolean isTracking(KeyEvent event) {
             return mDownKeyCode == event.getKeyCode();
         }
-        
+
         /**
          * Keep track of the given event's key code as having performed an
          * action with a long press, so no action should occur on the up.
@@ -2751,7 +2775,7 @@
         public void performedLongPress(KeyEvent event) {
             mActiveLongPresses.put(event.getKeyCode(), 1);
         }
-        
+
         /**
          * Handle key up event to stop tracking.  This resets the dispatcher state,
          * and updates the key event state based on it.
@@ -2906,12 +2930,12 @@
             return new KeyEvent[size];
         }
     };
-    
+
     /** @hide */
     public static KeyEvent createFromParcelBody(Parcel in) {
         return new KeyEvent(in);
     }
-    
+
     private KeyEvent(Parcel in) {
         mDeviceId = in.readInt();
         mSource = in.readInt();
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 97a1f21..5a8d2c8 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -39,9 +39,11 @@
     private static native void nativeDestroy(long nativeObject);
 
     private static native Bitmap nativeScreenshot(IBinder displayToken,
-            int width, int height, int minLayer, int maxLayer, boolean allLayers);
+            int width, int height, int minLayer, int maxLayer, boolean allLayers,
+            boolean useIdentityTransform);
     private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
-            int width, int height, int minLayer, int maxLayer, boolean allLayers);
+            int width, int height, int minLayer, int maxLayer, boolean allLayers,
+            boolean useIdentityTransform);
 
     private static native void nativeOpenTransaction();
     private static native void nativeCloseTransaction();
@@ -554,10 +556,15 @@
      * include in the screenshot.
      * @param maxLayer The highest (top-most Z order) surface layer to
      * include in the screenshot.
+     * @param useIdentityTransform Replace whatever transformation (rotation,
+     * scaling, translation) the surface layers are currently using with the
+     * identity transformation while taking the screenshot.
      */
     public static void screenshot(IBinder display, Surface consumer,
-            int width, int height, int minLayer, int maxLayer) {
-        screenshot(display, consumer, width, height, minLayer, maxLayer, false);
+            int width, int height, int minLayer, int maxLayer,
+            boolean useIdentityTransform) {
+        screenshot(display, consumer, width, height, minLayer, maxLayer, false,
+                useIdentityTransform);
     }
 
     /**
@@ -572,7 +579,7 @@
      */
     public static void screenshot(IBinder display, Surface consumer,
             int width, int height) {
-        screenshot(display, consumer, width, height, 0, 0, true);
+        screenshot(display, consumer, width, height, 0, 0, true, false);
     }
 
     /**
@@ -582,7 +589,7 @@
      * @param consumer The {@link Surface} to take the screenshot into.
      */
     public static void screenshot(IBinder display, Surface consumer) {
-        screenshot(display, consumer, 0, 0, 0, 0, true);
+        screenshot(display, consumer, 0, 0, 0, 0, true, false);
     }
 
 
@@ -602,15 +609,20 @@
      * include in the screenshot.
      * @param maxLayer The highest (top-most Z order) surface layer to
      * include in the screenshot.
+     * @param useIdentityTransform Replace whatever transformation (rotation,
+     * scaling, translation) the surface layers are currently using with the
+     * identity transformation while taking the screenshot.
      * @return Returns a Bitmap containing the screen contents, or null
      * if an error occurs. Make sure to call Bitmap.recycle() as soon as
      * possible, once its content is not needed anymore.
      */
-    public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) {
+    public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer,
+            boolean useIdentityTransform) {
         // TODO: should take the display as a parameter
         IBinder displayToken = SurfaceControl.getBuiltInDisplay(
                 SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
-        return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false);
+        return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false,
+                useIdentityTransform);
     }
 
     /**
@@ -629,17 +641,19 @@
         // TODO: should take the display as a parameter
         IBinder displayToken = SurfaceControl.getBuiltInDisplay(
                 SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
-        return nativeScreenshot(displayToken, width, height, 0, 0, true);
+        return nativeScreenshot(displayToken, width, height, 0, 0, true, false);
     }
 
     private static void screenshot(IBinder display, Surface consumer,
-            int width, int height, int minLayer, int maxLayer, boolean allLayers) {
+            int width, int height, int minLayer, int maxLayer, boolean allLayers,
+            boolean useIdentityTransform) {
         if (display == null) {
             throw new IllegalArgumentException("displayToken must not be null");
         }
         if (consumer == null) {
             throw new IllegalArgumentException("consumer must not be null");
         }
-        nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers);
+        nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers,
+                useIdentityTransform);
     }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 089f816..af62d9c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12755,6 +12755,10 @@
         if (mDisplayList != null) {
             mDisplayList.clearDirty();
         }
+
+        if (mBackgroundDisplayList != null) {
+            mBackgroundDisplayList.clearDirty();
+        }
     }
 
     /**
@@ -13073,13 +13077,18 @@
 
     private void cleanupDraw() {
         if (mAttachInfo != null) {
+            // Ensure the display lists are reset when the view root dies.
             if (mDisplayList != null) {
                 mDisplayList.markDirty();
                 mAttachInfo.mViewRootImpl.enqueueDisplayList(mDisplayList);
             }
+            if (mBackgroundDisplayList != null) {
+                mBackgroundDisplayList.markDirty();
+                mAttachInfo.mViewRootImpl.enqueueDisplayList(mBackgroundDisplayList);
+            }
             mAttachInfo.mViewRootImpl.cancelInvalidate(this);
         } else {
-            // Should never happen
+            // Should never happen.
             resetDisplayList();
         }
     }
@@ -15216,12 +15225,6 @@
         }
 
         if (mBackgroundSizeChanged) {
-            // We should see the background invalidate itself, but just to be
-            // careful we're going to clear the display list and force redraw.
-            if (mBackgroundDisplayList != null) {
-                mBackgroundDisplayList.clear();
-            }
-
             background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
             mBackgroundSizeChanged = false;
         }
@@ -15269,10 +15272,6 @@
      * @return A valid display list for the specified drawable
      */
     private static DisplayList getDrawableDisplayList(Drawable drawable, DisplayList displayList) {
-        if (displayList != null && displayList.isValid()) {
-            return displayList;
-        }
-
         if (displayList == null) {
             displayList = DisplayList.create(drawable.getClass().getName());
         }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 93b95bf6e..7ea0613 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2309,10 +2309,16 @@
         // data and discard the view if we fail.
         final View transientView = mRecycler.getTransientStateView(position);
         if (transientView != null) {
-            final View updatedView = mAdapter.getView(position, transientView, this);
-            if (updatedView != transientView) {
-                // Failed to re-bind the data, scrap the obtained view.
-                mRecycler.addScrapView(updatedView, position);
+            final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
+
+            // If the view type hasn't changed, attempt to re-bind the data.
+            if (params.viewType == mAdapter.getItemViewType(position)) {
+                final View updatedView = mAdapter.getView(position, transientView, this);
+
+                // If we failed to re-bind the data, scrap the obtained view.
+                if (updatedView != transientView) {
+                    mRecycler.addScrapView(updatedView, position);
+                }
             }
 
             // Scrap view implies temporary detachment.
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index a7a7e7e..7640749 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -128,16 +128,20 @@
 
     private Handler mHandler;
 
-    View.OnClickListener mButtonHandler = new View.OnClickListener() {
+    private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
+        @Override
         public void onClick(View v) {
-            Message m = null;
+            final Message m;
             if (v == mButtonPositive && mButtonPositiveMessage != null) {
                 m = Message.obtain(mButtonPositiveMessage);
             } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
                 m = Message.obtain(mButtonNegativeMessage);
             } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
                 m = Message.obtain(mButtonNeutralMessage);
+            } else {
+                m = null;
             }
+
             if (m != null) {
                 m.sendToTarget();
             }
@@ -425,12 +429,13 @@
             customView = null;
         }
 
-        if (customView == null || !canTextInput(customView)) {
+        final boolean hasCustomView = customView != null;
+        if (!hasCustomView || !canTextInput(customView)) {
             mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                     WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
         }
 
-        if (customView != null) {
+        if (hasCustomView) {
             final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
             custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
 
@@ -445,12 +450,11 @@
         } else {
             customPanel.setVisibility(View.GONE);
         }
-        
-        /* Only display the divider if we have a title and a 
-         * custom view or a message.
-         */
+
+        // Only display the divider if we have a title and a custom view or a
+        // message.
         if (hasTitle) {
-            View divider = null;
+            final View divider;
             if (mMessage != null || customView != null || mListView != null) {
                 divider = mWindow.findViewById(R.id.titleDivider);
             } else {
@@ -461,8 +465,9 @@
                 divider.setVisibility(View.VISIBLE);
             }
         }
-        
-        setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel);
+
+        setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, hasTitle, hasCustomView,
+                hasButtons);
         a.recycle();
     }
 
@@ -620,76 +625,64 @@
         }
     }
 
-    private void setBackground(LinearLayout topPanel, LinearLayout contentPanel,
-            View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle, 
-            View buttonPanel) {
-        
-        /* Get all the different background required */
-        int fullDark = a.getResourceId(
-                R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
-        int topDark = a.getResourceId(
-                R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
-        int centerDark = a.getResourceId(
-                R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
-        int bottomDark = a.getResourceId(
-                R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
-        int fullBright = a.getResourceId(
-                R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
-        int topBright = a.getResourceId(
+    private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel,
+            View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) {
+        final int topBright = a.getResourceId(
                 R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright);
-        int centerBright = a.getResourceId(
+        final int topDark = a.getResourceId(
+                R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
+        final int centerBright = a.getResourceId(
                 R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright);
-        int bottomBright = a.getResourceId(
-                R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
-        int bottomMedium = a.getResourceId(
-                R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
-        
-        /*
-         * We now set the background of all of the sections of the alert.
+        final int centerDark = a.getResourceId(
+                R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
+
+        /* We now set the background of all of the sections of the alert.
          * First collect together each section that is being displayed along
          * with whether it is on a light or dark background, then run through
          * them setting their backgrounds.  This is complicated because we need
          * to correctly use the full, top, middle, and bottom graphics depending
          * on how many views they are and where they appear.
          */
-        
-        View[] views = new View[4];
-        boolean[] light = new boolean[4];
+
+        final View[] views = new View[4];
+        final boolean[] light = new boolean[4];
         View lastView = null;
         boolean lastLight = false;
-        
+
         int pos = 0;
         if (hasTitle) {
             views[pos] = topPanel;
             light[pos] = false;
             pos++;
         }
-        
+
         /* The contentPanel displays either a custom text message or
          * a ListView. If it's text we should use the dark background
          * for ListView we should use the light background. If neither
          * are there the contentPanel will be hidden so set it as null.
          */
-        views[pos] = (contentPanel.getVisibility() == View.GONE) 
-                ? null : contentPanel;
+        views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel;
         light[pos] = mListView != null;
         pos++;
-        if (customPanel != null) {
+
+        if (hasCustomView) {
             views[pos] = customPanel;
             light[pos] = mForceInverseBackground;
             pos++;
         }
+
         if (hasButtons) {
             views[pos] = buttonPanel;
             light[pos] = true;
         }
-        
+
         boolean setView = false;
-        for (pos=0; pos<views.length; pos++) {
-            View v = views[pos];
+        for (pos = 0; pos < views.length; pos++) {
+            final View v = views[pos];
             if (v == null) {
                 continue;
             }
+
             if (lastView != null) {
                 if (!setView) {
                     lastView.setBackgroundResource(lastLight ? topBright : topDark);
@@ -698,23 +691,34 @@
                 }
                 setView = true;
             }
+
             lastView = v;
             lastLight = light[pos];
         }
-        
+
         if (lastView != null) {
             if (setView) {
-                
-                /* ListViews will use the Bright background but buttons use
-                 * the Medium background.
-                 */ 
+                final int bottomBright = a.getResourceId(
+                        R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
+                final int bottomMedium = a.getResourceId(
+                        R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
+                final int bottomDark = a.getResourceId(
+                        R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
+
+                // ListViews will use the Bright background, but buttons use the
+                // Medium background.
                 lastView.setBackgroundResource(
                         lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
             } else {
+                final int fullBright = a.getResourceId(
+                        R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
+                final int fullDark = a.getResourceId(
+                        R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
+
                 lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
             }
         }
-        
+
         /* TODO: uncomment section below. The logic for this should be if 
          * it's a Contextual menu being displayed AND only a Cancel button 
          * is shown then do this.
@@ -739,12 +743,14 @@
             mListView.addFooterView(buttonPanel);
             */
 //        }
-        
-        if ((mListView != null) && (mAdapter != null)) {
-            mListView.setAdapter(mAdapter);
-            if (mCheckedItem > -1) {
-                mListView.setItemChecked(mCheckedItem, true);
-                mListView.setSelection(mCheckedItem);
+
+        final ListView listView = mListView;
+        if (listView != null && mAdapter != null) {
+            listView.setAdapter(mAdapter);
+            final int checkedItem = mCheckedItem;
+            if (checkedItem > -1) {
+                listView.setItemChecked(checkedItem, true);
+                listView.setSelection(checkedItem);
             }
         }
     }
@@ -969,7 +975,8 @@
             
             if (mOnClickListener != null) {
                 listView.setOnItemClickListener(new OnItemClickListener() {
-                    public void onItemClick(AdapterView parent, View v, int position, long id) {
+                    @Override
+                    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
                         mOnClickListener.onClick(dialog.mDialogInterface, position);
                         if (!mIsSingleChoice) {
                             dialog.mDialogInterface.dismiss();
@@ -978,7 +985,8 @@
                 });
             } else if (mOnCheckboxClickListener != null) {
                 listView.setOnItemClickListener(new OnItemClickListener() {
-                    public void onItemClick(AdapterView parent, View v, int position, long id) {
+                    @Override
+                    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
                         if (mCheckedItems != null) {
                             mCheckedItems[position] = listView.isItemChecked(position);
                         }
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index 03fa9b4..c2d22dd 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.ColorFilter;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.ActionMode;
@@ -50,7 +51,8 @@
     public ActionBarContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        setBackgroundDrawable(null);
+        // Set a transparent background so that we project appropriately.
+        setBackground(new ActionBarBackgroundDrawable());
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.ActionBar);
@@ -242,24 +244,6 @@
     }
 
     @Override
-    public void onDraw(Canvas canvas) {
-        if (getWidth() == 0 || getHeight() == 0) {
-            return;
-        }
-
-        if (mIsSplit) {
-            if (mSplitBackground != null) mSplitBackground.draw(canvas);
-        } else {
-            if (mBackground != null) {
-                mBackground.draw(canvas);
-            }
-            if (mStackedBackground != null && mIsStacked) {
-                mStackedBackground.draw(canvas);
-            }
-        }
-    }
-
-    @Override
     public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
         // No starting an action mode for an action bar child! (Where would it go?)
         return null;
@@ -290,12 +274,13 @@
     public void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
-        final boolean hasTabs = mTabContainer != null && mTabContainer.getVisibility() != GONE;
+        final View tabContainer = mTabContainer;
+        final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE;
 
-        if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
+        if (tabContainer != null && tabContainer.getVisibility() != GONE) {
             final int containerHeight = getMeasuredHeight();
-            final int tabHeight = mTabContainer.getMeasuredHeight();
-            mTabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
+            final int tabHeight = tabContainer.getMeasuredHeight();
+            tabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
         }
 
         boolean needsInvalidate = false;
@@ -306,13 +291,15 @@
             }
         } else {
             if (mBackground != null) {
-                mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
-                        mActionBarView.getRight(), mActionBarView.getBottom());
+                final ActionBarView actionBarView = mActionBarView;
+                mBackground.setBounds(actionBarView.getLeft(), actionBarView.getTop(),
+                        actionBarView.getRight(), actionBarView.getBottom());
                 needsInvalidate = true;
             }
-            if ((mIsStacked = hasTabs && mStackedBackground != null)) {
-                mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
-                        mTabContainer.getRight(), mTabContainer.getBottom());
+            mIsStacked = hasTabs;
+            if (hasTabs && mStackedBackground != null) {
+                mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(),
+                        tabContainer.getRight(), tabContainer.getBottom());
                 needsInvalidate = true;
             }
         }
@@ -321,4 +308,37 @@
             invalidate();
         }
     }
+
+    /**
+     * Dummy drawable so that we don't break background display lists and
+     * projection surfaces.
+     */
+    private class ActionBarBackgroundDrawable extends Drawable {
+        @Override
+        public void draw(Canvas canvas) {
+            if (mIsSplit) {
+                if (mSplitBackground != null) mSplitBackground.draw(canvas);
+            } else {
+                if (mBackground != null) {
+                    mBackground.draw(canvas);
+                }
+                if (mStackedBackground != null && mIsStacked) {
+                    mStackedBackground.draw(canvas);
+                }
+            }
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter cf) {
+        }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+    }
 }
diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java
index 194a084..e374563 100644
--- a/core/java/com/android/server/SystemService.java
+++ b/core/java/com/android/server/SystemService.java
@@ -49,9 +49,32 @@
      * Boot Phases
      */
     public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency?
+
+    /**
+     * After receiving this boot phase, services can obtain lock settings data.
+     */
     public static final int PHASE_LOCK_SETTINGS_READY = 480;
+
+    /**
+     * After receiving this boot phase, services can safely call into core system services
+     * such as the PowerManager or PackageManager.
+     */
     public static final int PHASE_SYSTEM_SERVICES_READY = 500;
+
+    /**
+     * After receiving this boot phase, services can broadcast Intents.
+     */
+    public static final int PHASE_ACTIVITY_MANAGER_READY = 550;
+
+    /**
+     * After receiving this boot phase, services can start/bind to third party apps.
+     * Apps will be able to make Binder calls into services at this point.
+     */
     public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600;
+
+    /**
+     * After receiving this boot phase, services must have finished all boot-related work.
+     */
     public static final int PHASE_BOOT_COMPLETE = 1000;
 
     private final Context mContext;
diff --git a/core/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java
index 6c853be..eb8df0e 100644
--- a/core/java/com/android/server/SystemServiceManager.java
+++ b/core/java/com/android/server/SystemServiceManager.java
@@ -45,18 +45,13 @@
     }
 
     /**
-     * Starts a service by name if the class exists, otherwise ignores it.
+     * Starts a service by class name.
      *
-     * @return The service instance, or null if not found.
+     * @return The service instance.
      */
     @SuppressWarnings("unchecked")
-    public SystemService startServiceIfExists(String className) {
-        try {
-            return startService((Class<SystemService>)Class.forName(className));
-        } catch (ClassNotFoundException cnfe) {
-            Slog.i(TAG, className + " not available, ignoring.");
-            return null;
-        }
+    public SystemService startService(String className) throws ClassNotFoundException {
+        return startService((Class<SystemService>) Class.forName(className));
     }
 
     /**
diff --git a/core/jni/android_view_GLRenderer.cpp b/core/jni/android_view_GLRenderer.cpp
index 7cf93d0..5c5b52c 100644
--- a/core/jni/android_view_GLRenderer.cpp
+++ b/core/jni/android_view_GLRenderer.cpp
@@ -28,6 +28,7 @@
 
 #include <Caches.h>
 #include <Extensions.h>
+#include <LayerRenderer.h>
 
 #ifdef USE_OPENGL_RENDERER
     EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
@@ -131,6 +132,13 @@
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
+static void android_view_GLRenderer_destroyLayer(JNIEnv* env, jobject clazz,
+        jlong layerPtr) {
+    using namespace android::uirenderer;
+    Layer* layer = reinterpret_cast<Layer*>(layerPtr);
+    LayerRenderer::destroyLayer(layer);
+}
+
 #endif // USE_OPENGL_RENDERER
 
 // ----------------------------------------------------------------------------
@@ -160,6 +168,7 @@
     { "beginFrame",            "([I)V", (void*) android_view_GLRenderer_beginFrame },
 
     { "getSystemTime",         "()J",   (void*) android_view_GLRenderer_getSystemTime },
+    { "nDestroyLayer",         "(J)V",  (void*) android_view_GLRenderer_destroyLayer },
 #endif
 
     { "setupShadersDiskCache", "(Ljava/lang/String;)V",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 88ec0d7..480d0ac 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -72,12 +72,15 @@
     }
 
     status_t update(const sp<IBinder>& display, int width, int height,
-            int minLayer, int maxLayer, bool allLayers) {
+            int minLayer, int maxLayer, bool allLayers,
+            bool useIdentityTransform) {
         status_t res = (width > 0 && height > 0)
                 ? (allLayers
-                        ? mScreenshot.update(display, width, height)
-                        : mScreenshot.update(display, width, height, minLayer, maxLayer))
-                : mScreenshot.update(display);
+                        ? mScreenshot.update(display, width, height,
+                                useIdentityTransform)
+                        : mScreenshot.update(display, width, height,
+                                minLayer, maxLayer, useIdentityTransform))
+                : mScreenshot.update(display, useIdentityTransform);
         if (res != NO_ERROR) {
             return res;
         }
@@ -162,7 +165,8 @@
 }
 
 static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject displayTokenObj,
-        jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) {
+        jint width, jint height, jint minLayer, jint maxLayer, bool allLayers,
+        bool useIdentityTransform) {
     sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
     if (displayToken == NULL) {
         return NULL;
@@ -170,7 +174,7 @@
 
     ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
     if (pixels->update(displayToken, width, height,
-            minLayer, maxLayer, allLayers) != NO_ERROR) {
+            minLayer, maxLayer, allLayers, useIdentityTransform) != NO_ERROR) {
         delete pixels;
         return NULL;
     }
@@ -202,7 +206,8 @@
 
 static void nativeScreenshot(JNIEnv* env, jclass clazz,
         jobject displayTokenObj, jobject surfaceObj,
-        jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) {
+        jint width, jint height, jint minLayer, jint maxLayer, bool allLayers,
+        bool useIdentityTransform) {
     sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
     if (displayToken != NULL) {
         sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj);
@@ -213,7 +218,8 @@
             }
             ScreenshotClient::capture(
                     displayToken, consumer->getIGraphicBufferProducer(),
-                    width, height, uint32_t(minLayer), uint32_t(maxLayer));
+                    width, height, uint32_t(minLayer), uint32_t(maxLayer),
+                    useIdentityTransform);
         }
     }
 }
@@ -417,9 +423,9 @@
             (void*)nativeRelease },
     {"nativeDestroy", "(J)V",
             (void*)nativeDestroy },
-    {"nativeScreenshot", "(Landroid/os/IBinder;IIIIZ)Landroid/graphics/Bitmap;",
+    {"nativeScreenshot", "(Landroid/os/IBinder;IIIIZZ)Landroid/graphics/Bitmap;",
             (void*)nativeScreenshotBitmap },
-    {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;IIIIZ)V",
+    {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;IIIIZZ)V",
             (void*)nativeScreenshot },
     {"nativeOpenTransaction", "()V",
             (void*)nativeOpenTransaction },
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 4a0c78a..6b12c26 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"Permite que la aplicación use cualquier decodificador de archivos multimedia instalado para la reproducción."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"administrar credenciales de confianza"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"Permite que la aplicación instale y desinstale certificados de CA como credenciales de confianza."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"ejecutar la aplicación durante el tiempo de inactividad"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Con este permiso, el sistema Android puede ejecutar la aplicación en segundo plano mientras el dispositivo no está en uso."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"leer y escribir a recursos dentro del grupo de diagnóstico"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"Permite que la aplicación lea y escriba en cualquier recurso propiedad del grupo de diagnóstico como, por ejemplo, archivos in/dev. Este permiso podría afectar la seguridad y estabilidad del sistema. SOLO se debe utilizar para diagnósticos específicos de hardware realizados por el fabricante o el operador."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"activar o desactivar componentes de la aplicación"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 8ce67fd..1fed273 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"‏اجازه می‎دهد برنامه از هر رمزگشای رسانه نصب شده‌ای استفاده کند تا برای پخش رمزگشایی شود."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"مدیریت اطلاعات کاربری مورد اعتماد"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"‏به برنامه امکان می‌دهد گواهینامه‌های CA را به عنوان اطلاعات کاربری مورد اعتماد نصب یا حذف نصب کند."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"اجرای برنامه در هنگام بی‌حرکت بودن دستگاه"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"‏این مجوز به سیستم Android امکان می‌دهد تا وقتی دستگاه استفاده نمی‌شود برنامه را در پس‌زمینه اجرا کند."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"خواندن/نوشتن منابع متعلق به تشخیص"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"‏به برنامه اجازه می‌دهد هر منبعی را که متعلق به گروه تشخیص است بخواند و در آن بنویسد؛ به‌عنوان مثال، فایل‌های /dev. این امر به‌صورت بالقوه می‌تواند بر پایدار بودن و امنیت سیستم تأثیر بگذارد. این تنها باید برای تشخیص‎‌های مختص سخت‌افزار توسط تولیدکننده یا اپراتور استفاده شود."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"فعال یا غیر فعال کردن اجزای برنامه"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 602b513..cbec12b 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"Sallii sovelluksen käyttää mitä tahansa asennettua tietovälineen koodin purkajaa toistoa varten."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"hallinnoi luotettavia varmenteita"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"Antaa sovellukselle luvan asentaa ja poistaa luotettavia CA-varmenteita."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"suorita sovellus laitteen ollessa käyttämättömänä"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Tämä oikeus sallii Android-järjestelmän siirtää sovelluksen suorituksen taustalle, kun laite ei ole käytössä."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"lue diag:in omistamia resursseja / kirjoita resursseihin"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"Antaa sovelluksen lukea ja kirjoittaa diag-ryhmän omistamiin resursseihin, esimerkiksi /dev-hakemistossa oleviin tiedostoihin. Tämä voi vaikuttaa järjestelmän vakauteen ja turvallisuuteen. Tämä lupa tulee myöntää VAIN valmistajan tai operaattorin laitteistotesteille."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"sovelluskomponenttien ottaminen käyttöön tai pois käytöstä"</string>
@@ -565,10 +563,8 @@
     <string name="permdesc_modifyPhoneState" msgid="1029877529007686732">"Antaa sovelluksen hallita laitteen puhelinominaisuuksia. Jos sovelluksella on tämä oikeus, se voi esimerkiksi vaihtaa verkkoa tai ottaa puhelinradion käyttöön tai poistaa sen käytöstä ilmoittamatta käyttäjälle."</string>
     <string name="permlab_readPhoneState" msgid="9178228524507610486">"lue puhelimen tila ja identiteetti"</string>
     <string name="permdesc_readPhoneState" msgid="1639212771826125528">"Antaa sovelluksen käyttää laitteen puhelinominaisuuksia. Sovellus voi määrittää puhelinnumeron ja laitteen tunnuksen, puhelun tilan sekä soitetun numeron."</string>
-    <!-- no translation found for permlab_readPrecisePhoneState (5476483020282007597) -->
-    <skip />
-    <!-- no translation found for permdesc_readPrecisePhoneState (6648009074263855418) -->
-    <skip />
+    <string name="permlab_readPrecisePhoneState" msgid="5476483020282007597">"lue puhelimen tarkat tilat"</string>
+    <string name="permdesc_readPrecisePhoneState" msgid="6648009074263855418">"Antaa sovelluksen käyttää puhelimen tarkkoja tiloja. Tämän oikeus antaa sovelluksen määrittää puhelun todellisen tilan, eli onko puhelu aktiivinen vai taustalla, puhelujen epäonnistumiset, tietoliikenneyhteyden tarkan tilan ja tietoliikenneyhteyden muodostuksen epäonnistumiset."</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"estä tablet-laitetta menemästä virransäästötilaan"</string>
     <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"estä puhelinta menemästä virransäästötilaan"</string>
     <string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"Antaa sovelluksen estää tablet-laitetta siirtymästä virransäästötilaan."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 679eed1..91749e6 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"Permet à une application d\'utiliser n\'importe quel décodeur installé pour lire les fichiers multimédias."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"gérer les certificats de confiance"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"Permet à l\'application d\'installer et de désinstaller les certificats CA en tant que certificats de confiance."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"exécuter l\'application lorsque l\'appareil est inactif"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Cette autorisation permet au système Android d\'exécuter l\'application en arrière-plan lorsque l\'appareil est inactif."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"lire ou modifier les ressources appartenant au groupe de diagnostics"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"Permet à l\'application d\'obtenir des droits en lecture et en écriture pour toute ressource appartenant au groupe de diagnostics (par exemple, les fichiers du répertoire /dev). Cela peut affecter la stabilité et la sécurité du système. Cette fonctionnalité est UNIQUEMENT réservée aux diagnostics matériels effectués par le fabricant ou le fournisseur de services."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"activer ou désactiver les composants d\'une application"</string>
@@ -565,7 +563,7 @@
     <string name="permdesc_modifyPhoneState" msgid="1029877529007686732">"Permet à l\'application de contrôler les fonctionnalités de téléphonie de l\'appareil. Une application disposant de cette autorisation peut, par exemple, basculer d\'un réseau à l\'autre et activer ou désactiver le signal radio du téléphone à votre insu."</string>
     <string name="permlab_readPhoneState" msgid="9178228524507610486">"voir l\'état et l\'identité du téléphone"</string>
     <string name="permdesc_readPhoneState" msgid="1639212771826125528">"Permet à l\'application d\'accéder aux fonctionnalités téléphoniques de l\'appareil. Cette autorisation permet à l\'application de déterminer le numéro de téléphone et les identifiants de l\'appareil, si un appel est actif et le numéro distant connecté par un appel."</string>
-    <string name="permlab_readPrecisePhoneState" msgid="5476483020282007597">"accéder aux états précis du téléphone"</string>
+    <string name="permlab_readPrecisePhoneState" msgid="5476483020282007597">"Accéder aux états précis du téléphone"</string>
     <string name="permdesc_readPrecisePhoneState" msgid="6648009074263855418">"Permet à l\'application d\'accéder aux états précis du téléphone. Cette autorisation lui permet de déterminer le statut d\'appel réel, si un appel est actif ou en arrière-plan, si des appels ont échoué, l\'état précis de la connexion et si cette dernière a échoué."</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"empêcher la tablette de passer en mode veille"</string>
     <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"empêcher le téléphone de passer en mode veille"</string>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index fabdf87..4c90dbe 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"ອະນຸຍາດໃຫ້ແອັບຯໃຊ້ທຸກຕົວຖອດລະຫັດສື່ທີ່ຕິດຕັ້ງໄວ້ແລ້ວ ເພື່ອການຖອດລະຫັດການຫຼິ້ນໄຟລ໌ຕ່າງໆ."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"ຈັດການໜັງສືຮັບຮອງທີ່ເຊື່ອຖືໄດ້."</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"ອະ​ນຸ​ຍາດ​ໃຫ້ແອັບຯ ຕິດຕັ້ງ ແລະ ຖອນການຕິດຕັ້ງໃບຢັ້ງຢືນ CA ທີ່ເປັນໃບຮັບຮອງທີ່ເຊື່ອຖືໄດ້."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"ເປີດ​ແອັບ​ພ​ລິ​ເຄ​ຊັນ​ໃນ​ເວ​ລາ​ທີ່​ບໍ່​ເຮັດ​ວຽກ"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"ນີ້​ຈະ​ເປັນ​ການອ​ະ​ນຸ​ຍາດ​​ໃຫ້​ລະ​ບົບ Android ສາ​ມາດ​ເປີດ​ແອັບ​ພ​ລິ​ເຄ​ຊັນ​ໃນ​ພື້ນຫຼັງໄດ້​ໃນ​ຂະ​ນະ​ທີ່​ອຸ​ປະ​ກອນບໍ່​​ຖືກ​ນຳ​ໃຊ້."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"ອ່ານ/ຂຽນ ໃສ່ຊັບພະຍາກອນທີ່ເປັນຂອງກຸ່ມວິໄຈ"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນອ່ານ ແລະຂຽນ ໃສ່ທຸກຊັບພະຍາກອນທີ່ເປັນຂອງກຸ່ມວິນິໄສ; ຕົວຢ່າງ: ໄຟລ໌ໃນ /dev. ສິ່ງນີ້ອາດສົ່ງຜົນກະທົບຕໍ່ຄວາມສະຖຽນ ແລະຄວາມປອດໄພຂອງລະບົບ. ສິ່ງນີ້ຄວນໃຊ້ສຳຫຼັບການວິເຄາະບັນຫາຈຳເພາະ ຂອງບາງຮາດແວໂດຍຜູ່ຜະລິດ ຫຼືຜູ່ປະຕິບັດການເທົ່ານັ້ນ."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"ເປີດ ຫຼືປິດນຳໃຊ້ອົງປະກອບຂອງແອັບຯ"</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index f2fed04..d5382ab 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"Membenarkan apl untuk menggunakan sebarang penyahkod media yang dipasangkan untuk menyahkod main semula."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"urus bukti kelayakan yang dipercayai"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"Membenarkan apl memasang dan menyahpasang sijil CA sebagai bukti kelayakan yang dipercayai."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"jalankan aplikasi pada masa melahu"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Kebenaran ini membolehkan sistem Android menjalankan aplikasi di latar belakang semasa peranti tidak digunakan."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"baca/tulis ke sumber yang dimiliki oleh diag"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"Membenarkan apl membaca dan menulis ke sebarang sumber yang dimiliki oleh kumpulan diag; contohnya, fail dalam /dev. Hal ini berpotensi menjejaskan kestabilan dan keselamatan sistem. Perkara ini seharusnya HANYA digunakan untuk diagnosis khusus perkakasan oleh pengilang atau pengendali."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"dayakan atau lumpuhkan komponen apl"</string>
@@ -565,10 +563,8 @@
     <string name="permdesc_modifyPhoneState" msgid="1029877529007686732">"Membenarkan apl untuk mengawal ciri-ciri telefon peranti. Apl dengan kebenaran ini boleh menukar rangkaian, menghidupkan dan mematikan radio telefon dan sebagainya tanpa memberitahu anda."</string>
     <string name="permlab_readPhoneState" msgid="9178228524507610486">"baca status dan identiti telefon"</string>
     <string name="permdesc_readPhoneState" msgid="1639212771826125528">"Membenarkan apl mengakses ciri telefon pada peranti. Kebenaran ini membolehkan apl menentukan nombor telefon dan ID peranti, sama ada panggilan aktif dan nombor jauh yang dihubungkan dengan panggilan."</string>
-    <!-- no translation found for permlab_readPrecisePhoneState (5476483020282007597) -->
-    <skip />
-    <!-- no translation found for permdesc_readPrecisePhoneState (6648009074263855418) -->
-    <skip />
+    <string name="permlab_readPrecisePhoneState" msgid="5476483020282007597">"baca keadaan telefon yang tepat"</string>
+    <string name="permdesc_readPrecisePhoneState" msgid="6648009074263855418">"Membenarkan apl mengakses keadaan telefon yang tepat. Kebenaran ini membolehkan apl menentukan status panggilan sebenar, sama ada panggilan aktif atau di latar belakang, kegagalan panggilan, status sambungan data yang tepat dan kegagalan sambungan data."</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"menghalang tablet daripada tidur"</string>
     <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"halang telefon daripada tidur"</string>
     <string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"Membenarkan apl menghalang tablet daripada tidur."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 046d3ae..e0433a6 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"Permite aplicaţiei să utilizeze orice decodor media instalat pentru a decodifica redarea."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"gestionarea acreditărilor de încredere"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"Permite aplicației să instaleze și să dezinstaleze certificate CA ca acreditări de încredere."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"rulați aplicația în timp ce dispozitivul este inactiv"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Cu această permisiune, sistemul Android poate rula aplicația în fundal în timp ce dispozitivul nu este utilizat."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"citire/scriere în resursele deţinute de diag"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"Permite aplicaţiei să citească şi să scrie în orice resursă deţinută de grupul diag, de ex., fişierele din /dev. Această permisiune ar putea să afecteze stabilitatea şi securitatea sistemului. Permisiunea trebuie utilizată NUMAI de producător sau de operator pentru diagnostice specifice pentru hardware."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"activare sau dezactivare a componentelor aplicaţiei"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index a056ff1..1d5772b 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"Umožňuje aplikácii používať na reprodukciu ľubovoľný nainštalovaný dekódovač na dekódovanie."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"spravovať dôveryhodné poverenia"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"Umožňuje aplikácii inštalovať a odinštalovať certifikáty CA ako dôveryhodné poverenia."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"spustiť aplikáciu počas nečinnosti"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Toto povolenie umožňuje systému Android spustiť aplikáciu na pozadí, keď sa zariadenie nepoužíva."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"čítanie alebo zápis do prostriedkov funkcie diag"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"Umožňuje aplikácii čítať ľubovoľné prostriedky v skupine diag, napr. súbory v priečinku /dev, a zapisovať do nich. Môže dôjsť k ovplyvneniu stability a bezpečnosti systému. Toto nastavenie by mal používať IBA výrobca či operátor na diagnostiku hardvéru."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"povoliť alebo zakázať súčasti aplikácie"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 882fd76..7cbe055 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"Tillåter att appen använder installerade medieavkodare för att avkoda media för uppspelning."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"hantera betrodda uppgifter"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"Tillåter att appen installerar och avinstallerar CA-certifikat som betrodda uppgifter."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"kör appen när enheten är inaktiv"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Med den här behörigheten tillåts Android-systemet att köra appen i bakgrunden när enheten inte används."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"läsa/skriva till resurser som ägs av diag"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"Tillåter att appen läser och skriver till en resurs som ägs av diag-gruppen, till exempel filer i /dev. Detta kan eventuellt påverka systemets stabilitet och säkerhet. Detta bör ENDAST användas av tillverkaren eller operatören för maskinvaruspecifik diagnostik."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"aktivera eller inaktivera appkomponenter"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 7604de7..87afca6 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -100,7 +100,7 @@
     <string name="roamingText4" msgid="8808456682550796530">"Nje ya Jengo"</string>
     <string name="roamingText5" msgid="7604063252850354350">"Urandaji - Mfumo unaopendelewa"</string>
     <string name="roamingText6" msgid="2059440825782871513">"Uzururaji - Mfumo Unaopatikana"</string>
-    <string name="roamingText7" msgid="7112078724097233605">"Uzururaji - Mwenza wa Ushirikiamo"</string>
+    <string name="roamingText7" msgid="7112078724097233605">"Roaming - Alliance Partner"</string>
     <string name="roamingText8" msgid="5989569778604089291">"Uzururaji - Mwenzi wa Thamani"</string>
     <string name="roamingText9" msgid="7969296811355152491">"Uzururaji - Utendajikazi Kamili wa Huduma"</string>
     <string name="roamingText10" msgid="3992906999815316417">"Uzururaji - Utendajikazi Nusi wa Huduma"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 4a9ef4d..08308c7 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -564,7 +564,7 @@
     <string name="permlab_readPhoneState" msgid="9178228524507610486">"อ่านสถานะและข้อมูลระบุตัวตนของโทรศัพท์"</string>
     <string name="permdesc_readPhoneState" msgid="1639212771826125528">"อนุญาตให้แอปพลิเคชันเข้าถึงคุณลักษณะโทรศัพท์ของอุปกรณ์ การอนุญาตนี้ทำให้แอปพลิเคชันสามารถตรวจสอบหมายเลขโทรศัพท์และรหัสอุปกรณ์ ตรวจสอบว่ามีการโทรที่ทำงานอยู่หรือไม่ และตรวจสอบหมายเลขระยะไกลที่เชื่อมต่อด้วยการโทร"</string>
     <string name="permlab_readPrecisePhoneState" msgid="5476483020282007597">"อ่านสถานะที่แม่นยำของโทรศัพท์"</string>
-    <string name="permdesc_readPrecisePhoneState" msgid="6648009074263855418">"ช่วยให้แอปสามารถเข้าถึงสถานะที่แม่นยำของโทรศัพท์ สิทธิ์นี้ช่วยให้แอปสามารถทราบถึงสถานะการโทรที่แท้จริงว่ากำลังมีการโทรอยู่หรือการโทรอยู่ในพื้นหลัง การโทรล้มเหลว สถานะการเชื่อมต่อข้อมูลที่แม่นยำและการเชื่อมต่อข้อมูลล้มเหลว"</string>
+    <string name="permdesc_readPrecisePhoneState" msgid="6648009074263855418">"ช่วยให้แอปสามารถเข้าถึงสถานะที่แม่นยำของโทรศัพท์ สิทธิ์นี้ช่วยให้แอปสามารถทราบถึงสถานะการโทรที่แท้จริงว่ากำลังมีการโทรอยู่หรือการโทรในพื้นหลัง การโทรล้มเหลว สถานะการเชื่อมต่อข้อมูลที่แม่นยำและการเชื่อมต่อข้อมูลล้มเหลว"</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"ป้องกันไม่ให้แท็บเล็ตเข้าสู่โหมดสลีป"</string>
     <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"ป้องกันไม่ให้โทรศัพท์เข้าโหมดสลีป"</string>
     <string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"อนุญาตให้แอปพลิเคชันป้องกันไม่ให้แท็บเล็ตเข้าสู่โหมดสลีป"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 0834e48..35b1241 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"Uygulamaya, oynatma kodunu çözmek için herhangi bir yüklü medya kod çözücüyü kullanma izni verir."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"güvenilen kimlik bilgilerini yönetme"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"Uygulamaya, güvenilir kimlik bilgileri olarak CA sertifikaları yükleme veya sertifikaların yüklemelerini kaldırma izni verir."</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"boşta kaldığında uygulamayı çalıştır"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Bu izin, cihaz kullanımda değilken Android sistemin uygulamayı arka planda çalıştırmasına olanak sağlar."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"sahibi tanılama olan kaynakları oku/bunlara yaz"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"Uygulamaya, tanılama grubunun sahip olduğu tüm kaynaklara (örneğin /dev içindeki dosyalar) okuma ve yazma izni verir. Bu işlevin sistem kararlılığını ve güvenliğini olumsuz etkileme olasılığı vardır. Üretici veya operatör tarafından YALNIZCA donanıma özgü tanılama için kullanılmalıdır."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"uygulama bileşenlerini etkinleştir veya devre dışı bırak"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 34b4e89..3a5f184 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -423,8 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"Дозволяє програмі використовувати будь-який установлений медіа-декодер для декодування з метою відтворення."</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"керувати захищеними обліковими даними"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"Дозволяє програмі встановлювати та видаляти сертифікати центру сертифікації (CA) як захищені облікові дані."</string>
-    <string name="permlab_bindIdleService" msgid="816311765497613780">"запускати програму, коли пристрій неактивний"</string>
-    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Маючи цей дозвіл, система Android може запускати програму у фоновому режимі, коли пристрій не використовується."</string>
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"запускати додаток, коли пристрій неактивний"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"Маючи цей дозвіл, система Android може запускати додаток у фоновому режимі, коли пристрій не використовується."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"чит./зап. на ресури., якими вол. діаг."</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"Дозволяє програмі читати та писати на будь-який ресурс, яким володіє діагностична група; наприклад, у файли в папці /dev. Це потенційно може вплинути на стабільність і безпеку системи. Потрібно використовувати ЛИШЕ для певної діагностики обладнання, яку виконує виробник чи оператор."</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"вмикати чи вимикати компоненти програми"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index e3886ad..9d3879b 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -423,10 +423,8 @@
     <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"允许该应用使用任何已安装的媒体解码器进行解码,以便播放媒体。"</string>
     <string name="permlab_manageCaCertificates" msgid="1678391896786882014">"管理受信任的凭据"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"允许应用安装和卸载 CA 证书(作为受信任的凭据)。"</string>
-    <!-- no translation found for permlab_bindIdleService (816311765497613780) -->
-    <skip />
-    <!-- no translation found for permdesc_bindIdleService (1767538493214100612) -->
-    <skip />
+    <string name="permlab_bindIdleService" msgid="816311765497613780">"在设备处于闲置状态时运行应用"</string>
+    <string name="permdesc_bindIdleService" msgid="1767538493214100612">"当设备处于闲置状态时,此权限允许Android系统在后台运行该应用。"</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"读取/写入诊断所拥有的资源"</string>
     <string name="permdesc_diagnostic" msgid="6608295692002452283">"允许应用读取/写入诊断组拥有的所有资源(例如 /dev 中的文件)。这可能会影响系统的稳定性和安全性。此权限仅供制造商或运营商诊断硬件方面的问题时使用。"</string>
     <string name="permlab_changeComponentState" msgid="6335576775711095931">"启用或停用应用组件"</string>
diff --git a/graphics/java/android/graphics/drawable/RevealDrawable.java b/graphics/java/android/graphics/drawable/RevealDrawable.java
index 38765e8..91de638 100644
--- a/graphics/java/android/graphics/drawable/RevealDrawable.java
+++ b/graphics/java/android/graphics/drawable/RevealDrawable.java
@@ -18,7 +18,6 @@
 
 import android.content.res.Resources;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffXfermode;
@@ -233,16 +232,14 @@
 
         getDrawable(0).draw(canvas);
 
+        final Rect bounds = getBounds();
         final ArrayList<Ripple> activeRipples = mActiveRipples;
-        if (layerCount == 1 || activeRipples == null || activeRipples.isEmpty()) {
+        if (layerCount == 1 || bounds.isEmpty() || activeRipples == null
+                || activeRipples.isEmpty()) {
             // Nothing to reveal, we're done here.
             return;
         }
 
-        final Rect bounds = getBounds();
-        final int width = bounds.width();
-        final int height = bounds.height();
-
         if (mRipplePaint == null) {
             mRipplePaint = new Paint();
             mRipplePaint.setAntiAlias(true);
@@ -260,7 +257,11 @@
                 n--;
             } else {
                 if (layerSaveCount < 0) {
-                    layerSaveCount = canvas.saveLayer(0, 0, width, height, null, 0);
+                    layerSaveCount = canvas.saveLayer(
+                            bounds.left, bounds.top, bounds.right, bounds.bottom, null, 0);
+                    // Ripples must be clipped to bounds, otherwise SRC_IN will
+                    // miss them and we'll get artifacts.
+                    canvas.clipRect(bounds);
                 }
 
                 needsMask |= ripple.draw(canvas, mRipplePaint);
@@ -279,7 +280,8 @@
 
                 // TODO: When Drawable.setXfermode() is supported by all drawables,
                 // we won't need an extra layer.
-                canvas.saveLayer(0, 0, width, height, mMaskingPaint, 0);
+                canvas.saveLayer(
+                        bounds.left, bounds.top, bounds.right, bounds.bottom, mMaskingPaint, 0);
                 getDrawable(1).draw(canvas);
             }
 
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
index 1bfdc4d..f7cd9de 100644
--- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -338,11 +338,16 @@
         return dirtyBounds;
     }
 
-    private static class TouchFeedbackState extends ConstantState {
-        private ColorStateList mColorStateList;
-        private Xfermode mXfermode;
-        private int mTargetDensity;
-        private boolean mProjected;
+    @Override
+    public ConstantState getConstantState() {
+        return mState;
+    }
+
+    static class TouchFeedbackState extends ConstantState {
+        ColorStateList mColorStateList;
+        Xfermode mXfermode;
+        int mTargetDensity;
+        boolean mProjected;
 
         public TouchFeedbackState(TouchFeedbackState orig) {
             if (orig != null) {
diff --git a/media/java/android/media/IMediaController.aidl b/media/java/android/media/IMediaController.aidl
new file mode 100644
index 0000000..fc3525a
--- /dev/null
+++ b/media/java/android/media/IMediaController.aidl
@@ -0,0 +1,34 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.content.Intent;
+import android.media.IMediaControllerCallback;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.KeyEvent;
+
+/**
+ * Interface to a MediaSession in the system.
+ * @hide
+ */
+interface IMediaController {
+    void sendCommand(String command, in Bundle extras);
+    void sendMediaButton(in KeyEvent mediaButton);
+    void registerCallbackListener(in IMediaControllerCallback cb);
+    void unregisterCallbackListener(in IMediaControllerCallback cb);
+    int getPlaybackState();
+}
\ No newline at end of file
diff --git a/media/java/android/media/IMediaControllerCallback.aidl b/media/java/android/media/IMediaControllerCallback.aidl
new file mode 100644
index 0000000..b54d0cf
--- /dev/null
+++ b/media/java/android/media/IMediaControllerCallback.aidl
@@ -0,0 +1,28 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IMediaControllerCallback {
+    void onEvent(String event, in Bundle extras);
+    void onMetadataUpdate(in Bundle metadata);
+    void onPlaybackUpdate(int newState);
+    void onRouteChanged(in Bundle route);
+}
\ No newline at end of file
diff --git a/media/java/android/media/IMediaSession.aidl b/media/java/android/media/IMediaSession.aidl
new file mode 100644
index 0000000..ed71d78
--- /dev/null
+++ b/media/java/android/media/IMediaSession.aidl
@@ -0,0 +1,33 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.IMediaController;
+import android.os.Bundle;
+
+/**
+ * Interface to a MediaSession in the system.
+ * @hide
+ */
+interface IMediaSession {
+    void sendEvent(in Bundle data);
+    IMediaController getMediaSessionToken();
+    void setPlaybackState(int state);
+    void setMetadata(in Bundle metadata);
+    void setRouteState(in Bundle routeState);
+    void setRoute(in Bundle mediaRouteDescriptor);
+    void destroy();
+}
\ No newline at end of file
diff --git a/media/java/android/media/IMediaSessionCallback.aidl b/media/java/android/media/IMediaSessionCallback.aidl
new file mode 100644
index 0000000..3aaf925
--- /dev/null
+++ b/media/java/android/media/IMediaSessionCallback.aidl
@@ -0,0 +1,29 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+/**
+ * @hide
+ */
+oneway interface IMediaSessionCallback {
+    void onCommand(String command, in Bundle extras);
+    void onMediaButton(in Intent mediaRequestIntent);
+    void onRequestRouteChange(in Bundle route);
+}
\ No newline at end of file
diff --git a/media/java/android/media/IMediaSessionManager.aidl b/media/java/android/media/IMediaSessionManager.aidl
new file mode 100644
index 0000000..8bc0c3b
--- /dev/null
+++ b/media/java/android/media/IMediaSessionManager.aidl
@@ -0,0 +1,28 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.IMediaSession;
+import android.media.IMediaSessionCallback;
+import android.os.Bundle;
+
+/**
+ * Interface to the MediaSessionManagerService
+ * @hide
+ */
+interface IMediaSessionManager {
+    IMediaSession createSession(String packageName, in IMediaSessionCallback cb, String tag);
+}
\ No newline at end of file
diff --git a/media/java/android/media/MediaController.java b/media/java/android/media/MediaController.java
new file mode 100644
index 0000000..1e99942
--- /dev/null
+++ b/media/java/android/media/MediaController.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+
+/**
+ * Allows an app to interact with an ongoing media session. Media buttons and
+ * other commands can be sent to the session. A callback may be registered to
+ * receive updates from the session, such as metadata and play state changes.
+ * <p>
+ * A MediaController can be created through {@link MediaSessionManager} if you
+ * hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if
+ * you have a {@link MediaSessionToken} from the session owner.
+ * <p>
+ * MediaController objects are thread-safe.
+ */
+public final class MediaController {
+    private static final String TAG = "MediaController";
+
+    private static final int MESSAGE_EVENT = 1;
+    private static final int MESSAGE_PLAYBACK_STATE = 2;
+    private static final int MESSAGE_METADATA = 3;
+    private static final int MESSAGE_ROUTE = 4;
+
+    private static final String KEY_EVENT = "event";
+    private static final String KEY_EXTRAS = "extras";
+
+    private final IMediaController mSessionBinder;
+
+    private final CallbackStub mCbStub = new CallbackStub();
+    private final ArrayList<Callback> mCbs = new ArrayList<Callback>();
+    private final Object mLock = new Object();
+
+    private boolean mCbRegistered = false;
+
+    /**
+     * If you have a {@link MediaSessionToken} from the owner of the session a
+     * controller can be created directly. It is up to the session creator to
+     * handle token distribution if desired.
+     *
+     * @see MediaSession#getSessionToken()
+     * @param token A token from the creator of the session
+     */
+    public MediaController(MediaSessionToken token) {
+        mSessionBinder = token.getBinder();
+    }
+
+    /**
+     * @hide
+     */
+    public MediaController(IMediaController sessionBinder) {
+        mSessionBinder = sessionBinder;
+    }
+
+    /**
+     * Sends a generic command to the session. It is up to the session creator
+     * to decide what commands and parameters they will support. As such,
+     * commands should only be sent to sessions that the controller owns.
+     *
+     * @param command The command to send
+     * @param params Any parameters to include with the command
+     */
+    public void sendCommand(String command, Bundle params) {
+        if (TextUtils.isEmpty(command)) {
+            throw new IllegalArgumentException("command cannot be null or empty");
+        }
+        try {
+            mSessionBinder.sendCommand(command, params);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Dead object in sendCommand.", e);
+        }
+    }
+
+    /**
+     * Send the specified media button to the session. Only media keys can be
+     * sent using this method.
+     *
+     * @param keycode The media button keycode, such as
+     *            {@link KeyEvent#KEYCODE_MEDIA_PLAY}.
+     */
+    public void sendMediaButton(int keycode) {
+        if (!KeyEvent.isMediaKey(keycode)) {
+            throw new IllegalArgumentException("May only send media buttons through "
+                    + "sendMediaButton");
+        }
+        // TODO do something better than key down/up events
+        KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keycode);
+        try {
+            mSessionBinder.sendMediaButton(event);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Dead object in sendMediaButton", e);
+        }
+    }
+
+    /**
+     * Adds a callback to receive updates from the Session. Updates will be
+     * posted on the caller's thread.
+     *
+     * @param cb The callback object, must not be null
+     */
+    public void addCallback(Callback cb) {
+        addCallback(cb, null);
+    }
+
+    /**
+     * Adds a callback to receive updates from the session. Updates will be
+     * posted on the specified handler.
+     *
+     * @param cb Cannot be null.
+     * @param handler The handler to post updates on, if null the callers thread
+     *            will be used
+     */
+    public void addCallback(Callback cb, Handler handler) {
+        if (handler == null) {
+            handler = new Handler();
+        }
+        synchronized (mLock) {
+            addCallbackLocked(cb, handler);
+        }
+    }
+
+    /**
+     * Stop receiving updates on the specified callback. If an update has
+     * already been posted you may still receive it after calling this method.
+     *
+     * @param cb The callback to remove
+     */
+    public void removeCallback(Callback cb) {
+        synchronized (mLock) {
+            removeCallbackLocked(cb);
+        }
+    }
+
+    /*
+     * @hide
+     */
+    IMediaController getSessionBinder() {
+        return mSessionBinder;
+    }
+
+    private void addCallbackLocked(Callback cb, Handler handler) {
+        if (cb == null) {
+            throw new IllegalArgumentException("Callback cannot be null");
+        }
+        if (handler == null) {
+            throw new IllegalArgumentException("Handler cannot be null");
+        }
+        if (mCbs.contains(cb)) {
+            Log.w(TAG, "Callback is already added, ignoring");
+            return;
+        }
+        cb.setHandler(handler);
+        mCbs.add(cb);
+
+        // Only register one cb binder, track callbacks internally and notify
+        if (!mCbRegistered) {
+            try {
+                mSessionBinder.registerCallbackListener(mCbStub);
+                mCbRegistered = true;
+            } catch (RemoteException e) {
+                Log.d(TAG, "Dead object in registerCallback", e);
+            }
+        }
+    }
+
+    private void removeCallbackLocked(Callback cb) {
+        if (cb == null) {
+            throw new IllegalArgumentException("Callback cannot be null");
+        }
+        mCbs.remove(cb);
+
+        if (mCbs.size() == 0 && mCbRegistered) {
+            try {
+                mSessionBinder.unregisterCallbackListener(mCbStub);
+            } catch (RemoteException e) {
+                Log.d(TAG, "Dead object in unregisterCallback", e);
+            }
+            mCbRegistered = false;
+        }
+    }
+
+    private void pushOnEventLocked(String event, Bundle extras) {
+        for (int i = mCbs.size() - 1; i >= 0; i--) {
+            mCbs.get(i).postEvent(event, extras);
+        }
+    }
+
+    private void pushOnMetadataUpdateLocked(Bundle metadata) {
+        for (int i = mCbs.size() - 1; i >= 0; i--) {
+            mCbs.get(i).postMetadataUpdate(metadata);
+        }
+    }
+
+    private void pushOnPlaybackUpdateLocked(int newState) {
+        for (int i = mCbs.size() - 1; i >= 0; i--) {
+            mCbs.get(i).postPlaybackStateChange(newState);
+        }
+    }
+
+    private void pushOnRouteChangedLocked(Bundle routeDescriptor) {
+        for (int i = mCbs.size() - 1; i >= 0; i--) {
+            mCbs.get(i).postRouteChanged(routeDescriptor);
+        }
+    }
+
+    /**
+     * MediaSession callbacks will be posted on the thread that created the
+     * Callback object.
+     */
+    public static abstract class Callback {
+        private Handler mHandler;
+
+        /**
+         * Override to handle custom events sent by the session owner.
+         * Controllers should only handle these for sessions they own.
+         *
+         * @param event
+         */
+        public void onEvent(String event, Bundle extras) {
+        }
+
+        /**
+         * Override to handle updates to the playback state. Valid values are in
+         * {@link RemoteControlClient}. TODO put playstate values somewhere more
+         * generic.
+         *
+         * @param state
+         */
+        public void onPlaybackStateChange(int state) {
+        }
+
+        /**
+         * Override to handle metadata changes for this session's media. The
+         * default supported fields are those in {@link MediaMetadataRetriever}.
+         *
+         * @param metadata
+         */
+        public void onMetadataUpdate(Bundle metadata) {
+        }
+
+        /**
+         * Override to handle route changes for this session.
+         *
+         * @param route
+         */
+        public void onRouteChanged(Bundle route) {
+        }
+
+        private void setHandler(Handler handler) {
+            mHandler = new MessageHandler(handler.getLooper(), this);
+        }
+
+        private void postEvent(String event, Bundle extras) {
+            Bundle eventBundle = new Bundle();
+            eventBundle.putString(KEY_EVENT, event);
+            eventBundle.putBundle(KEY_EXTRAS, extras);
+            Message msg = mHandler.obtainMessage(MESSAGE_EVENT, eventBundle);
+            mHandler.sendMessage(msg);
+        }
+
+        private void postPlaybackStateChange(final int state) {
+            Message msg = mHandler.obtainMessage(MESSAGE_PLAYBACK_STATE, state, 0);
+            mHandler.sendMessage(msg);
+        }
+
+        private void postMetadataUpdate(final Bundle metadata) {
+            Message msg = mHandler.obtainMessage(MESSAGE_METADATA, metadata);
+            mHandler.sendMessage(msg);
+        }
+
+        private void postRouteChanged(final Bundle descriptor) {
+            Message msg = mHandler.obtainMessage(MESSAGE_ROUTE, descriptor);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    private final class CallbackStub extends IMediaControllerCallback.Stub {
+
+        @Override
+        public void onEvent(String event, Bundle extras) throws RemoteException {
+            synchronized (mLock) {
+                pushOnEventLocked(event, extras);
+            }
+        }
+
+        @Override
+        public void onMetadataUpdate(Bundle metadata) throws RemoteException {
+            synchronized (mLock) {
+                pushOnMetadataUpdateLocked(metadata);
+            }
+        }
+
+        @Override
+        public void onPlaybackUpdate(final int newState) throws RemoteException {
+            synchronized (mLock) {
+                pushOnPlaybackUpdateLocked(newState);
+            }
+        }
+
+        @Override
+        public void onRouteChanged(Bundle mediaRouteDescriptor) throws RemoteException {
+            synchronized (mLock) {
+                pushOnRouteChangedLocked(mediaRouteDescriptor);
+            }
+        }
+
+    }
+
+    private final static class MessageHandler extends Handler {
+        private final MediaController.Callback mCb;
+
+        public MessageHandler(Looper looper, MediaController.Callback cb) {
+            super(looper);
+            mCb = cb;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_EVENT:
+                    Bundle eventBundle = (Bundle) msg.obj;
+                    String event = eventBundle.getString(KEY_EVENT);
+                    Bundle extras = eventBundle.getBundle(KEY_EXTRAS);
+                    mCb.onEvent(event, extras);
+                    break;
+                case MESSAGE_PLAYBACK_STATE:
+                    mCb.onPlaybackStateChange(msg.arg1);
+                    break;
+                case MESSAGE_METADATA:
+                    mCb.onMetadataUpdate((Bundle) msg.obj);
+                    break;
+                case MESSAGE_ROUTE:
+                    mCb.onRouteChanged((Bundle) msg.obj);
+            }
+        }
+    }
+
+}
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index 25ab99d..b155cda 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -262,7 +262,7 @@
                 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
                 while (displayIterator.hasNext()) {
                     final DisplayInfoForServer di =
-                            (DisplayInfoForServer) displayIterator.next();
+                            displayIterator.next();
                     if (di.mClientNotifListComp != null) {
                         boolean wasEnabled = di.mEnabled;
                         di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
@@ -538,7 +538,7 @@
             //  evaluated it, traversal order doesn't matter here)
             Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
             while(stackIterator.hasNext()) {
-                FocusRequester fr = (FocusRequester)stackIterator.next();
+                FocusRequester fr = stackIterator.next();
                 if(fr.hasSameClient(clientToRemove)) {
                     Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for "
                             + clientToRemove);
@@ -562,7 +562,7 @@
         //  evaluated it, traversal order doesn't matter here)
         Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
         while(stackIterator.hasNext()) {
-            FocusRequester fr = (FocusRequester)stackIterator.next();
+            FocusRequester fr = stackIterator.next();
             if(fr.hasSameBinder(cb)) {
                 Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for " + cb);
                 stackIterator.remove();
@@ -930,33 +930,11 @@
         }
     }
 
-    protected static boolean isMediaKeyCode(int keyCode) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_MUTE:
-            case KeyEvent.KEYCODE_HEADSETHOOK:
-            case KeyEvent.KEYCODE_MEDIA_PLAY:
-            case KeyEvent.KEYCODE_MEDIA_PAUSE:
-            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
-            case KeyEvent.KEYCODE_MEDIA_STOP:
-            case KeyEvent.KEYCODE_MEDIA_NEXT:
-            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
-            case KeyEvent.KEYCODE_MEDIA_REWIND:
-            case KeyEvent.KEYCODE_MEDIA_RECORD:
-            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
-            case KeyEvent.KEYCODE_MEDIA_CLOSE:
-            case KeyEvent.KEYCODE_MEDIA_EJECT:
-            case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
-                return true;
-            default:
-                return false;
-        }
-    }
-
     private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
         if (keyEvent == null) {
             return false;
         }
-        return MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode());
+        return KeyEvent.isMediaKey(keyEvent.getKeyCode());
     }
 
     /**
@@ -1383,7 +1361,7 @@
         synchronized(mRCStack) {
             final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
             while (displayIterator.hasNext()) {
-                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+                final DisplayInfoForServer di = displayIterator.next();
                 pw.println("  IRCD: " + di.mRcDisplay +
                         "  -- w:" + di.mArtworkExpectedWidth +
                         "  -- h:" + di.mArtworkExpectedHeight +
@@ -1410,7 +1388,7 @@
                 // (using an iterator on the stack so we can safely remove an entry after having
                 //  evaluated it, traversal order doesn't matter here)
                 while(stackIterator.hasNext()) {
-                    RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
+                    RemoteControlStackEntry rcse = stackIterator.next();
                     if (removeAll && packageName.equals(rcse.mMediaIntent.getCreatorPackage())) {
                         // a stack entry is from the package being removed, remove it from the stack
                         stackIterator.remove();
@@ -2075,7 +2053,7 @@
                 // remove the display from the list
                 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
                 while (displayIterator.hasNext()) {
-                    final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+                    final DisplayInfoForServer di = displayIterator.next();
                     if (di.mRcDisplay == mRcDisplay) {
                         if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
                         displayIterator.remove();
@@ -2099,7 +2077,7 @@
     private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) {
         final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
         while (displayIterator.hasNext()) {
-            final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+            final DisplayInfoForServer di = displayIterator.next();
             try {
                 rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
                         di.mArtworkExpectedHeight);
@@ -2137,7 +2115,7 @@
     private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
         final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
         while (displayIterator.hasNext()) {
-            final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+            final DisplayInfoForServer di = displayIterator.next();
             if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
                 return true;
             }
@@ -2216,7 +2194,7 @@
             boolean displayWasPluggedIn = false;
             final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
             while (displayIterator.hasNext() && !displayWasPluggedIn) {
-                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+                final DisplayInfoForServer di = displayIterator.next();
                 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
                     displayWasPluggedIn = true;
                     di.release();
@@ -2258,7 +2236,7 @@
             final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
             boolean artworkSizeUpdate = false;
             while (displayIterator.hasNext() && !artworkSizeUpdate) {
-                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+                final DisplayInfoForServer di = displayIterator.next();
                 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
                     if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
                         di.mArtworkExpectedWidth = w;
@@ -2305,7 +2283,7 @@
             // (display stack traversal order doesn't matter).
             final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
             while (displayIterator.hasNext()) {
-                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+                final DisplayInfoForServer di = displayIterator.next();
                 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
                     di.mWantsPositionSync = wantsSync;
                     rcdRegistered = true;
diff --git a/media/java/android/media/MediaSession.java b/media/java/android/media/MediaSession.java
new file mode 100644
index 0000000..5e5c9fa
--- /dev/null
+++ b/media/java/android/media/MediaSession.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.content.Intent;
+import android.media.IMediaSession;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Allows interaction with media controllers, media routes, volume keys, media
+ * buttons, and transport controls.
+ * <p>
+ * A MediaSession should be created when an app wants to publish media playback
+ * information or negotiate with a media route. In general an app only needs one
+ * session for all playback, though multiple sessions can be created for sending
+ * media to multiple routes or to provide finer grain controls of media.
+ * <p>
+ * A MediaSession is created by calling
+ * {@link MediaSessionManager#createSession(String)}. Once a session is created
+ * apps that have the MEDIA_CONTENT_CONTROL permission can interact with the
+ * session through {@link MediaSessionManager#getActiveSessions()}. The owner of
+ * the session may also use {@link #getSessionToken()} to allow apps without
+ * this permission to create a {@link MediaController} to interact with this
+ * session.
+ * <p>
+ * To receive commands, media keys, and other events a Callback must be set with
+ * {@link #addCallback(Callback)}.
+ * <p>
+ * When an app is finished performing playback it must call {@link #release()}
+ * to clean up the session and notify any controllers.
+ * <p>
+ * MediaSession objects are thread safe
+ */
+public final class MediaSession {
+    private static final String TAG = "MediaSession";
+
+    private static final int MESSAGE_MEDIA_BUTTON = 1;
+    private static final int MESSAGE_COMMAND = 2;
+    private static final int MESSAGE_ROUTE_CHANGE = 3;
+
+    private static final String KEY_COMMAND = "command";
+    private static final String KEY_EXTRAS = "extras";
+
+    private final Object mLock = new Object();
+
+    private final MediaSessionToken mSessionToken;
+    private final IMediaSession mBinder;
+    private final CallbackStub mCbStub;
+
+    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+
+    /**
+     * @hide
+     */
+    public MediaSession(IMediaSession binder, CallbackStub cbStub) {
+        mBinder = binder;
+        mCbStub = cbStub;
+        IMediaController controllerBinder = null;
+        try {
+            controllerBinder = mBinder.getMediaSessionToken();
+        } catch (RemoteException e) {
+            throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
+        }
+        mSessionToken = new MediaSessionToken(controllerBinder);
+    }
+
+    /**
+     * Set the callback to receive updates on.
+     *
+     * @param callback The callback object
+     */
+    public void addCallback(Callback callback) {
+        addCallback(callback, null);
+    }
+
+    public void addCallback(Callback callback, Handler handler) {
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback cannot be null");
+        }
+        synchronized (mLock) {
+            if (mCallbacks.contains(callback)) {
+                Log.w(TAG, "Callback is already added, ignoring");
+            }
+            if (handler == null) {
+                handler = new Handler();
+            }
+            MessageHandler msgHandler = new MessageHandler(handler.getLooper(), callback);
+            callback.setHandler(msgHandler);
+            mCallbacks.add(callback);
+        }
+    }
+
+    public void removeCallback(Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    /**
+     * Publish the current playback state to the system and any controllers.
+     * Valid values are defined in {@link RemoteControlClient}. TODO move play
+     * states somewhere else.
+     *
+     * @param state
+     */
+    public void setPlaybackState(int state) {
+        try {
+            mBinder.setPlaybackState(state);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setPlaybackState: ", e);
+        }
+    }
+
+    /**
+     * This must be called when an app has finished performing playback. If
+     * playback is expected to start again shortly the session can be left open,
+     * but it must be released if your activity or service is being destroyed.
+     */
+    public void release() {
+        try {
+            mBinder.destroy();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in onDestroy: ", e);
+        }
+    }
+
+    /**
+     * Retrieve a token object that can be used by apps to create a
+     * {@link MediaController} for interacting with this session. The owner of
+     * the session is responsible for deciding how to distribute these tokens.
+     *
+     * @return A token that can be used to create a MediaController for this
+     *         session
+     */
+    public MediaSessionToken getSessionToken() {
+        return mSessionToken;
+    }
+
+    private void postCommand(String command, Bundle extras) {
+        Bundle commandBundle = new Bundle();
+        commandBundle.putString(KEY_COMMAND, command);
+        commandBundle.putBundle(KEY_EXTRAS, extras);
+        synchronized (mLock) {
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                Callback cb = mCallbacks.get(i);
+                Message msg = cb.mHandler.obtainMessage(MESSAGE_COMMAND, commandBundle);
+                cb.mHandler.sendMessage(msg);
+            }
+        }
+    }
+
+    private void postMediaButton(Intent mediaButtonIntent) {
+        synchronized (mLock) {
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                Callback cb = mCallbacks.get(i);
+                Message msg = cb.mHandler.obtainMessage(MESSAGE_MEDIA_BUTTON, mediaButtonIntent);
+                cb.mHandler.sendMessage(msg);
+            }
+        }
+    }
+
+    private void postRequestRouteChange(Bundle mediaRouteDescriptor) {
+        synchronized (mLock) {
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                Callback cb = mCallbacks.get(i);
+                Message msg = cb.mHandler.obtainMessage(MESSAGE_ROUTE_CHANGE, mediaRouteDescriptor);
+                cb.mHandler.sendMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Receives commands or updates from controllers and routes. An app can
+     * specify what commands and buttons it supports by setting them on the
+     * MediaSession (TODO).
+     */
+    public abstract static class Callback {
+        private MessageHandler mHandler;
+
+        public Callback() {
+        }
+
+        /**
+         * Called when a media button is pressed and this session has the
+         * highest priority or a controller sends a media button event to the
+         * session. TODO determine if using Intents identical to the ones
+         * RemoteControlClient receives is useful
+         * <p>
+         * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
+         * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
+         *
+         * @param mediaButtonIntent an intent containing the KeyEvent as an
+         *            extra
+         */
+        public void onMediaButton(Intent mediaButtonIntent) {
+        }
+
+        /**
+         * Called when a controller has sent a custom command to this session.
+         * The owner of the session may handle custom commands but is not
+         * required to.
+         *
+         * @param command
+         * @param extras optional
+         */
+        public void onCommand(String command, Bundle extras) {
+        }
+
+        /**
+         * Called when the user has selected a different route to connect to.
+         * The app is responsible for connecting to the new route and migrating
+         * ongoing playback if necessary.
+         *
+         * @param descriptor
+         */
+        public void onRequestRouteChange(Bundle descriptor) {
+        }
+
+        private void setHandler(MessageHandler handler) {
+            mHandler = handler;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static class CallbackStub extends IMediaSessionCallback.Stub {
+        private MediaSession mMediaSession;
+
+        public void setMediaSession(MediaSession session) {
+            mMediaSession = session;
+        }
+
+        @Override
+        public void onCommand(String command, Bundle extras) throws RemoteException {
+            mMediaSession.postCommand(command, extras);
+        }
+
+        @Override
+        public void onMediaButton(Intent mediaButtonIntent) throws RemoteException {
+            mMediaSession.postMediaButton(mediaButtonIntent);
+        }
+
+        @Override
+        public void onRequestRouteChange(Bundle mediaRouteDescriptor) throws RemoteException {
+            mMediaSession.postRequestRouteChange(mediaRouteDescriptor);
+        }
+
+    }
+
+    private class MessageHandler extends Handler {
+        private MediaSession.Callback mCallback;
+
+        public MessageHandler(Looper looper, MediaSession.Callback callback) {
+            super(looper);
+            mCallback = callback;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (mLock) {
+                if (mCallback == null) {
+                    return;
+                }
+                switch (msg.what) {
+                    case MESSAGE_MEDIA_BUTTON:
+                        mCallback.onMediaButton((Intent) msg.obj);
+                        break;
+                    case MESSAGE_COMMAND:
+                        Bundle commandBundle = (Bundle) msg.obj;
+                        String command = commandBundle.getString(KEY_COMMAND);
+                        Bundle extras = commandBundle.getBundle(KEY_EXTRAS);
+                        mCallback.onCommand(command, extras);
+                        break;
+                    case MESSAGE_ROUTE_CHANGE:
+                        mCallback.onRequestRouteChange((Bundle) msg.obj);
+                        break;
+                }
+            }
+            msg.recycle();
+        }
+    }
+}
diff --git a/media/java/android/media/MediaSessionManager.java b/media/java/android/media/MediaSessionManager.java
new file mode 100644
index 0000000..90f0071
--- /dev/null
+++ b/media/java/android/media/MediaSessionManager.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MediaSessionManager allows the creation and control of MediaSessions in the
+ * system. A MediaSession enables publishing information about ongoing media and
+ * interacting with MediaControllers and MediaRoutes.
+ * <p>
+ * Use <code>Context.getSystemService(Context.MEDIA_SESSION_SERVICE)</code> to
+ * get an instance of this class.
+ * <p>
+ *
+ * @see MediaSession
+ * @see MediaController
+ */
+public final class MediaSessionManager {
+    private static final String TAG = "MediaSessionManager";
+
+    private final IMediaSessionManager mService;
+
+    private Context mContext;
+
+    /**
+     * @hide
+     */
+    public MediaSessionManager(Context context) {
+        // Consider rewriting like DisplayManagerGlobal
+        // Decide if we need context
+        mContext = context;
+        IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
+        mService = IMediaSessionManager.Stub.asInterface(b);
+    }
+
+    /**
+     * Creates a new session.
+     *
+     * @param tag A short name for debugging purposes
+     * @return a {@link MediaSession} for the new session
+     */
+    public MediaSession createSession(String tag) {
+        try {
+            MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
+            MediaSession session = new MediaSession(mService
+                    .createSession(mContext.getPackageName(), cbStub, tag), cbStub);
+            cbStub.setMediaSession(session);
+
+            return session;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to create session: ", e);
+            return null;
+        }
+    }
+
+    /**
+     * Get a list of controllers for all ongoing sessions. This requires the
+     * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
+     * the calling app.
+     *
+     * @return a list of controllers for ongoing sessions
+     */
+    public List<MediaController> getActiveSessions() {
+        // TODO
+        return new ArrayList<MediaController>();
+    }
+}
diff --git a/media/java/android/media/MediaSessionToken.aidl b/media/java/android/media/MediaSessionToken.aidl
new file mode 100644
index 0000000..e2f1abc
--- /dev/null
+++ b/media/java/android/media/MediaSessionToken.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.media;
+
+parcelable MediaSessionToken;
diff --git a/media/java/android/media/MediaSessionToken.java b/media/java/android/media/MediaSessionToken.java
new file mode 100644
index 0000000..885fda3
--- /dev/null
+++ b/media/java/android/media/MediaSessionToken.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class MediaSessionToken implements Parcelable {
+    private IMediaController mBinder;
+
+    /**
+     * @hide
+     */
+    MediaSessionToken(IMediaController binder) {
+        mBinder = binder;
+    }
+
+    private MediaSessionToken(Parcel in) {
+        mBinder = IMediaController.Stub.asInterface(in.readStrongBinder());
+    }
+
+    /**
+     * @hide
+     */
+    IMediaController getBinder() {
+        return mBinder;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mBinder.asBinder());
+    }
+
+    public static final Parcelable.Creator<MediaSessionToken> CREATOR
+            = new Parcelable.Creator<MediaSessionToken>() {
+        @Override
+        public MediaSessionToken createFromParcel(Parcel in) {
+            return new MediaSessionToken(in);
+        }
+
+        @Override
+        public MediaSessionToken[] newArray(int size) {
+            return new MediaSessionToken[size];
+        }
+    };
+}
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index cd3ce1f..3711585 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -264,7 +264,7 @@
      * @throws IllegalArgumentException
      */
     public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException {
-        if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) {
+        if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
             throw new IllegalArgumentException("not a media key event");
         }
         final PendingIntent pi;
diff --git a/opengl/java/android/opengl/GLES20.java b/opengl/java/android/opengl/GLES20.java
index 8261474..137f2f5 100644
--- a/opengl/java/android/opengl/GLES20.java
+++ b/opengl/java/android/opengl/GLES20.java
@@ -823,6 +823,7 @@
 
     // C function void glGetActiveAttrib ( GLuint program, GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, char *name )
 
+    /** @hide Method is broken, but used to be public (b/6006380) */
     public static native void glGetActiveAttrib(
         int program,
         int index,
@@ -870,6 +871,7 @@
 
     // C function void glGetActiveUniform ( GLuint program, GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, char *name )
 
+    /** @hide Method is broken, but used to be public (b/6006380) */
     public static native void glGetActiveUniform(
         int program,
         int index,
@@ -1107,6 +1109,7 @@
 
     // C function void glGetShaderSource ( GLuint shader, GLsizei bufsize, GLsizei *length, char *source )
 
+    /** @hide Method is broken, but used to be public (b/6006380) */
     public static native void glGetShaderSource(
         int shader,
         int bufsize,
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 9ed493c..9336ade 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_data_connection_4g" msgid="7741000750630089612">"4G"</string>
     <string name="accessibility_data_connection_lte" msgid="5413468808637540658">"LTE"</string>
     <string name="accessibility_data_connection_cdma" msgid="6132648193978823023">"CDMA"</string>
-    <string name="accessibility_data_connection_roaming" msgid="5977362333466556094">"Inatumia data nje mtandao wako"</string>
+    <string name="accessibility_data_connection_roaming" msgid="5977362333466556094">"Inatumia data nje mtandao wako wa kawaida"</string>
     <string name="accessibility_data_connection_edge" msgid="4477457051631979278">"Ukingo"</string>
     <string name="accessibility_data_connection_wifi" msgid="2324496756590645221">"Wi-Fi"</string>
     <string name="accessibility_no_sim" msgid="8274017118472455155">"Hakuna SIM."</string>
diff --git a/services/Android.mk b/services/Android.mk
index a8881b6..5260540 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -8,13 +8,23 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under,java)
 
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    services.core \
-    services.accessibility \
-    services.appwidget \
-    services.backup \
-    services.devicepolicy \
-    services.print
+# Uncomment to enable output of certain warnings (deprecated, unchecked)
+# LOCAL_JAVACFLAGS := -Xlint
+
+# Services that will be built as part of services.jar
+# These should map to directory names relative to this
+# Android.mk.
+services := \
+    core \
+    accessibility \
+    appwidget \
+    backup \
+    devicepolicy \
+    print \
+    usb
+
+# The convention is to name each service module 'services.$(module_name)'
+LOCAL_STATIC_JAVA_LIBRARIES := $(addprefix services.,$(services))
 
 include $(BUILD_JAVA_LIBRARY)
 
@@ -39,7 +49,16 @@
 
 include $(BUILD_SHARED_LIBRARY)
 
+# =============================================================
+
 ifeq (,$(ONE_SHOT_MAKEFILE))
-include $(call all-makefiles-under, $(LOCAL_PATH))
+# A full make is happening, so make everything.
+include $(call all-makefiles-under,$(LOCAL_PATH))
+else
+# If we ran an mm[m] command, we still want to build the individual
+# services that we depend on. This differs from the above condition
+# by only including service makefiles and not any tests or other
+# modules.
+include $(patsubst %,$(LOCAL_PATH)/%/Android.mk,$(services))
 endif
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ba08a2e..b56923f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2632,7 +2632,7 @@
        final int mtu = nt.getLinkProperties().getMtu();
 
        if (mtu < 68 || mtu > 10000) {
-           loge("Unexpected mtu value: " + nt);
+           loge("Unexpected mtu value: " + mtu + ", " + nt);
            return;
        }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
new file mode 100644
index 0000000..0d3fa84
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.content.Intent;
+import android.media.IMediaController;
+import android.media.IMediaControllerCallback;
+import android.media.IMediaSession;
+import android.media.IMediaSessionCallback;
+import android.media.RemoteControlClient;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+
+/**
+ * This is the system implementation of a Session. Apps will interact with the
+ * MediaSession wrapper class instead.
+ */
+public class MediaSessionRecord implements IBinder.DeathRecipient {
+    private static final String TAG = "MediaSessionImpl";
+
+    private final int mPid;
+    private final String mPackageName;
+    private final String mTag;
+    private final ControllerStub mController;
+    private final SessionStub mSession;
+    private final SessionCb mSessionCb;
+    private final MediaSessionService mService;
+
+    private final ArrayList<IMediaControllerCallback> mSessionCallbacks =
+            new ArrayList<IMediaControllerCallback>();
+
+    private int mPlaybackState = RemoteControlClient.PLAYSTATE_NONE;
+
+    public MediaSessionRecord(int pid, String packageName, IMediaSessionCallback cb, String tag,
+            MediaSessionService service) {
+        mPid = pid;
+        mPackageName = packageName;
+        mTag = tag;
+        mController = new ControllerStub();
+        mSession = new SessionStub();
+        mSessionCb = new SessionCb(cb);
+        mService = service;
+    }
+
+    public IMediaSession getSessionBinder() {
+        return mSession;
+    }
+
+    public IMediaController getControllerBinder() {
+        return mController;
+    }
+
+    public void setPlaybackStateInternal(int state) {
+        mPlaybackState = state;
+        for (int i = mSessionCallbacks.size() - 1; i >= 0; i--) {
+            IMediaControllerCallback cb = mSessionCallbacks.get(i);
+            try {
+                cb.onPlaybackUpdate(state);
+            } catch (RemoteException e) {
+                Log.d(TAG, "SessionCallback object dead in setPlaybackState.", e);
+                mSessionCallbacks.remove(i);
+            }
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        mService.sessionDied(this);
+    }
+
+    private void onDestroy() {
+        mService.destroySession(this);
+    }
+
+    private final class SessionStub extends IMediaSession.Stub {
+
+        @Override
+        public void setPlaybackState(int state) throws RemoteException {
+            setPlaybackStateInternal(state);
+        }
+
+        @Override
+        public void destroy() throws RemoteException {
+            onDestroy();
+        }
+
+        @Override
+        public void sendEvent(Bundle data) throws RemoteException {
+        }
+
+        @Override
+        public IMediaController getMediaSessionToken() throws RemoteException {
+            return mController;
+        }
+
+        @Override
+        public void setMetadata(Bundle metadata) throws RemoteException {
+        }
+
+        @Override
+        public void setRouteState(Bundle routeState) throws RemoteException {
+        }
+
+        @Override
+        public void setRoute(Bundle medaiRouteDescriptor) throws RemoteException {
+        }
+
+    }
+
+    class SessionCb {
+        private final IMediaSessionCallback mCb;
+
+        public SessionCb(IMediaSessionCallback cb) {
+            mCb = cb;
+        }
+
+        public void sendMediaButton(KeyEvent keyEvent) {
+            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+            try {
+                mCb.onMediaButton(mediaButtonIntent);
+            } catch (RemoteException e) {
+                Log.d(TAG, "Controller object dead in sendMediaRequest.", e);
+                onDestroy();
+            }
+        }
+
+        public void sendCommand(String command, Bundle extras) {
+            try {
+                mCb.onCommand(command, extras);
+            } catch (RemoteException e) {
+                Log.d(TAG, "Controller object dead in sendCommand.", e);
+                onDestroy();
+            }
+        }
+
+        public void registerCallbackListener(IMediaSessionCallback cb) {
+
+        }
+
+    }
+
+    class ControllerStub extends IMediaController.Stub {
+        /*
+         */
+        @Override
+        public void sendCommand(String command, Bundle extras) throws RemoteException {
+            mSessionCb.sendCommand(command, extras);
+        }
+
+        @Override
+        public void sendMediaButton(KeyEvent mediaButtonIntent) {
+            mSessionCb.sendMediaButton(mediaButtonIntent);
+        }
+
+        /*
+         */
+        @Override
+        public void registerCallbackListener(IMediaControllerCallback cb) throws RemoteException {
+            if (!mSessionCallbacks.contains(cb)) {
+                mSessionCallbacks.add(cb);
+            }
+        }
+
+        /*
+         */
+        @Override
+        public void unregisterCallbackListener(IMediaControllerCallback cb)
+                throws RemoteException {
+            mSessionCallbacks.remove(cb);
+        }
+
+        /*
+         */
+        @Override
+        public int getPlaybackState() throws RemoteException {
+            return mPlaybackState;
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
new file mode 100644
index 0000000..9c96c35
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.content.Context;
+import android.media.IMediaSession;
+import android.media.IMediaSessionCallback;
+import android.media.IMediaSessionManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+
+/**
+ * System implementation of MediaSessionManager
+ */
+public class MediaSessionService extends SystemService {
+    private static final String TAG = "MediaSessionService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final SessionManagerImpl mSessionManagerImpl;
+
+    private final ArrayList<MediaSessionRecord> mSessions
+            = new ArrayList<MediaSessionRecord>();
+    private final Object mLock = new Object();
+
+    public MediaSessionService(Context context) {
+        super(context);
+        mSessionManagerImpl = new SessionManagerImpl();
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
+    }
+
+    void sessionDied(MediaSessionRecord session) {
+        synchronized (mSessions) {
+            destroySessionLocked(session);
+        }
+    }
+
+    void destroySession(MediaSessionRecord session) {
+        synchronized (mSessions) {
+            destroySessionLocked(session);
+        }
+    }
+
+    private void destroySessionLocked(MediaSessionRecord session) {
+        mSessions.remove(session);
+    }
+
+    private void enforcePackageName(String packageName, int uid) {
+        if (TextUtils.isEmpty(packageName)) {
+            throw new IllegalArgumentException("packageName may not be empty");
+        }
+        String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
+        final int packageCount = packages.length;
+        for (int i = 0; i < packageCount; i++) {
+            if (packageName.equals(packages[i])) {
+                return;
+            }
+        }
+        throw new IllegalArgumentException("packageName is not owned by the calling process");
+    }
+
+    private MediaSessionRecord createSessionInternal(int pid, String packageName,
+            IMediaSessionCallback cb, String tag) {
+        synchronized (mLock) {
+            return createSessionLocked(pid, packageName, cb, tag);
+        }
+    }
+
+    private MediaSessionRecord createSessionLocked(int pid, String packageName,
+            IMediaSessionCallback cb, String tag) {
+        final MediaSessionRecord session = new MediaSessionRecord(pid, packageName, cb, tag, this);
+        try {
+            cb.asBinder().linkToDeath(session, 0);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Media Session owner died prematurely.", e);
+        }
+        synchronized (mSessions) {
+            mSessions.add(session);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Created session for package " + packageName + " with tag " + tag);
+        }
+        return session;
+    }
+
+    class SessionManagerImpl extends IMediaSessionManager.Stub {
+        @Override
+        public IMediaSession createSession(String packageName, IMediaSessionCallback cb, String tag)
+                throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                enforcePackageName(packageName, uid);
+                if (cb == null) {
+                    throw new IllegalArgumentException("Controller callback cannot be null");
+                }
+                return createSessionInternal(pid, packageName, cb, tag).getSessionBinder();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 130b94e..84f0f2e 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2576,15 +2576,41 @@
         }
     }
 
+    /**
+     * Compares two sets of signatures. Returns:
+     * <br />
+     * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
+     * <br />
+     * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
+     * <br />
+     * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
+     * <br />
+     * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
+     * <br />
+     * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
+     */
     static int compareSignatures(Signature[] s1, Signature[] s2) {
         if (s1 == null) {
             return s2 == null
                     ? PackageManager.SIGNATURE_NEITHER_SIGNED
                     : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
         }
+
         if (s2 == null) {
             return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
         }
+
+        if (s1.length != s2.length) {
+            return PackageManager.SIGNATURE_NO_MATCH;
+        }
+
+        // Since both signature sets are of size 1, we can compare without HashSets.
+        if (s1.length == 1) {
+            return s1[0].equals(s2[0]) ?
+                    PackageManager.SIGNATURE_MATCH :
+                    PackageManager.SIGNATURE_NO_MATCH;
+        }
+
         HashSet<Signature> set1 = new HashSet<Signature>();
         for (Signature sig : s1) {
             set1.add(sig);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bf88d9f..f46056b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5762,7 +5762,11 @@
                                 + " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer);
                     }
                 }
-                rawss = SurfaceControl.screenshot(dw, dh, minLayer, maxLayer);
+                // TODO: Replace 'false' in the following line with a variable that indicates
+                // whether the screenshot should use the identity transformation matrix
+                // (e.g., enable it when taking a screenshot for recents, since we might be in
+                // the middle of the rotation animation, but don't want a rotated recent image).
+                rawss = SurfaceControl.screenshot(dw, dh, minLayer, maxLayer, false);
             }
         } while (!screenshotReady && retryCount <= MAX_SCREENSHOT_RETRIES);
         if (retryCount > MAX_SCREENSHOT_RETRIES)  Slog.i(TAG, "Screenshot max retries " +
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5221f1f..42193af 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -62,6 +62,7 @@
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LightsService;
 import com.android.server.media.MediaRouterService;
+import com.android.server.media.MediaSessionService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
 import com.android.server.notification.NotificationManagerService;
@@ -110,6 +111,12 @@
             "com.android.server.appwidget.AppWidgetService";
     private static final String PRINT_MANAGER_SERVICE_CLASS =
             "com.android.server.print.PrintManagerService";
+    private static final String USB_SERVICE_CLASS =
+            "com.android.server.usb.UsbService$Lifecycle";
+    private static final String WIFI_SERVICE_CLASS =
+            "com.android.server.wifi.WifiService";
+    private static final String WIFI_P2P_SERVICE_CLASS =
+            "com.android.server.wifi.p2p.WifiP2pService";
 
     private final int mFactoryTestMode;
     private Timer mProfilerSnapshotTimer;
@@ -530,8 +537,7 @@
 
                 try {
                     if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
-                        mSystemServiceManager.startServiceIfExists(
-                                DEVICE_POLICY_MANAGER_SERVICE_CLASS);
+                        mSystemServiceManager.startService(DEVICE_POLICY_MANAGER_SERVICE_CLASS);
                     }
                 } catch (Throwable e) {
                     reportWtf("starting DevicePolicyService", e);
@@ -599,17 +605,13 @@
                 }
 
                 try {
-                    Slog.i(TAG, "Wi-Fi P2pService");
-                    mSystemServiceManager.startServiceIfExists(
-                            "com.android.server.wifi.p2p.WifiP2pService");
+                    mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
                 } catch (Throwable e) {
                     reportWtf("starting Wi-Fi P2pService", e);
                 }
 
                 try {
-                    Slog.i(TAG, "Wi-Fi Service");
-                    mSystemServiceManager.startServiceIfExists(
-                            "com.android.server.wifi.WifiService");
+                    mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
                 } catch (Throwable e) {
                     reportWtf("starting Wi-Fi Service", e);
                 }
@@ -754,10 +756,11 @@
 
             if (!disableNonCoreServices) {
                 try {
-                    Slog.i(TAG, "USB Service");
-                    // Manage USB host and device support
-                    usb = new UsbService(context);
-                    ServiceManager.addService(Context.USB_SERVICE, usb);
+                    if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST) ||
+                            pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)) {
+                        // Manage USB host and device support
+                        mSystemServiceManager.startService(USB_SERVICE_CLASS);
+                    }
                 } catch (Throwable e) {
                     reportWtf("starting UsbService", e);
                 }
@@ -779,7 +782,7 @@
             if (!disableNonCoreServices) {
                 try {
                     if (pm.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
-                        mSystemServiceManager.startServiceIfExists(BACKUP_MANAGER_SERVICE_CLASS);
+                        mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
                     }
                 } catch (Throwable e) {
                     Slog.e(TAG, "Failure starting Backup Service", e);
@@ -787,7 +790,7 @@
 
                 try {
                     if (pm.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
-                        mSystemServiceManager.startServiceIfExists(APPWIDGET_SERVICE_CLASS);
+                        mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
                     }
                 } catch (Throwable e) {
                     reportWtf("starting AppWidget Service", e);
@@ -873,12 +876,19 @@
 
             try {
                 if (pm.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
-                    mSystemServiceManager.startServiceIfExists(PRINT_MANAGER_SERVICE_CLASS);
+                    mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
                 }
             } catch (Throwable e) {
                 reportWtf("starting Print Service", e);
             }
 
+            try {
+                Slog.i(TAG, "MediaSessionService");
+                mSystemServiceManager.startService(MediaSessionService.class);
+            } catch (Throwable e) {
+                reportWtf("starting MediaSessionService", e);
+            }
+
             if (!disableNonCoreServices) {
                 try {
                     Slog.i(TAG, "Media Router Service");
@@ -965,7 +975,6 @@
         }
 
         // These are needed to propagate to the runnable below.
-        final Context contextF = context;
         final MountService mountServiceF = mountService;
         final BatteryService batteryF = battery;
         final NetworkManagementService networkManagementF = networkManagement;
@@ -973,7 +982,6 @@
         final NetworkPolicyManagerService networkPolicyF = networkPolicy;
         final ConnectivityService connectivityF = connectivity;
         final DockObserver dockF = dock;
-        final UsbService usbF = usb;
         final WallpaperManagerService wallpaperF = wallpaper;
         final InputMethodManagerService immF = imm;
         final RecognitionManagerService recognitionF = recognition;
@@ -993,132 +1001,138 @@
         // where third party code can really run (but before it has actually
         // started launching the initial applications), for us to complete our
         // initialization.
+        final Handler handler = new Handler();
         mActivityManagerService.systemReady(new Runnable() {
+            @Override
             public void run() {
-                Slog.i(TAG, "Making services ready");
+                // We initiate all boot phases on the SystemServer thread.
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        Slog.i(TAG, "Making services ready");
+                        mSystemServiceManager.startBootPhase(
+                                SystemService.PHASE_ACTIVITY_MANAGER_READY);
 
-                try {
-                    mActivityManagerService.startObservingNativeCrashes();
-                } catch (Throwable e) {
-                    reportWtf("observing native crashes", e);
-                }
-                try {
-                    startSystemUi(contextF);
-                } catch (Throwable e) {
-                    reportWtf("starting System UI", e);
-                }
-                try {
-                    if (mountServiceF != null) mountServiceF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Mount Service ready", e);
-                }
-                try {
-                    if (batteryF != null) batteryF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Battery Service ready", e);
-                }
-                try {
-                    if (networkManagementF != null) networkManagementF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Network Managment Service ready", e);
-                }
-                try {
-                    if (networkStatsF != null) networkStatsF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Network Stats Service ready", e);
-                }
-                try {
-                    if (networkPolicyF != null) networkPolicyF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Network Policy Service ready", e);
-                }
-                try {
-                    if (connectivityF != null) connectivityF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Connectivity Service ready", e);
-                }
-                try {
-                    if (dockF != null) dockF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Dock Service ready", e);
-                }
-                try {
-                    if (usbF != null) usbF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making USB Service ready", e);
-                }
-                try {
-                    if (recognitionF != null) recognitionF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Recognition Service ready", e);
-                }
-                Watchdog.getInstance().start();
+                        try {
+                            mActivityManagerService.startObservingNativeCrashes();
+                        } catch (Throwable e) {
+                            reportWtf("observing native crashes", e);
+                        }
+                        try {
+                            startSystemUi(context);
+                        } catch (Throwable e) {
+                            reportWtf("starting System UI", e);
+                        }
+                        try {
+                            if (mountServiceF != null) mountServiceF.systemReady();
+                        } catch (Throwable e) {
+                            reportWtf("making Mount Service ready", e);
+                        }
+                        try {
+                            if (batteryF != null) batteryF.systemReady();
+                        } catch (Throwable e) {
+                            reportWtf("making Battery Service ready", e);
+                        }
+                        try {
+                            if (networkManagementF != null) networkManagementF.systemReady();
+                        } catch (Throwable e) {
+                            reportWtf("making Network Managment Service ready", e);
+                        }
+                        try {
+                            if (networkStatsF != null) networkStatsF.systemReady();
+                        } catch (Throwable e) {
+                            reportWtf("making Network Stats Service ready", e);
+                        }
+                        try {
+                            if (networkPolicyF != null) networkPolicyF.systemReady();
+                        } catch (Throwable e) {
+                            reportWtf("making Network Policy Service ready", e);
+                        }
+                        try {
+                            if (connectivityF != null) connectivityF.systemReady();
+                        } catch (Throwable e) {
+                            reportWtf("making Connectivity Service ready", e);
+                        }
+                        try {
+                            if (dockF != null) dockF.systemReady();
+                        } catch (Throwable e) {
+                            reportWtf("making Dock Service ready", e);
+                        }
+                        try {
+                            if (recognitionF != null) recognitionF.systemReady();
+                        } catch (Throwable e) {
+                            reportWtf("making Recognition Service ready", e);
+                        }
+                        Watchdog.getInstance().start();
 
-                // It is now okay to let the various system services start their
-                // third party code...
-                mSystemServiceManager.startBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+                        // It is now okay to let the various system services start their
+                        // third party code...
+                        mSystemServiceManager.startBootPhase(
+                                SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
 
-                try {
-                    if (wallpaperF != null) wallpaperF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying WallpaperService running", e);
-                }
-                try {
-                    if (immF != null) immF.systemRunning(statusBarF);
-                } catch (Throwable e) {
-                    reportWtf("Notifying InputMethodService running", e);
-                }
-                try {
-                    if (locationF != null) locationF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying Location Service running", e);
-                }
-                try {
-                    if (countryDetectorF != null) countryDetectorF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying CountryDetectorService running", e);
-                }
-                try {
-                    if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying NetworkTimeService running", e);
-                }
-                try {
-                    if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying CommonTimeManagementService running", e);
-                }
-                try {
-                    if (textServiceManagerServiceF != null)
-                        textServiceManagerServiceF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying TextServicesManagerService running", e);
-                }
-                try {
-                    if (atlasF != null) atlasF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying AssetAtlasService running", e);
-                }
-                try {
-                    // TODO(BT) Pass parameter to input manager
-                    if (inputManagerF != null) inputManagerF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying InputManagerService running", e);
-                }
+                        try {
+                            if (wallpaperF != null) wallpaperF.systemRunning();
+                        } catch (Throwable e) {
+                            reportWtf("Notifying WallpaperService running", e);
+                        }
+                        try {
+                            if (immF != null) immF.systemRunning(statusBarF);
+                        } catch (Throwable e) {
+                            reportWtf("Notifying InputMethodService running", e);
+                        }
+                        try {
+                            if (locationF != null) locationF.systemRunning();
+                        } catch (Throwable e) {
+                            reportWtf("Notifying Location Service running", e);
+                        }
+                        try {
+                            if (countryDetectorF != null) countryDetectorF.systemRunning();
+                        } catch (Throwable e) {
+                            reportWtf("Notifying CountryDetectorService running", e);
+                        }
+                        try {
+                            if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
+                        } catch (Throwable e) {
+                            reportWtf("Notifying NetworkTimeService running", e);
+                        }
+                        try {
+                            if (commonTimeMgmtServiceF != null) {
+                                commonTimeMgmtServiceF.systemRunning();
+                            }
+                        } catch (Throwable e) {
+                            reportWtf("Notifying CommonTimeManagementService running", e);
+                        }
+                        try {
+                            if (textServiceManagerServiceF != null)
+                                textServiceManagerServiceF.systemRunning();
+                        } catch (Throwable e) {
+                            reportWtf("Notifying TextServicesManagerService running", e);
+                        }
+                        try {
+                            if (atlasF != null) atlasF.systemRunning();
+                        } catch (Throwable e) {
+                            reportWtf("Notifying AssetAtlasService running", e);
+                        }
+                        try {
+                            // TODO(BT) Pass parameter to input manager
+                            if (inputManagerF != null) inputManagerF.systemRunning();
+                        } catch (Throwable e) {
+                            reportWtf("Notifying InputManagerService running", e);
+                        }
+                        try {
+                            if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
+                        } catch (Throwable e) {
+                            reportWtf("Notifying TelephonyRegistry running", e);
+                        }
+                        try {
+                            if (mediaRouterF != null) mediaRouterF.systemRunning();
+                        } catch (Throwable e) {
+                            reportWtf("Notifying MediaRouterService running", e);
+                        }
 
-                try {
-                    if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying TelephonyRegistry running", e);
-                }
-
-                try {
-                    if (mediaRouterF != null) mediaRouterF.systemRunning();
-                } catch (Throwable e) {
-                    reportWtf("Notifying MediaRouterService running", e);
-                }
-
-                mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETE);
+                        mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETE);
+                    }
+                });
             }
         });
     }
diff --git a/services/usb/Android.mk b/services/usb/Android.mk
new file mode 100644
index 0000000..feabf0a
--- /dev/null
+++ b/services/usb/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.usb
+
+LOCAL_SRC_FILES += \
+      $(call all-java-files-under,java)
+
+LOCAL_JAVA_LIBRARIES := services.core
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/core/java/com/android/server/usb/UsbDebuggingManager.java b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
similarity index 100%
rename from services/core/java/com/android/server/usb/UsbDebuggingManager.java
rename to services/usb/java/com/android/server/usb/UsbDebuggingManager.java
diff --git a/services/core/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
similarity index 99%
rename from services/core/java/com/android/server/usb/UsbDeviceManager.java
rename to services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 2312288..d5dd9a6 100644
--- a/services/core/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -810,7 +810,7 @@
                     if (mOemModeMap == null) {
                         mOemModeMap = new HashMap<String, List<Pair<String, String>>>();
                     }
-                    List overrideList = mOemModeMap.get(items[0]);
+                    List<Pair<String, String>> overrideList = mOemModeMap.get(items[0]);
                     if (overrideList == null) {
                         overrideList = new LinkedList<Pair<String, String>>();
                         mOemModeMap.put(items[0], overrideList);
diff --git a/services/core/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
similarity index 100%
rename from services/core/java/com/android/server/usb/UsbHostManager.java
rename to services/usb/java/com/android/server/usb/UsbHostManager.java
diff --git a/services/core/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
similarity index 94%
rename from services/core/java/com/android/server/usb/UsbService.java
rename to services/usb/java/com/android/server/usb/UsbService.java
index 36669b1..b6ae192 100644
--- a/services/core/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.SystemService;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -43,6 +44,28 @@
  * support is delegated to UsbDeviceManager.
  */
 public class UsbService extends IUsbManager.Stub {
+
+    public static class Lifecycle extends SystemService {
+        private UsbService mUsbService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mUsbService = new UsbService(getContext());
+            publishBinderService(Context.USB_SERVICE, mUsbService);
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+                mUsbService.systemReady();
+            }
+        }
+    }
+
     private static final String TAG = "UsbService";
 
     private final Context mContext;
diff --git a/services/core/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
similarity index 100%
rename from services/core/java/com/android/server/usb/UsbSettingsManager.java
rename to services/usb/java/com/android/server/usb/UsbSettingsManager.java
diff --git a/tests/OneMedia/Android.mk b/tests/OneMedia/Android.mk
new file mode 100644
index 0000000..93b9c9a
--- /dev/null
+++ b/tests/OneMedia/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files) \
+        $(call all-Iaidl-files-under, src)
+
+LOCAL_PACKAGE_NAME := OneMedia
+LOCAL_CERTIFICATE := platform
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+        android-support-v7-appcompat \
+        android-support-v7-mediarouter
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
new file mode 100644
index 0000000..7d6ba1d
--- /dev/null
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.onemedia"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="19"/>
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.android.onemedia.OnePlayerActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <service
+            android:name="com.android.onemedia.OnePlayerService"
+            android:exported="false"
+            android:process="com.android.onemedia.service" />
+    </application>
+
+</manifest>
diff --git a/tests/OneMedia/res/drawable-hdpi/ic_launcher.png b/tests/OneMedia/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..288b665
--- /dev/null
+++ b/tests/OneMedia/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/OneMedia/res/drawable-mdpi/ic_launcher.png b/tests/OneMedia/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..6ae570b
--- /dev/null
+++ b/tests/OneMedia/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/OneMedia/res/drawable-xhdpi/ic_launcher.png b/tests/OneMedia/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d4fb7cd
--- /dev/null
+++ b/tests/OneMedia/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/OneMedia/res/drawable-xxhdpi/ic_launcher.png b/tests/OneMedia/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..85a6081
--- /dev/null
+++ b/tests/OneMedia/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/OneMedia/res/layout/activity_main.xml b/tests/OneMedia/res/layout/activity_main.xml
new file mode 100644
index 0000000..168c9b8ce
--- /dev/null
+++ b/tests/OneMedia/res/layout/activity_main.xml
@@ -0,0 +1,16 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    tools:context=".MainActivity" >
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/hello_world" />
+
+</RelativeLayout>
diff --git a/tests/OneMedia/res/layout/activity_one_player.xml b/tests/OneMedia/res/layout/activity_one_player.xml
new file mode 100644
index 0000000..4208355
--- /dev/null
+++ b/tests/OneMedia/res/layout/activity_one_player.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 Google Inc. All Rights Reserved. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center_horizontal"
+        android:orientation="vertical">
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:text="@string/app_name"
+            style="@style/Title" />
+    <EditText
+            android:id="@+id/content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textUri"
+            android:hint="@string/media_content_hint"
+            android:gravity="center"
+            android:textSize="24sp" />
+    <EditText
+            android:id="@+id/next_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textNoSuggestions"
+            android:hint="@string/media_next_hint"
+            android:gravity="center"
+            android:textSize="24sp" />
+    <CheckBox
+            android:id="@+id/has_video"
+            android:layout_marginRight="8dip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/has_video" />
+    <LinearLayout
+            android:id="@+id/controls"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+        <Button
+                android:id="@+id/start_button"
+                style="@style/BottomBarButton"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/start_button" />
+        <Button
+                android:id="@+id/play_button"
+                style="@style/BottomBarButton"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/play_button" />
+    </LinearLayout>
+    <TextView
+            android:id="@+id/status"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/tests/OneMedia/res/menu/main.xml b/tests/OneMedia/res/menu/main.xml
new file mode 100644
index 0000000..c002028
--- /dev/null
+++ b/tests/OneMedia/res/menu/main.xml
@@ -0,0 +1,9 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/action_settings"
+        android:orderInCategory="100"
+        android:showAsAction="never"
+        android:title="@string/action_settings"/>
+
+</menu>
diff --git a/tests/OneMedia/res/values/colors.xml b/tests/OneMedia/res/values/colors.xml
new file mode 100644
index 0000000..9b9dc2a
--- /dev/null
+++ b/tests/OneMedia/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<resources>
+    <color name="title_color">#33B5E5</color>
+</resources>
diff --git a/tests/OneMedia/res/values/dimens.xml b/tests/OneMedia/res/values/dimens.xml
new file mode 100644
index 0000000..562edef
--- /dev/null
+++ b/tests/OneMedia/res/values/dimens.xml
@@ -0,0 +1,9 @@
+<resources>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="title_size">22sp</dimen>
+    <dimen name="small_size">11sp</dimen>
+
+</resources>
diff --git a/tests/OneMedia/res/values/strings.xml b/tests/OneMedia/res/values/strings.xml
new file mode 100644
index 0000000..1b0cebb
--- /dev/null
+++ b/tests/OneMedia/res/values/strings.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">OneMedia</string>
+    <string name="action_settings">Settings</string>
+    <string name="hello_world">Test app for trying out new media components</string>
+
+    <string name="start_button">Start</string>
+    <string name="play_button">Play</string>
+    <string name="media_content_hint">Content</string>
+    <string name="media_next_hint">Next content</string>
+    <string name="has_video">Is video</string>
+    <string name="has_duration">Has duration</string>
+
+</resources>
diff --git a/tests/OneMedia/res/values/styles.xml b/tests/OneMedia/res/values/styles.xml
new file mode 100644
index 0000000..60f3139
--- /dev/null
+++ b/tests/OneMedia/res/values/styles.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+    <style name="Title">
+        <item name="android:textSize">@dimen/title_size</item>
+        <item name="android:textColor">@color/title_color</item>
+        <item name="android:clickable">false</item>
+        <item name="android:longClickable">false</item>
+    </style>
+
+    <style name="Text">
+        <item name="android:textSize">@dimen/small_size</item>
+        <item name="android:textColor">@color/title_color</item>
+        <item name="android:clickable">false</item>
+        <item name="android:longClickable">false</item>
+    </style>
+
+    <style name="BottomBarButton">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingLeft">0dip</item>
+        <item name="android:paddingRight">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
+        <item name="android:textSize">12sp</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+</resources>
diff --git a/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl b/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl
new file mode 100644
index 0000000..9bc3baa
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl
@@ -0,0 +1,22 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ 
+package com.android.onemedia;
+
+import android.media.MediaSessionToken;
+
+interface IPlayerCallback {
+    void onSessionChanged(in MediaSessionToken session);
+}
\ No newline at end of file
diff --git a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
new file mode 100644
index 0000000..ab1d3fc
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
@@ -0,0 +1,29 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ 
+package com.android.onemedia;
+
+import android.media.MediaSessionToken;
+import android.os.Bundle;
+
+import com.android.onemedia.IPlayerCallback;
+import com.android.onemedia.playback.IRequestCallback;
+
+interface IPlayerService {
+    MediaSessionToken getSessionToken(); 
+    void registerCallback(in IPlayerCallback cb);
+    void unregisterCallback(in IPlayerCallback cb);
+    void sendRequest(String action, in Bundle params, in IRequestCallback cb);
+}
\ No newline at end of file
diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
new file mode 100644
index 0000000..7ff81e4
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
@@ -0,0 +1,144 @@
+package com.android.onemedia;
+
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.onemedia.playback.Renderer;
+
+public class OnePlayerActivity extends Activity {
+    private static final String TAG = "OnePlayerActivity";
+
+    protected PlayerController mPlayer;
+
+    private Button mStartButton;
+    private Button mPlayButton;
+    private TextView mStatusView;
+
+    private EditText mContentText;
+    private EditText mNextContentText;
+    private CheckBox mHasVideo;
+
+    private int mPlaybackState;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_one_player);
+        mPlayer = new PlayerController(this, OnePlayerService.getServiceIntent(this));
+
+
+        mStartButton = (Button) findViewById(R.id.start_button);
+        mPlayButton = (Button) findViewById(R.id.play_button);
+        mStatusView = (TextView) findViewById(R.id.status);
+        mContentText = (EditText) findViewById(R.id.content);
+        mNextContentText = (EditText) findViewById(R.id.next_content);
+        mHasVideo = (CheckBox) findViewById(R.id.has_video);
+
+        mStartButton.setOnClickListener(mButtonListener);
+        mPlayButton.setOnClickListener(mButtonListener);
+
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.main, menu);
+        return true;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mPlayer.onResume();
+        mPlayer.setListener(mListener);
+    }
+
+    @Override
+    public void onPause() {
+        mPlayer.setListener(null);
+        mPlayer.onPause();
+        super.onPause();
+    }
+
+    private void setControlsEnabled(boolean enabled) {
+        mStartButton.setEnabled(enabled);
+        mPlayButton.setEnabled(enabled);
+    }
+
+    private View.OnClickListener mButtonListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.play_button:
+                    Log.d(TAG, "Play button pressed, in state " + mPlaybackState);
+                    if (mPlaybackState == Renderer.STATE_PAUSED
+                            || mPlaybackState == Renderer.STATE_ENDED) {
+                        mPlayer.play();
+                    } else if (mPlaybackState == Renderer.STATE_PLAYING) {
+                        mPlayer.pause();
+                    }
+                    break;
+                case R.id.start_button:
+                    Log.d(TAG, "Start button pressed, in state " + mPlaybackState);
+                    mPlayer.setContent(mContentText.getText().toString());
+                    break;
+            }
+
+        }
+    };
+
+    private PlayerController.Listener mListener = new PlayerController.Listener() {
+        @Override
+        public void onSessionStateChange(int state) {
+            mPlaybackState = state;
+            boolean enablePlay = false;
+            switch (mPlaybackState) {
+                case Renderer.STATE_PLAYING:
+                    mStatusView.setText("playing");
+                    mPlayButton.setText("Pause");
+                    enablePlay = true;
+                    break;
+                case Renderer.STATE_PAUSED:
+                    mStatusView.setText("paused");
+                    mPlayButton.setText("Play");
+                    enablePlay = true;
+                    break;
+                case Renderer.STATE_ENDED:
+                    mStatusView.setText("ended");
+                    mPlayButton.setText("Play");
+                    enablePlay = true;
+                    break;
+                case Renderer.STATE_ERROR:
+                    mStatusView.setText("error");
+                    break;
+                case Renderer.STATE_PREPARING:
+                    mStatusView.setText("preparing");
+                    break;
+                case Renderer.STATE_READY:
+                    mStatusView.setText("ready");
+                    break;
+                case Renderer.STATE_STOPPED:
+                    mStatusView.setText("stopped");
+                    break;
+            }
+            mPlayButton.setEnabled(enablePlay);
+        }
+
+        @Override
+        public void onPlayerStateChange(int state) {
+            if (state == PlayerController.STATE_DISCONNECTED) {
+                setControlsEnabled(false);
+            } else if (state == PlayerController.STATE_CONNECTED) {
+                setControlsEnabled(true);
+            }
+        }
+    };
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerService.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerService.java
new file mode 100644
index 0000000..01610cd
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerService.java
@@ -0,0 +1,30 @@
+package com.android.onemedia;
+
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.ArrayList;
+
+/**
+ * TODO: Insert description here. (generated by epastern)
+ */
+public class OnePlayerService extends PlayerService {
+    private static final String TAG = "OnePlayerService";
+
+    public static Intent getServiceIntent(Context context) {
+        return new Intent(context, OnePlayerService.class).setPackage(
+                OnePlayerService.class.getPackage().getName());
+    }
+
+    @Override
+    protected Intent onCreateServiceIntent() {
+        return getServiceIntent(this);
+    }
+
+    @Override
+    protected ArrayList<String> getAllowedPackages() {
+        ArrayList<String> allowedPackages = new ArrayList<String>();
+        allowedPackages.add("com.android.onemedia");
+        return allowedPackages;
+    }
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
new file mode 100644
index 0000000..4ccc846
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -0,0 +1,157 @@
+
+package com.android.onemedia;
+
+import android.media.MediaController;
+import android.media.MediaSessionManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import com.android.onemedia.playback.RequestUtils;
+
+public class PlayerController {
+    private static final String TAG = "PlayerSession";
+
+    public static final int STATE_DISCONNECTED = 0;
+    public static final int STATE_CONNECTED = 1;
+
+    protected MediaController mController;
+    protected IPlayerService mBinder;
+
+    private final Intent mServiceIntent;
+    private Context mContext;
+    private Listener mListener;
+    private SessionCallback mControllerCb;
+    private MediaSessionManager mManager;
+    private Handler mHandler = new Handler();
+
+    private boolean mResumed;
+
+    public PlayerController(Context context, Intent serviceIntent) {
+        mContext = context;
+        if (serviceIntent == null) {
+            mServiceIntent = new Intent(mContext, PlayerService.class);
+        } else {
+            mServiceIntent = serviceIntent;
+        }
+        mControllerCb = new SessionCallback();
+        mManager = (MediaSessionManager) context
+                .getSystemService(Context.MEDIA_SESSION_SERVICE);
+
+        mResumed = false;
+    }
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+        Log.d(TAG, "Listener set to " + listener + " session is " + mController);
+        if (mListener != null) {
+            mHandler = new Handler();
+            mListener.onPlayerStateChange(
+                    mController == null ? STATE_DISCONNECTED : STATE_CONNECTED);
+        }
+    }
+
+    public void onResume() {
+        mResumed = true;
+        Log.d(TAG, "onResume. Binding to service with intent " + mServiceIntent.toString());
+        bindToService();
+    }
+
+    public void onPause() {
+        mResumed = false;
+        Log.d(TAG, "onPause, unbinding from service");
+        unbindFromService();
+    }
+
+    public void play() {
+        mController.sendMediaButton(KeyEvent.KEYCODE_MEDIA_PLAY);
+    }
+
+    public void pause() {
+        mController.sendMediaButton(KeyEvent.KEYCODE_MEDIA_PAUSE);
+    }
+
+    public void setContent(String source) {
+        RequestUtils.ContentBuilder bob = new RequestUtils.ContentBuilder();
+        bob.setSource(source);
+        try {
+            mBinder.sendRequest(RequestUtils.ACTION_SET_CONTENT, bob.build(), null);
+        } catch (RemoteException e) {
+            Log.d(TAG, "setContent failed, service may have died.", e);
+        }
+    }
+
+    public void setNextContent(String source) {
+        RequestUtils.ContentBuilder bob = new RequestUtils.ContentBuilder();
+        bob.setSource(source);
+        try {
+            mBinder.sendRequest(RequestUtils.ACTION_SET_NEXT_CONTENT, bob.build(), null);
+        } catch (RemoteException e) {
+            Log.d(TAG, "setNexctContent failed, service may have died.", e);
+        }
+    }
+
+    private void unbindFromService() {
+        mContext.unbindService(mServiceConnection);
+    }
+
+    private void bindToService() {
+        mContext.bindService(mServiceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    private ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (mController != null) {
+                mController.removeCallback(mControllerCb);
+            }
+            mBinder = null;
+            mController = null;
+            Log.d(TAG, "Disconnected from PlayerService");
+
+            if (mListener != null) {
+                mListener.onPlayerStateChange(STATE_DISCONNECTED);
+            }
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mBinder = IPlayerService.Stub.asInterface(service);
+            Log.d(TAG, "service is " + service + " binder is " + mBinder);
+            try {
+                mController = new MediaController(mBinder.getSessionToken());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error getting session", e);
+                return;
+            }
+            mController.addCallback(mControllerCb, mHandler);
+            Log.d(TAG, "Ready to use PlayerService");
+
+            if (mListener != null) {
+                mListener.onPlayerStateChange(STATE_CONNECTED);
+            }
+        }
+    };
+
+    private class SessionCallback extends MediaController.Callback {
+        @Override
+        public void onPlaybackStateChange(int state) {
+            if (mListener != null) {
+                mListener.onSessionStateChange(state);
+            }
+        }
+    }
+
+    public interface Listener {
+        public void onSessionStateChange(int state);
+
+        public void onPlayerStateChange(int state);
+    }
+
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerService.java b/tests/OneMedia/src/com/android/onemedia/PlayerService.java
new file mode 100644
index 0000000..0819077
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerService.java
@@ -0,0 +1,102 @@
+package com.android.onemedia;
+
+import android.app.Service;
+import android.content.Intent;
+import android.media.MediaSessionToken;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.onemedia.playback.IRequestCallback;
+import com.android.onemedia.playback.RequestUtils;
+
+import java.util.ArrayList;
+
+public class PlayerService extends Service {
+    private static final String TAG = "PlayerService";
+
+    private PlayerBinder mBinder;
+    private PlayerSession mSession;
+    private Intent mIntent;
+
+    private ArrayList<IPlayerCallback> mCbs = new ArrayList<IPlayerCallback>();
+
+    @Override
+    public void onCreate() {
+        mIntent = onCreateServiceIntent();
+        mSession = onCreatePlayerController();
+        mSession.createSession();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (mBinder == null) {
+            mBinder = new PlayerBinder();
+        }
+        return mBinder;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        return START_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+        mSession.onDestroy();
+    }
+
+    protected Intent onCreateServiceIntent() {
+        return new Intent(this, PlayerService.class).setPackage(getBasePackageName());
+    }
+
+    protected PlayerSession onCreatePlayerController() {
+        return new PlayerSession(this);
+    }
+
+    protected ArrayList<String> getAllowedPackages() {
+        return null;
+    }
+
+    public class PlayerBinder extends IPlayerService.Stub {
+        @Override
+        public void sendRequest(String action, Bundle params, IRequestCallback cb) {
+            if (RequestUtils.ACTION_SET_CONTENT.equals(action)) {
+                mSession.setContent(params);
+            } else if (RequestUtils.ACTION_SET_NEXT_CONTENT.equals(action)) {
+                mSession.setNextContent(params);
+            }
+        }
+
+        @Override
+        public void registerCallback(final IPlayerCallback cb) throws RemoteException {
+            if (!mCbs.contains(cb)) {
+                mCbs.add(cb);
+                cb.asBinder().linkToDeath(new IBinder.DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        mCbs.remove(cb);
+                    }
+                }, 0);
+            }
+            try {
+                cb.onSessionChanged(getSessionToken());
+            } catch (RemoteException e) {
+                mCbs.remove(cb);
+                throw e;
+            }
+        }
+
+        @Override
+        public void unregisterCallback(IPlayerCallback cb) throws RemoteException {
+            mCbs.remove(cb);
+        }
+
+        @Override
+        public MediaSessionToken getSessionToken() throws RemoteException {
+            // TODO(epastern): Auto-generated method stub
+            return mSession.getSessionToken();
+        }
+    }
+
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
new file mode 100644
index 0000000..25a8f0d
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -0,0 +1,117 @@
+package com.android.onemedia;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaSession;
+import android.media.MediaSessionManager;
+import android.media.MediaSessionToken;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import com.android.onemedia.playback.LocalRenderer;
+import com.android.onemedia.playback.Renderer;
+import com.android.onemedia.playback.RendererFactory;
+
+public class PlayerSession {
+    private static final String TAG = "PlayerController";
+
+    protected MediaSession mSession;
+    protected Context mContext;
+    protected RendererFactory mRendererFactory;
+    protected LocalRenderer mRenderer;
+    protected ControllerCb mCallback;
+    protected RenderListener mRenderListener;
+
+    public PlayerSession(Context context) {
+        mContext = context;
+        mRendererFactory = new RendererFactory();
+        mRenderer = new LocalRenderer(context, null);
+        mCallback = new ControllerCb();
+        mRenderListener = new RenderListener();
+
+        mRenderer.registerListener(mRenderListener);
+    }
+
+    public void createSession() {
+        if (mSession != null) {
+            mSession.release();
+        }
+        MediaSessionManager man = (MediaSessionManager) mContext
+                .getSystemService(Context.MEDIA_SESSION_SERVICE);
+        Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
+        mSession = man.createSession("OneMedia");
+        mSession.addCallback(mCallback);
+    }
+
+    public void onDestroy() {
+        if (mSession != null) {
+            mSession.release();
+        }
+        if (mRenderer != null) {
+            mRenderer.unregisterListener(mRenderListener);
+            mRenderer.onDestroy();
+        }
+    }
+
+    public MediaSessionToken getSessionToken() {
+        return mSession.getSessionToken();
+    }
+
+    public void setContent(Bundle request) {
+        mRenderer.setContent(request);
+    }
+
+    public void setNextContent(Bundle request) {
+        mRenderer.setNextContent(request);
+    }
+
+    protected class RenderListener implements Renderer.Listener {
+
+        @Override
+        public void onError(int type, int extra, Bundle extras, Throwable error) {
+            mSession.setPlaybackState(Renderer.STATE_ERROR);
+        }
+
+        @Override
+        public void onStateChanged(int newState) {
+            mSession.setPlaybackState(newState);
+        }
+
+        @Override
+        public void onBufferingUpdate(int percent) {
+        }
+
+        @Override
+        public void onFocusLost() {
+            mSession.setPlaybackState(Renderer.STATE_PAUSED);
+        }
+
+        @Override
+        public void onNextStarted() {
+        }
+
+    }
+
+    protected class ControllerCb extends MediaSession.Callback {
+
+        @Override
+        public void onMediaButton(Intent mediaRequestIntent) {
+            if (Intent.ACTION_MEDIA_BUTTON.equals(mediaRequestIntent.getAction())) {
+                KeyEvent event = (KeyEvent) mediaRequestIntent
+                        .getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+                switch (event.getKeyCode()) {
+                    case KeyEvent.KEYCODE_MEDIA_PLAY:
+                        Log.d(TAG, "play button received");
+                        mRenderer.onPlay();
+                        break;
+                    case KeyEvent.KEYCODE_MEDIA_PAUSE:
+                        Log.d(TAG, "pause button received");
+                        mRenderer.onPause();
+                        break;
+                }
+            }
+        }
+    }
+
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/IRequestCallback.aidl b/tests/OneMedia/src/com/android/onemedia/playback/IRequestCallback.aidl
new file mode 100644
index 0000000..c5a30a8
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/playback/IRequestCallback.aidl
@@ -0,0 +1,22 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ 
+package com.android.onemedia.playback;
+
+import android.os.Bundle;
+
+oneway interface IRequestCallback {
+    void onResult(in Bundle result);
+}
\ No newline at end of file
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java
new file mode 100644
index 0000000..7493366
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java
@@ -0,0 +1,703 @@
+package com.android.onemedia.playback;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnBufferingUpdateListener;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.net.http.AndroidHttpClient;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Helper class for wrapping a MediaPlayer and doing a lot of the default work
+ * to play audio. This class is not currently thread safe and all calls to it
+ * should be made on the same thread.
+ */
+public class LocalRenderer extends Renderer implements OnPreparedListener,
+        OnBufferingUpdateListener, OnCompletionListener, OnErrorListener,
+        OnAudioFocusChangeListener {
+    private static final String TAG = "MediaPlayerManager";
+    private static final boolean DEBUG = true;
+    private static long sDebugInstanceId = 0;
+
+    private static final String[] SUPPORTED_FEATURES = {
+            FEATURE_SET_CONTENT,
+            FEATURE_SET_NEXT_CONTENT,
+            FEATURE_PLAY,
+            FEATURE_PAUSE,
+            FEATURE_NEXT,
+            FEATURE_PREVIOUS,
+            FEATURE_SEEK_TO,
+            FEATURE_STOP
+    };
+
+    /**
+     * These are the states where it is valid to call play directly on the
+     * MediaPlayer.
+     */
+    private static final int CAN_PLAY = STATE_READY | STATE_PAUSED | STATE_ENDED;
+    /**
+     * These are the states where we expect the MediaPlayer to be ready in the
+     * future, so we can set a flag to start playing when it is.
+     */
+    private static final int CAN_READY_PLAY = STATE_INIT | STATE_PREPARING;
+    /**
+     * The states when it is valid to call pause on the MediaPlayer.
+     */
+    private static final int CAN_PAUSE = STATE_PLAYING;
+    /**
+     * The states where it is valid to call seek on the MediaPlayer.
+     */
+    private static final int CAN_SEEK = STATE_READY | STATE_PLAYING | STATE_PAUSED | STATE_ENDED;
+    /**
+     * The states where we expect the MediaPlayer to be ready in the future and
+     * can store a seek position to set later.
+     */
+    private static final int CAN_READY_SEEK = STATE_INIT | STATE_PREPARING;
+    /**
+     * The states where it is valid to call stop on the MediaPlayer.
+     */
+    private static final int CAN_STOP = STATE_READY | STATE_PLAYING | STATE_PAUSED | STATE_ENDED;
+    /**
+     * The states where it is valid to get the current play position and the
+     * duration from the MediaPlayer.
+     */
+    private static final int CAN_GET_POSITION = STATE_READY | STATE_PLAYING | STATE_PAUSED;
+
+
+
+    private class PlayerContent {
+        public final String source;
+        public final Map<String, String> headers;
+
+        public PlayerContent(String source, Map<String, String> headers) {
+            this.source = source;
+            this.headers = headers;
+        }
+    }
+
+    private class AsyncErrorRetriever extends AsyncTask<HttpGet, Void, Void> {
+        private final long errorId;
+        private boolean closeHttpClient;
+
+        public AsyncErrorRetriever(long errorId) {
+            this.errorId = errorId;
+            closeHttpClient = false;
+        }
+
+        public boolean cancelRequestLocked(boolean closeHttp) {
+            closeHttpClient = closeHttp;
+            return this.cancel(false);
+        }
+
+        @Override
+        protected Void doInBackground(HttpGet[] params) {
+            synchronized (mErrorLock) {
+                if (isCancelled() || mHttpClient == null) {
+                    if (mErrorRetriever == this) {
+                        mErrorRetriever = null;
+                    }
+                    return null;
+                }
+                mSafeToCloseClient = false;
+            }
+            final PlaybackError error = new PlaybackError();
+            try {
+                HttpResponse response = mHttpClient.execute(params[0]);
+                synchronized (mErrorLock) {
+                    if (mErrorId != errorId || mError == null) {
+                        // A new error has occurred, abort
+                        return null;
+                    }
+                    error.type = mError.type;
+                    error.extra = mError.extra;
+                    error.errorMessage = mError.errorMessage;
+                }
+                final int code = response.getStatusLine().getStatusCode();
+                if (code >= 300) {
+                    error.extra = code;
+                }
+                final Bundle errorExtras = new Bundle();
+                Header[] headers = response.getAllHeaders();
+                if (headers != null && headers.length > 0) {
+                    for (Header header : headers) {
+                        errorExtras.putString(header.getName(), header.getValue());
+                    }
+                    error.errorExtras = errorExtras;
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "IOException requesting from server, unable to get more exact error");
+            } finally {
+                synchronized (mErrorLock) {
+                    mSafeToCloseClient = true;
+                    if (mErrorRetriever == this) {
+                        mErrorRetriever = null;
+                    }
+                    if (isCancelled()) {
+                        if (closeHttpClient) {
+                            mHttpClient.close();
+                            mHttpClient = null;
+                        }
+                        return null;
+                    }
+                }
+            }
+            mHandler.post(new Runnable() {
+                    @Override
+                public void run() {
+                    synchronized (mErrorLock) {
+                        if (mErrorId == errorId) {
+                            setError(error.type, error.extra, error.errorExtras, null);
+                        }
+                    }
+                }
+            });
+            return null;
+        }
+    }
+
+    private int mState = STATE_INIT;
+
+    private AudioManager mAudioManager;
+    private MediaPlayer mPlayer;
+    private PlayerContent mContent;
+    private MediaPlayer mNextPlayer;
+    private PlayerContent mNextContent;
+    private SurfaceHolder mHolder;
+    private SurfaceHolder.Callback mHolderCB;
+    private Context mContext;
+
+    private Handler mHandler = new Handler();
+
+    private AndroidHttpClient mHttpClient = AndroidHttpClient.newInstance("TUQ");
+    // The ongoing error request thread if there is one. This should only be
+    // modified while mErrorLock is held.
+    private AsyncErrorRetriever mErrorRetriever;
+    // This is set to false while a server request is being made to retrieve
+    // the current error. It should only be set while mErrorLock is held.
+    private boolean mSafeToCloseClient = true;
+    private final Object mErrorLock = new Object();
+    // A tracking id for the current error. This should only be modified while
+    // mErrorLock is held.
+    private long mErrorId = 0;
+    // The current error state of this player. This is cleared when the state
+    // leaves an error state and set when it enters one. This should only be
+    // modified when mErrorLock is held.
+    private PlaybackError mError;
+
+    private boolean mPlayOnReady;
+    private int mSeekOnReady;
+    private boolean mHasAudioFocus;
+    private long mDebugId = sDebugInstanceId++;
+
+    public LocalRenderer(Context context, Bundle params) {
+        super(context, params);
+        mContext = context;
+        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    }
+
+    @Override
+    protected void initFeatures(Bundle params) {
+        for (String feature : SUPPORTED_FEATURES) {
+            mFeatures.add(feature);
+        }
+    }
+
+    /**
+     * Call this when completely finished with the MediaPlayerManager to have it
+     * clean up. The instance may not be used again after this is called.
+     */
+    @Override
+    public void onDestroy() {
+        synchronized (mErrorLock) {
+            if (DEBUG) {
+                Log.d(TAG, "onDestroy, error retriever? " + mErrorRetriever + " safe to close? "
+                        + mSafeToCloseClient + " client? " + mHttpClient);
+            }
+            if (mErrorRetriever != null) {
+                mErrorRetriever.cancelRequestLocked(true);
+                mErrorRetriever = null;
+            }
+            // Increment the error id to ensure no errors are sent after this
+            // point.
+            mErrorId++;
+            if (mSafeToCloseClient) {
+                mHttpClient.close();
+                mHttpClient = null;
+            }
+        }
+    }
+
+    @Override
+    public void onPrepared(MediaPlayer player) {
+        if (!isCurrentPlayer(player)) {
+            return;
+        }
+        setState(STATE_READY);
+        if (DEBUG) {
+            Log.d(TAG, mDebugId + ": Finished preparing, seekOnReady is " + mSeekOnReady);
+        }
+        if (mSeekOnReady >= 0) {
+            onSeekTo(mSeekOnReady);
+            mSeekOnReady = -1;
+        }
+        if (mPlayOnReady) {
+            player.start();
+            setState(STATE_PLAYING);
+        }
+    }
+
+    @Override
+    public void onBufferingUpdate(MediaPlayer player, int percent) {
+        if (!isCurrentPlayer(player)) {
+            return;
+        }
+        pushOnBufferingUpdate(percent);
+    }
+
+    @Override
+    public void onCompletion(MediaPlayer player) {
+        if (!isCurrentPlayer(player)) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, mDebugId + ": Completed item. Have next item? " + (mNextPlayer != null));
+        }
+        if (mNextPlayer != null) {
+            if (mPlayer != null) {
+                mPlayer.release();
+            }
+            mPlayer = mNextPlayer;
+            mContent = mNextContent;
+            mNextPlayer = null;
+            mNextContent = null;
+            pushOnNextStarted();
+            return;
+        }
+        setState(STATE_ENDED);
+    }
+
+    @Override
+    public boolean onError(MediaPlayer player, int what, int extra) {
+        if (!isCurrentPlayer(player)) {
+            return false;
+        }
+        if (DEBUG) {
+            Log.d(TAG, mDebugId + ": Entered error state, what: " + what + " extra: " + extra);
+        }
+        synchronized (mErrorLock) {
+            ++mErrorId;
+            mError = new PlaybackError();
+            mError.type = what;
+            mError.extra = extra;
+        }
+
+        if (what == MediaPlayer.MEDIA_ERROR_UNKNOWN && extra == MediaPlayer.MEDIA_ERROR_IO
+                && mContent != null && mContent.source.startsWith("http")) {
+            HttpGet request = new HttpGet(mContent.source);
+            if (mContent.headers != null) {
+                for (String key : mContent.headers.keySet()) {
+                    request.addHeader(key, mContent.headers.get(key));
+                }
+            }
+            synchronized (mErrorLock) {
+                if (mErrorRetriever != null) {
+                    mErrorRetriever.cancelRequestLocked(false);
+                }
+                mErrorRetriever = new AsyncErrorRetriever(mErrorId);
+                mErrorRetriever.execute(request);
+            }
+        } else {
+            setError(what, extra, null, null);
+        }
+        return true;
+    }
+
+    @Override
+    public void onAudioFocusChange(int focusChange) {
+        // TODO figure out appropriate logic for handling focus loss at the TUQ
+        // level.
+        switch (focusChange) {
+            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+                if (mState == STATE_PLAYING) {
+                    onPause();
+                    mPlayOnReady = true;
+                }
+                mHasAudioFocus = false;
+                break;
+            case AudioManager.AUDIOFOCUS_LOSS:
+                if (mState == STATE_PLAYING) {
+                    onPause();
+                    mPlayOnReady = false;
+                }
+                pushOnFocusLost();
+                mHasAudioFocus = false;
+                break;
+            case AudioManager.AUDIOFOCUS_GAIN:
+                mHasAudioFocus = true;
+                if (mPlayOnReady) {
+                    onPlay();
+                }
+                break;
+            default:
+                Log.d(TAG, "Unknown focus change event " + focusChange);
+                break;
+        }
+    }
+
+    @Override
+    public void setContent(Bundle request) {
+        setContent(request, null);
+    }
+
+    /**
+     * Prepares the player for the given playback request. If the holder is null
+     * it is assumed this is an audio only source. If playOnReady is set to true
+     * the media will begin playing as soon as it can.
+     */
+    public void setContent(Bundle request, SurfaceHolder holder) {
+        String source = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
+        Map<String, String> headers = null; // request.mHeaders;
+        boolean playOnReady = true; // request.mPlayOnReady;
+        if (DEBUG) {
+            Log.d(TAG, mDebugId + ": Settings new content. Have a player? " + (mPlayer != null)
+                    + " have a next player? " + (mNextPlayer != null));
+        }
+        cleanUpPlayer();
+        setState(STATE_PREPARING);
+        mPlayOnReady = playOnReady;
+        mSeekOnReady = -1;
+        final MediaPlayer newPlayer = new MediaPlayer();
+
+        requestAudioFocus();
+
+        mPlayer = newPlayer;
+        mContent = new PlayerContent(source, headers);
+        try {
+            if (headers != null) {
+                Uri sourceUri = Uri.parse(source);
+                newPlayer.setDataSource(mContext, sourceUri, headers);
+            } else {
+                newPlayer.setDataSource(source);
+            }
+        } catch (Exception e) {
+            setError(Listener.ERROR_LOAD_FAILED, 0, null, e);
+            return;
+        }
+        if (isHolderReady(holder, newPlayer)) {
+            preparePlayer(newPlayer, true);
+        }
+    }
+
+    @Override
+    public void setNextContent(Bundle request) {
+        String source = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
+        Map<String, String> headers = null; // request.mHeaders;
+
+        // TODO support video
+
+        if (DEBUG) {
+            Log.d(TAG, mDebugId + ": Setting next content. Have player? " + (mPlayer != null)
+                    + " have next player? " + (mNextPlayer != null));
+        }
+
+        if (mPlayer == null) {
+            // The manager isn't being used to play anything, don't try to
+            // set a next.
+            return;
+        }
+        if (mNextPlayer != null) {
+            // Before setting up the new one clear out the old one and release
+            // it to ensure it doesn't play.
+            mPlayer.setNextMediaPlayer(null);
+            mNextPlayer.release();
+            mNextPlayer = null;
+            mNextContent = null;
+        }
+        if (source == null) {
+            // If there's no new content we're done
+            return;
+        }
+        final MediaPlayer newPlayer = new MediaPlayer();
+
+        try {
+            if (headers != null) {
+                Uri sourceUri = Uri.parse(source);
+                newPlayer.setDataSource(mContext, sourceUri, headers);
+            } else {
+                newPlayer.setDataSource(source);
+            }
+        } catch (Exception e) {
+            newPlayer.release();
+            // Don't return an error until we get to this item in playback
+            return;
+        }
+
+        if (preparePlayer(newPlayer, false)) {
+            mPlayer.setNextMediaPlayer(newPlayer);
+            mNextPlayer = newPlayer;
+            mNextContent = new PlayerContent(source, headers);
+        }
+    }
+
+    private void requestAudioFocus() {
+        int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN);
+        mHasAudioFocus = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+    }
+
+    /**
+     * Start the player if possible or queue it to play when ready. If the
+     * player is in a state where it will never be ready returns false.
+     *
+     * @return true if the content was started or will be started later
+     */
+    @Override
+    public boolean onPlay() {
+        MediaPlayer player = mPlayer;
+        if (player != null && mState == STATE_PLAYING) {
+            // already playing, just return
+            return true;
+        }
+        if (!mHasAudioFocus) {
+            requestAudioFocus();
+        }
+        if (player != null && canPlay()) {
+            player.start();
+            setState(STATE_PLAYING);
+        } else if (canReadyPlay()) {
+            mPlayOnReady = true;
+        } else if (!isPlaying()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Pause the player if possible or set it to not play when ready. If the
+     * player is in a state where it will never be ready returns false.
+     *
+     * @return true if the content was paused or will wait to play when ready
+     *         later
+     */
+    @Override
+    public boolean onPause() {
+        MediaPlayer player = mPlayer;
+        if (player != null && (mState & CAN_PAUSE) != 0) {
+            player.pause();
+            setState(STATE_PAUSED);
+        } else if ((mState & CAN_READY_PLAY) != 0) {
+            mPlayOnReady = false;
+        } else if (!isPaused()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Seek to a given position in the media. If the seek succeeded or will be
+     * performed when loading is complete returns true. If the position is not
+     * in range or the player will never be ready returns false.
+     *
+     * @param position The position to seek to in milliseconds
+     * @return true if playback was moved or will be moved when ready
+     */
+    @Override
+    public boolean onSeekTo(int position) {
+        MediaPlayer player = mPlayer;
+        if (player != null && (mState & CAN_SEEK) != 0) {
+            if (position < 0 || position >= getDuration()) {
+                return false;
+            } else {
+                if (mState == STATE_ENDED) {
+                    player.start();
+                    player.pause();
+                    setState(STATE_PAUSED);
+                }
+                player.seekTo(position);
+            }
+        } else if ((mState & CAN_READY_SEEK) != 0) {
+            mSeekOnReady = position;
+        } else {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Stop the player. It cannot be used again until
+     * {@link #setContent(String, boolean)} is called.
+     *
+     * @return true if stopping the player succeeded
+     */
+    @Override
+    public boolean onStop() {
+        cleanUpPlayer();
+        setState(STATE_STOPPED);
+        return true;
+    }
+
+    public boolean isPlaying() {
+        return mState == STATE_PLAYING;
+    }
+
+    public boolean isPaused() {
+        return mState == STATE_PAUSED;
+    }
+
+    @Override
+    public long getSeekPosition() {
+        return ((mState & CAN_GET_POSITION) == 0) ? -1 : mPlayer.getCurrentPosition();
+    }
+
+    @Override
+    public long getDuration() {
+        return ((mState & CAN_GET_POSITION) == 0) ? -1 : mPlayer.getDuration();
+    }
+
+    private boolean canPlay() {
+        return ((mState & CAN_PLAY) != 0) && mHasAudioFocus;
+    }
+
+    private boolean canReadyPlay() {
+        return (mState & CAN_PLAY) != 0 || (mState & CAN_READY_PLAY) != 0;
+    }
+
+    /**
+     * Sends a state update if the listener exists
+     */
+    private void setState(int state) {
+        if (state == mState) {
+            return;
+        }
+        Log.d(TAG, "Entering state " + state + " from state " + mState);
+        mState = state;
+        if (state != STATE_ERROR) {
+            // Don't notify error here, it'll get sent via onError
+            pushOnStateChanged(state);
+        }
+    }
+
+    private boolean preparePlayer(final MediaPlayer player, boolean current) {
+        player.setOnPreparedListener(this);
+        player.setOnBufferingUpdateListener(this);
+        player.setOnCompletionListener(this);
+        player.setOnErrorListener(this);
+        try {
+            player.prepareAsync();
+            if (current) {
+                setState(STATE_PREPARING);
+            }
+        } catch (IllegalStateException e) {
+            if (current) {
+                setError(Listener.ERROR_PREPARE_ERROR, 0, null, e);
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @param extra
+     * @param e
+     */
+    private void setError(int type, int extra, Bundle extras, Exception e) {
+        setState(STATE_ERROR);
+        pushOnError(type, extra, extras, e);
+        cleanUpPlayer();
+        return;
+    }
+
+    /**
+     * Checks if the holder is ready and either sets up a callback to wait for
+     * it or sets it directly. If
+     *
+     * @param holder
+     * @param player
+     * @return
+     */
+    private boolean isHolderReady(final SurfaceHolder holder, final MediaPlayer player) {
+        mHolder = holder;
+        if (holder != null) {
+            if (holder.getSurface() != null && holder.getSurface().isValid()) {
+                player.setDisplay(holder);
+                return true;
+            } else {
+                Log.w(TAG, "Holder not null, waiting for it to be ready");
+                // If the holder isn't ready yet add a callback to set the
+                // holder when it's ready.
+                SurfaceHolder.Callback cb = new SurfaceHolder.Callback() {
+                        @Override
+                    public void surfaceDestroyed(SurfaceHolder arg0) {
+                    }
+
+                        @Override
+                    public void surfaceCreated(SurfaceHolder arg0) {
+                        if (player.equals(mPlayer)) {
+                            player.setDisplay(arg0);
+                            preparePlayer(player, true);
+                        }
+                    }
+
+                        @Override
+                    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
+                    }
+                };
+                mHolderCB = cb;
+                holder.addCallback(cb);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void cleanUpPlayer() {
+        if (DEBUG) {
+            Log.d(TAG, mDebugId + ": Cleaning up current player");
+        }
+        synchronized (mErrorLock) {
+            mError = null;
+            if (mErrorRetriever != null) {
+                mErrorRetriever.cancelRequestLocked(false);
+                // Don't set to null as we may need to cancel again with true if
+                // the object gets destroyed.
+            }
+        }
+        mAudioManager.abandonAudioFocus(this);
+
+        SurfaceHolder.Callback cb = mHolderCB;
+        mHolderCB = null;
+        SurfaceHolder holder = mHolder;
+        mHolder = null;
+        if (holder != null && cb != null) {
+            holder.removeCallback(cb);
+        }
+
+        MediaPlayer player = mPlayer;
+        mPlayer = null;
+        if (player != null) {
+            player.reset();
+            player.release();
+        }
+    }
+
+    private boolean isCurrentPlayer(MediaPlayer player) {
+        return player.equals(mPlayer);
+    }
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
new file mode 100644
index 0000000..f9e6794
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
@@ -0,0 +1,59 @@
+package com.android.onemedia.playback;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v7.media.MediaItemMetadata;
+
+/**
+ * TODO: Insert description here. (generated by epastern)
+ */
+public class MediaItem implements Parcelable {
+    private Bundle mBundle;
+
+    public MediaItem() {
+
+    }
+
+    private MediaItem(Parcel in) {
+        mBundle = in.readBundle();
+    }
+
+    public String getTitle() {
+        return mBundle.getString(MediaItemMetadata.KEY_TITLE);
+    }
+
+    public String getArtist() {
+        return mBundle.getString(MediaItemMetadata.KEY_ALBUM_ARTIST);
+    }
+
+    /* (non-Javadoc)
+     * @see android.os.Parcelable#describeContents()
+     */
+    @Override
+    public int describeContents() {
+        // TODO(epastern): Auto-generated method stub
+        return 0;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBundle(mBundle);
+    }
+
+    public static final Parcelable.Creator<MediaItem> CREATOR
+            = new Parcelable.Creator<MediaItem>() {
+                public MediaItem createFromParcel(Parcel in) {
+                    return new MediaItem(in);
+                }
+
+                public MediaItem[] newArray(int size) {
+                    return new MediaItem[size];
+                }
+            };
+
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java b/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java
new file mode 100644
index 0000000..72d936c
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java
@@ -0,0 +1,10 @@
+package com.android.onemedia.playback;
+
+import android.os.Bundle;
+
+public class PlaybackError {
+    public int type;
+    public int extra;
+    public String errorMessage;
+    public Bundle errorExtras;
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java b/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java
new file mode 100644
index 0000000..2451bdf
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java
@@ -0,0 +1,199 @@
+package com.android.onemedia.playback;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TODO: Insert description here. (generated by epastern)
+ */
+public abstract class Renderer {
+    public static final String FEATURE_SET_CONTENT = "com.android.media.SET_CONTENT";
+    public static final String FEATURE_SET_NEXT_CONTENT = "com.android.media.SET_NEXT_CONTENT";
+    public static final String FEATURE_PLAY = "com.android.media.PLAY";
+    public static final String FEATURE_PAUSE = "com.android.media.PAUSE";
+    public static final String FEATURE_NEXT = "com.android.media.NEXT";
+    public static final String FEATURE_PREVIOUS = "com.android.media.PREVIOUS";
+    public static final String FEATURE_SEEK_TO = "com.android.media.SEEK_TO";
+    public static final String FEATURE_STOP = "com.android.media.STOP";
+    // TODO move states somewhere else
+    public static final int STATE_ERROR = 0;
+    /**
+     * The state MediaPlayerManager starts in before any action has been
+     * performed.
+     */
+    public static final int STATE_INIT = 1 << 0;
+    /**
+     * Indicates the source has been set and it is being prepared/buffered
+     * before starting playback.
+     */
+    public static final int STATE_PREPARING = 1 << 1;
+    /**
+     * The media is ready and playback can be started.
+     */
+    public static final int STATE_READY = 1 << 2;
+    /**
+     * The media is currently playing.
+     */
+    public static final int STATE_PLAYING = 1 << 3;
+    /**
+     * The media is currently paused.
+     */
+    public static final int STATE_PAUSED = 1 << 4;
+    /**
+     * The service has been stopped and cannot be started again until a new
+     * source has been set.
+     */
+    public static final int STATE_STOPPED = 1 << 5;
+    /**
+     * The playback has reached the end. It can be restarted by calling play().
+     */
+    public static final int STATE_ENDED = 1 << 6;
+
+    // TODO decide on proper way of describing features
+    protected List<String> mFeatures = new ArrayList<String>();
+    protected List<Listener> mListeners = new ArrayList<Listener>();
+
+    public Renderer(Context context, Bundle params) {
+        onCreate(params);
+        initFeatures(params);
+    }
+
+    abstract public void setContent(Bundle request);
+
+    public void onCreate(Bundle params) {
+        // Do nothing by default
+    }
+
+    public void setNextContent(Bundle request) {
+        throw new UnsupportedOperationException("setNextContent() is not supported.");
+    }
+
+    public List<String> getFeatures() {
+        return mFeatures;
+    }
+
+    public boolean onPlay() {
+        throw new UnsupportedOperationException("play is not supported.");
+    }
+
+    public boolean onPause() {
+        throw new UnsupportedOperationException("pause is not supported.");
+    }
+
+    public boolean onNext() {
+        throw new UnsupportedOperationException("next is not supported.");
+    }
+
+    public boolean onPrevious() {
+        throw new UnsupportedOperationException("previous is not supported.");
+    }
+
+    public boolean onStop() {
+        throw new UnsupportedOperationException("stop is not supported.");
+    }
+
+    public boolean onSeekTo(int time) {
+        throw new UnsupportedOperationException("seekTo is not supported.");
+    }
+
+    public long getSeekPosition() {
+        throw new UnsupportedOperationException("getSeekPosition is not supported.");
+    }
+
+    public long getDuration() {
+        throw new UnsupportedOperationException("getDuration is not supported.");
+    }
+
+    public int getPlayState() {
+        throw new UnsupportedOperationException("getPlayState is not supported.");
+    }
+
+    public void onDestroy() {
+        // Do nothing by default
+    }
+
+    public void registerListener(Listener listener) {
+        if (!mListeners.contains(listener)) {
+            mListeners.add(listener);
+        }
+    }
+
+    public void unregisterListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    protected void initFeatures(Bundle params) {
+        mFeatures.add(FEATURE_SET_CONTENT);
+    }
+
+    protected void pushOnError(int type, int extra, Bundle extras, Throwable error) {
+        for (Listener listener : mListeners) {
+            listener.onError(type, extra, extras, error);
+        }
+    }
+
+    protected void pushOnStateChanged(int newState) {
+        for (Listener listener : mListeners) {
+            listener.onStateChanged(newState);
+        }
+    }
+
+    protected void pushOnBufferingUpdate(int percent) {
+        for (Listener listener : mListeners) {
+            listener.onBufferingUpdate(percent);
+        }
+    }
+
+    protected void pushOnFocusLost() {
+        for (Listener listener : mListeners) {
+            listener.onFocusLost();
+        }
+    }
+
+    protected void pushOnNextStarted() {
+        for (Listener listener : mListeners) {
+            listener.onNextStarted();
+        }
+    }
+
+    public interface Listener {
+        public static final int ERROR_LOAD_FAILED = 1770;
+        public static final int ERROR_PREPARE_ERROR = 1771;
+        public static final int ERROR_PLAYBACK_FAILED = 1772;
+
+        /**
+         * When an error occurs onError will be called but not onStateChanged.
+         * The Manager will remain in the error state until
+         * {@link #setContent()} is called again.
+         */
+        public void onError(int type, int extra, Bundle extras,
+                Throwable error);
+
+        /**
+         * onStateChanged will be called whenever the state of the manager
+         * transitions except to an error state.
+         */
+        public void onStateChanged(int newState);
+
+        /**
+         * This is a passthrough of
+         * {@link MediaPlayer.OnBufferingUpdateListener}.
+         */
+        public void onBufferingUpdate(int percent);
+
+        /**
+         * Called when audio focus is lost and it is not transient or ducking.
+         */
+        public void onFocusLost();
+
+        /**
+         * Called when the next item was started playing. Only called if a next
+         * item has been set and the current item has ended.
+         */
+        public void onNextStarted();
+    }
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java b/tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java
new file mode 100644
index 0000000..f333fce
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java
@@ -0,0 +1,22 @@
+package com.android.onemedia.playback;
+
+import android.content.Context;
+import android.media.MediaRouter;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * TODO: Insert description here.
+ */
+public class RendererFactory {
+    private static final String TAG = "RendererFactory";
+
+    public Renderer createRenderer(MediaRouter.RouteInfo route, Context context, Bundle params) {
+        if (route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL) {
+            return new LocalRenderer(context, params);
+        }
+        Log.e(TAG, "Unable to create renderer for route of playback type "
+                + route.getPlaybackType());
+        return null;
+    }
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
new file mode 100644
index 0000000..9b50dad
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
@@ -0,0 +1,53 @@
+package com.android.onemedia.playback;
+
+import android.os.Bundle;
+import android.support.v7.media.MediaItemMetadata;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: Insert description here. (generated by epastern)
+ */
+public class RequestUtils {
+    public static final String ACTION_SET_CONTENT = "set_content";
+    public static final String ACTION_SET_NEXT_CONTENT = "set_next_content";
+
+    public static final String EXTRA_KEY_SOURCE = "source";
+    public static final String EXTRA_KEY_METADATA = "metadata";
+    public static final String EXTRA_KEY_HEADERS = "headers";
+
+    private RequestUtils() {
+    }
+
+    public static class ContentBuilder {
+        private Bundle mBundle;
+
+        public ContentBuilder() {
+            mBundle = new Bundle();
+        }
+
+        public ContentBuilder setSource(String source) {
+            mBundle.putString(EXTRA_KEY_SOURCE, source);
+            return this;
+        }
+
+        /**
+         * @see MediaItemMetadata
+         * @param metadata The metadata for this item
+         */
+        public ContentBuilder setMetadata(Bundle metadata) {
+            mBundle.putBundle(EXTRA_KEY_METADATA, metadata);
+            return this;
+        }
+
+        public ContentBuilder setHeaders(HashMap<String, String> headers) {
+            mBundle.putSerializable(EXTRA_KEY_HEADERS, headers);
+            return this;
+        }
+
+        public Bundle build() {
+            return mBundle;
+        }
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index a25fb59..60cd157 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -103,6 +103,9 @@
         if (familyName == null) {
             familyName = DEFAULT_FAMILY;
         }
+        if (style < 0) {
+            style = Typeface.NORMAL;
+        }
 
         Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style);
         if (sFontLoader != null) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
index 108b651..cc7338a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
@@ -57,7 +57,9 @@
     private static final String FONT_SUFFIX_NONE = ".ttf";
     private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf";
     private static final String FONT_SUFFIX_BOLD = "-Bold.ttf";
-    private static final String FONT_SUFFIX_ITALIC = "-Italic.ttf";
+    // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked
+    // separately.
+    private static final String FONT_SUFFIX_ITALIC = "Italic.ttf";
     private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf";
 
     // This must match the values of Typeface styles so that we can use them for indices in this
@@ -285,10 +287,10 @@
                             mFontInfo.font[Typeface.NORMAL] = font;
                         } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) {
                             mFontInfo.font[Typeface.BOLD] = font;
-                        } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) {
-                            mFontInfo.font[Typeface.ITALIC] = font;
                         } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) {
                             mFontInfo.font[Typeface.BOLD_ITALIC] = font;
+                        } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) {
+                            mFontInfo.font[Typeface.ITALIC] = font;
                         } else if (fileName.endsWith(FONT_SUFFIX_NONE)) {
                             mFontInfo.font[Typeface.NORMAL] = font;
                         }