Merge "Add test UsageStats App" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 37008e4..f0ee69d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2746,6 +2746,7 @@
     method public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthTokenByFeatures(java.lang.String, java.lang.String, java.lang.String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public android.accounts.AuthenticatorDescription[] getAuthenticatorTypes();
     method public java.lang.String getPassword(android.accounts.Account);
+    method public java.lang.String getPreviousName(android.accounts.Account);
     method public java.lang.String getUserData(android.accounts.Account, java.lang.String);
     method public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
     method public void invalidateAuthToken(java.lang.String, java.lang.String);
@@ -2753,6 +2754,7 @@
     method public java.lang.String peekAuthToken(android.accounts.Account, java.lang.String);
     method public android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
     method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener);
+    method public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler);
     method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
     method public void setPassword(android.accounts.Account, java.lang.String);
     method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
@@ -16164,10 +16166,11 @@
     ctor public MediaBrowser(android.content.Context, android.content.ComponentName, android.media.browse.MediaBrowser.ConnectionCallback, android.os.Bundle);
     method public void connect();
     method public void disconnect();
+    method public android.os.Bundle getExtras();
     method public android.net.Uri getRoot();
     method public android.media.session.MediaSession.Token getSessionToken();
     method public boolean isConnected();
-    method public void loadThumbnail(android.net.Uri, int, int, int, android.media.browse.MediaBrowser.ThumbnailCallback);
+    method public void loadThumbnail(android.net.Uri, int, int, android.media.browse.MediaBrowser.ThumbnailCallback);
     method public void subscribe(android.net.Uri, android.media.browse.MediaBrowser.SubscriptionCallback);
     method public void unsubscribe(android.net.Uri);
   }
@@ -16195,6 +16198,8 @@
     method public int describeContents();
     method public android.os.Bundle getExtras();
     method public int getFlags();
+    method public int getIconResId();
+    method public android.net.Uri getIconUri();
     method public java.lang.CharSequence getSummary();
     method public java.lang.CharSequence getTitle();
     method public android.net.Uri getUri();
@@ -16210,6 +16215,8 @@
     ctor public MediaBrowserItem.Builder(android.net.Uri, int, java.lang.CharSequence);
     method public android.media.browse.MediaBrowserItem build();
     method public android.media.browse.MediaBrowserItem.Builder setExtras(android.os.Bundle);
+    method public android.media.browse.MediaBrowserItem.Builder setIconResId(int);
+    method public android.media.browse.MediaBrowserItem.Builder setIconUri(android.net.Uri);
     method public android.media.browse.MediaBrowserItem.Builder setSummary(java.lang.CharSequence);
   }
 
@@ -16217,16 +16224,19 @@
     ctor public MediaBrowserService();
     method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public android.media.session.MediaSession.Token getSessionToken();
-    method public void notifyChange();
     method public void notifyChildrenChanged(android.net.Uri);
     method public android.os.IBinder onBind(android.content.Intent);
-    method public abstract android.net.Uri onGetRoot(java.lang.String, int, android.os.Bundle);
-    method public abstract android.graphics.Bitmap onGetThumbnail(android.net.Uri, int, int, int);
-    method public abstract java.util.List<android.media.browse.MediaBrowserItem> onLoadChildren(android.net.Uri);
+    method protected abstract android.media.browse.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
+    method protected abstract android.graphics.Bitmap onGetThumbnail(android.net.Uri, int, int);
+    method protected abstract java.util.List<android.media.browse.MediaBrowserItem> onLoadChildren(android.net.Uri);
     method public void setSessionToken(android.media.session.MediaSession.Token);
     field public static final java.lang.String SERVICE_ACTION = "android.media.browse.MediaBrowserService";
   }
 
+  public static class MediaBrowserService.BrowserRoot {
+    ctor public MediaBrowserService.BrowserRoot(android.net.Uri, android.os.Bundle);
+  }
+
 }
 
 package android.media.effect {
@@ -27322,7 +27332,6 @@
 package android.service.voice {
 
   public class AlwaysOnHotwordDetector {
-    method public int getAvailability();
     method public android.content.Intent getManageIntent(int);
     method public int getSupportedRecognitionModes();
     method public int startRecognition(int);
@@ -27344,6 +27353,7 @@
   }
 
   public static abstract interface AlwaysOnHotwordDetector.Callback {
+    method public abstract void onAvailabilityChanged(int);
     method public abstract void onDetected(byte[]);
     method public abstract void onDetectionStopped();
   }
@@ -27355,7 +27365,6 @@
     method public android.os.IBinder onBind(android.content.Intent);
     method public void onReady();
     method public void onShutdown();
-    method public void onSoundModelsChanged();
     method public void startSession(android.os.Bundle);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
     field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction";
@@ -28706,14 +28715,13 @@
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
-  public class RemoteCallVideoClient implements android.os.IBinder.DeathRecipient {
-    method public void binderDied();
-    method public void handleCallSessionEvent(int) throws android.os.RemoteException;
-    method public void handleCameraCapabilitiesChange(android.telecomm.CallCameraCapabilities) throws android.os.RemoteException;
-    method public void receiveSessionModifyRequest(android.telecomm.VideoCallProfile) throws android.os.RemoteException;
-    method public void receiveSessionModifyResponse(int, android.telecomm.VideoCallProfile, android.telecomm.VideoCallProfile) throws android.os.RemoteException;
-    method public void updateCallDataUsage(int) throws android.os.RemoteException;
-    method public void updatePeerDimensions(int, int) throws android.os.RemoteException;
+  public class RemoteCallVideoClient {
+    method public void handleCallSessionEvent(int);
+    method public void handleCameraCapabilitiesChange(android.telecomm.CallCameraCapabilities);
+    method public void receiveSessionModifyRequest(android.telecomm.VideoCallProfile);
+    method public void receiveSessionModifyResponse(int, android.telecomm.VideoCallProfile, android.telecomm.VideoCallProfile);
+    method public void updateCallDataUsage(int);
+    method public void updatePeerDimensions(int, int);
   }
 
   public class RemoteCallVideoProvider {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index aab6e80..09b484b 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -663,6 +663,72 @@
     }
 
     /**
+     * Rename the specified {@link Account}.  This is equivalent to removing
+     * the existing account and adding a new renamed account with the old
+     * account's user data.
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+     * and have the same UID as the account's authenticator.
+     *
+     * @param account The {@link Account} to rename
+     * @param newName String name to be associated with the account.
+     * @param callback Callback to invoke when the request completes, null for
+     *     no callback
+     * @param handler {@link Handler} identifying the callback thread, null for
+     *     the main thread
+     * @return An {@link AccountManagerFuture} which resolves to the Account
+     *     after the name change. If successful the account's name will be the
+     *     specified new name.
+     */
+    public AccountManagerFuture<Account> renameAccount(
+            final Account account,
+            final String newName,
+            AccountManagerCallback<Account> callback,
+            Handler handler) {
+        if (account == null) throw new IllegalArgumentException("account is null.");
+        if (TextUtils.isEmpty(newName)) {
+              throw new IllegalArgumentException("newName is empty or null.");
+        }
+        return new Future2Task<Account>(handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.renameAccount(mResponse, account, newName);
+            }
+            @Override
+            public Account bundleToResult(Bundle bundle) throws AuthenticatorException {
+                String name = bundle.getString(KEY_ACCOUNT_NAME);
+                String type = bundle.getString(KEY_ACCOUNT_TYPE);
+                return new Account(name, type);
+            }
+        }.start();
+    }
+
+    /**
+     * Gets the previous name associated with the account or {@code null}, if
+     * none. This is intended so that clients of {@link
+     * #LOGIN_ACCOUNTS_CHANGED_ACTION} broadcasts can determine if an
+     * authenticator has renamed an account.
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * @param account The account to query for a previous name.
+     * @return The account's previous name, null if the account has never been
+     *         renamed.
+     */
+    public String getPreviousName(final Account account) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        try {
+            return mService.getPreviousName(account);
+        } catch (RemoteException e) {
+            // will never happen
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
      * Removes an account from the AccountManager.  Does nothing if the account
      * does not exist.  Does not delete the account from the server.
      * The authenticator may have its own policies preventing account
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index a04875d..a133788 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -69,4 +69,10 @@
     boolean addSharedAccountAsUser(in Account account, int userId);
     Account[] getSharedAccountsAsUser(int userId);
     boolean removeSharedAccountAsUser(in Account account, int userId);
+
+    /* Account renaming. */
+    void renameAccount(in IAccountManagerResponse response, in Account accountToRename, String newName);
+    String getPreviousName(in Account account);
+    boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
+
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4d6bd4b..175b6a5 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -413,8 +413,7 @@
         registerService(CONNECTIVITY_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
                     IBinder b = ServiceManager.getService(CONNECTIVITY_SERVICE);
-                    return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b),
-                        ctx.getPackageName());
+                    return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b));
                 }});
 
         registerService(COUNTRY_DETECTOR, new StaticServiceFetcher() {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 8de545e..13364cd 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -543,8 +543,6 @@
 
     private final IConnectivityManager mService;
 
-    private final String mPackageName;
-
     private INetworkManagementService mNMService;
 
     /**
@@ -646,23 +644,6 @@
     }
 
     /**
-     * Checks if the given network type should be exempt from VPN routing rules
-     *
-     * @hide
-     */
-    public static boolean isNetworkTypeExempt(int networkType) {
-        switch (networkType) {
-            case TYPE_MOBILE_MMS:
-            case TYPE_MOBILE_SUPL:
-            case TYPE_MOBILE_HIPRI:
-            case TYPE_MOBILE_IA:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    /**
      * Specifies the preferred network type.  When the device has more
      * than one type available the preferred network type will be used.
      *
@@ -1236,13 +1217,7 @@
      *             {@link #setProcessDefaultNetwork} and {@link Network#getSocketFactory} api.
      */
     public boolean requestRouteToHost(int networkType, int hostAddress) {
-        InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
-
-        if (inetAddress == null) {
-            return false;
-        }
-
-        return requestRouteToHostAddress(networkType, inetAddress);
+        return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress));
     }
 
     /**
@@ -1260,9 +1235,8 @@
      *             {@link #setProcessDefaultNetwork} api.
      */
     public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
-        byte[] address = hostAddress.getAddress();
         try {
-            return mService.requestRouteToHostAddress(networkType, address, mPackageName);
+            return mService.requestRouteToHostAddress(networkType, hostAddress.getAddress());
         } catch (RemoteException e) {
             return false;
         }
@@ -1433,9 +1407,8 @@
     /**
      * {@hide}
      */
-    public ConnectivityManager(IConnectivityManager service, String packageName) {
+    public ConnectivityManager(IConnectivityManager service) {
         mService = checkNotNull(service, "missing IConnectivityManager");
-        mPackageName = checkNotNull(packageName, "missing package name");
     }
 
     /** {@hide} */
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index f61984a..8b12fb8 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -69,9 +69,7 @@
 
     int stopUsingNetworkFeature(int networkType, in String feature);
 
-    boolean requestRouteToHost(int networkType, int hostAddress, String packageName);
-
-    boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, String packageName);
+    boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
 
     /** Policy control over specific {@link NetworkStateTracker}. */
     void setPolicyDataEnable(int networkType, boolean enabled);
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 9b95305..663aa15 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -36,12 +36,6 @@
 
     private static final String TAG = "NetworkUtils";
 
-    /** Bring the named network interface up. */
-    public native static int enableInterface(String interfaceName);
-
-    /** Bring the named network interface down. */
-    public native static int disableInterface(String interfaceName);
-
     /** Setting bit 0 indicates reseting of IPv4 addresses required */
     public static final int RESET_IPV4_ADDRESSES = 0x01;
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 403f2c9..c1e6664 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -19,7 +19,6 @@
 
 import android.net.InterfaceConfiguration;
 import android.net.INetworkManagementEventObserver;
-import android.net.LinkAddress;
 import android.net.NetworkStats;
 import android.net.RouteInfo;
 import android.net.UidRange;
@@ -313,11 +312,6 @@
      */
     void setDnsServersForNetwork(int netId, in String[] servers, String domains);
 
-    /**
-     * Flush the DNS cache associated with the specified network.
-     */
-    void flushNetworkDnsCache(int netId);
-
     void setFirewallEnabled(boolean enabled);
     boolean isFirewallEnabled();
     void setFirewallInterfaceRule(String iface, boolean allow);
@@ -336,17 +330,6 @@
     void removeVpnUidRanges(int netId, in UidRange[] ranges);
 
     /**
-     * Exempts {@code host} from the routing set up by {@link setMarkedForwardingRoute}
-     * All connects to {@code host} will use the global routing table
-     */
-    void setHostExemption(in LinkAddress host);
-
-    /**
-     * Clears an exemption set by {@link setHostExemption}
-     */
-    void clearHostExemption(in LinkAddress host);
-
-    /**
      * Start the clatd (464xlat) service
      */
     void startClatd(String interfaceName);
@@ -402,7 +385,6 @@
     void removeInterfaceFromNetwork(String iface, int netId);
 
     void addLegacyRouteForNetId(int netId, in RouteInfo routeInfo, int uid);
-    void removeLegacyRouteForNetId(int netId, in RouteInfo routeInfo, int uid);
 
     void setDefaultNetId(int netId);
     void clearDefaultNetId();
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 27a7b8e..d685cc5 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -27,6 +27,7 @@
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
@@ -41,7 +42,7 @@
  * always-on keyphrase detection APIs.
  */
 public class AlwaysOnHotwordDetector {
-    //---- States of Keyphrase availability. Return codes for getAvailability() ----//
+    //---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----//
     /**
      * Indicates that this hotword detector is no longer valid for any recognition
      * and should not be used anymore.
@@ -66,6 +67,11 @@
      */
     public static final int STATE_KEYPHRASE_ENROLLED = 2;
 
+    /**
+     * Indicates that the detector isn't ready currently.
+     */
+    private static final int STATE_NOT_READY = 0;
+
     // Keyphrase management actions. Used in getManageIntent() ----//
     /** Indicates that we need to enroll. */
     public static final int MANAGE_ACTION_ENROLL = 0;
@@ -104,9 +110,12 @@
             = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
 
     static final String TAG = "AlwaysOnHotwordDetector";
+    // TODO: Set to false.
+    static final boolean DBG = true;
 
-    private static final int MSG_HOTWORD_DETECTED = 1;
-    private static final int MSG_DETECTION_STOPPED = 2;
+    private static final int MSG_STATE_CHANGED = 1;
+    private static final int MSG_HOTWORD_DETECTED = 2;
+    private static final int MSG_DETECTION_STOPPED = 3;
 
     private final String mText;
     private final String mLocale;
@@ -120,21 +129,40 @@
     private final IVoiceInteractionManagerService mModelManagementService;
     private final SoundTriggerListener mInternalCallback;
     private final Callback mExternalCallback;
-    private final boolean mDisabled;
     private final Object mLock = new Object();
+    private final Handler mHandler;
 
     /**
      * The sound model for the keyphrase, derived from the model management service
      * (IVoiceInteractionManagerService). May be null if the keyphrase isn't enrolled yet.
      */
     private KeyphraseSoundModel mEnrolledSoundModel;
-    private boolean mInvalidated;
+    private int mAvailability = STATE_NOT_READY;
 
     /**
      * Callbacks for always-on hotword detection.
      */
     public interface Callback {
         /**
+         * Called when the hotword availability changes.
+         * This indicates a change in the availability of recognition for the given keyphrase.
+         * It's called at least once with the initial availability.<p/>
+         *
+         * Availability implies whether the hardware on this system is capable of listening for
+         * the given keyphrase or not. <p/>
+         * If the return code is one of {@link #STATE_HARDWARE_UNAVAILABLE} or
+         * {@link #STATE_KEYPHRASE_UNSUPPORTED},
+         * detection is not possible and no further interaction should be
+         * performed with this detector. <br/>
+         * If it is {@link #STATE_KEYPHRASE_UNENROLLED} the caller may choose to begin
+         * an enrollment flow for the keyphrase. <br/>
+         * and for {@link #STATE_KEYPHRASE_ENROLLED} a recognition can be started as desired. <p/>
+         *
+         * If the return code is {@link #STATE_INVALID}, this detector is stale.
+         * A new detector should be obtained for use in the future.
+         */
+        void onAvailabilityChanged(int status);
+        /**
          * Called when the keyphrase is spoken.
          *
          * @param data Optional trigger audio data, if it was requested during
@@ -160,54 +188,24 @@
             KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
             IVoiceInteractionService voiceInteractionService,
             IVoiceInteractionManagerService modelManagementService) {
-        mInvalidated = false;
         mText = text;
         mLocale = locale;
         mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
         mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale);
         mExternalCallback = callback;
-        mInternalCallback = new SoundTriggerListener(new MyHandler());
+        mHandler = new MyHandler();
+        mInternalCallback = new SoundTriggerListener(mHandler);
         mVoiceInteractionService = voiceInteractionService;
         mModelManagementService = modelManagementService;
-        if (mKeyphraseMetadata != null) {
-            mEnrolledSoundModel = internalGetKeyphraseSoundModelLocked(mKeyphraseMetadata.id);
-        }
-        int initialAvailability = internalGetAvailabilityLocked();
-        mDisabled = (initialAvailability == STATE_HARDWARE_UNAVAILABLE)
-                || (initialAvailability == STATE_KEYPHRASE_UNSUPPORTED);
-    }
-
-    /**
-     * Gets the state of always-on hotword detection for the given keyphrase and locale
-     * on this system.
-     * Availability implies that the hardware on this system is capable of listening for
-     * the given keyphrase or not. <p/>
-     * If the return code is one of {@link #STATE_HARDWARE_UNAVAILABLE} or
-     * {@link #STATE_KEYPHRASE_UNSUPPORTED}, no further interaction should be performed with this
-     * detector. <br/>
-     * If the state is {@link #STATE_KEYPHRASE_UNENROLLED} the caller may choose to begin
-     * an enrollment flow for the keyphrase. <br/>
-     * For {@value #STATE_KEYPHRASE_ENROLLED} a recognition can be started as desired. <br/>
-     * If the return code is {@link #STATE_INVALID}, this detector is stale and must not be used.
-     * A new detector should be obtained and used.
-     *
-     * @return Indicates if always-on hotword detection is available for the given keyphrase.
-     *         The return code is one of {@link #STATE_HARDWARE_UNAVAILABLE},
-     *         {@link #STATE_KEYPHRASE_UNSUPPORTED}, {@link #STATE_KEYPHRASE_UNENROLLED},
-     *         {@link #STATE_KEYPHRASE_ENROLLED}, or {@link #STATE_INVALID}.
-     */
-    public int getAvailability() {
-        synchronized (mLock) {
-            return internalGetAvailabilityLocked();
-        }
+        new RefreshAvailabiltyTask().execute();
     }
 
     /**
      * Gets the recognition modes supported by the associated keyphrase.
      *
      * @throws UnsupportedOperationException if the keyphrase itself isn't supported.
-     *         Callers should check the availability by calling {@link #getAvailability()}
-     *         before calling this method to avoid this exception.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
      */
     public int getSupportedRecognitionModes() {
         synchronized (mLock) {
@@ -216,7 +214,9 @@
     }
 
     private int getSupportedRecognitionModesLocked() {
-        if (mDisabled) {
+        // This method only makes sense if we can actually support a recognition.
+        if (mAvailability != STATE_KEYPHRASE_ENROLLED
+                && mAvailability != STATE_KEYPHRASE_UNENROLLED) {
             throw new UnsupportedOperationException(
                     "Getting supported recognition modes for the keyphrase is not supported");
         }
@@ -232,8 +232,8 @@
      *        {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO}.
      * @return {@link #STATUS_OK} if the call succeeds, an error code otherwise.
      * @throws UnsupportedOperationException if the recognition isn't supported.
-     *         Callers should check the availability by calling {@link #getAvailability()}
-     *         before calling this method to avoid this exception.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
      */
     public int startRecognition(int recognitionFlags) {
         synchronized (mLock) {
@@ -242,7 +242,8 @@
     }
 
     private int startRecognitionLocked(int recognitionFlags) {
-        if (internalGetAvailabilityLocked() != STATE_KEYPHRASE_ENROLLED) {
+        // This method only makes sense if we can start a recognition.
+        if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
             throw new UnsupportedOperationException(
                     "Recognition for the given keyphrase is not supported");
         }
@@ -273,8 +274,8 @@
      *
      * @return {@link #STATUS_OK} if the call succeeds, an error code otherwise.
      * @throws UnsupportedOperationException if the recognition isn't supported.
-     *         Callers should check the availability by calling {@link #getAvailability()}
-     *         before calling this method to avoid this exception.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
      */
     public int stopRecognition() {
         synchronized (mLock) {
@@ -283,7 +284,8 @@
     }
 
     private int stopRecognitionLocked() {
-        if (internalGetAvailabilityLocked() != STATE_KEYPHRASE_ENROLLED) {
+        // This method only makes sense if we can start a recognition.
+        if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
             throw new UnsupportedOperationException(
                     "Recognition for the given keyphrase is not supported");
         }
@@ -310,11 +312,13 @@
      *        {@link #MANAGE_ACTION_UN_ENROLL}.
      * @return An {@link Intent} to manage the given keyphrase.
      * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
-     *         Callers should check the availability by calling {@link #getAvailability()}
-     *         before calling this method to avoid this exception.
+     *         Callers should only call this method after a supported state callback on
+     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
      */
     public Intent getManageIntent(int action) {
-        if (mDisabled) {
+        // This method only makes sense if we can actually support a recognition.
+        if (mAvailability != STATE_KEYPHRASE_ENROLLED
+                && mAvailability != STATE_KEYPHRASE_UNENROLLED) {
             throw new UnsupportedOperationException(
                     "Managing the given keyphrase is not supported");
         }
@@ -327,34 +331,6 @@
         return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale);
     }
 
-    private int internalGetAvailabilityLocked() {
-        if (mInvalidated) {
-            return STATE_INVALID;
-        }
-
-        ModuleProperties dspModuleProperties = null;
-        try {
-            dspModuleProperties =
-                    mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException in getDspProperties!");
-        }
-        // No DSP available
-        if (dspModuleProperties == null) {
-            return STATE_HARDWARE_UNAVAILABLE;
-        }
-        // No enrollment application supports this keyphrase/locale
-        if (mKeyphraseMetadata == null) {
-            return STATE_KEYPHRASE_UNSUPPORTED;
-        }
-
-        // This keyphrase hasn't been enrolled.
-        if (mEnrolledSoundModel == null) {
-            return STATE_KEYPHRASE_UNENROLLED;
-        }
-        return STATE_KEYPHRASE_ENROLLED;
-    }
-
     /**
      * Invalidates this hotword detector so that any future calls to this result
      * in an IllegalStateException.
@@ -363,7 +339,8 @@
      */
     void invalidate() {
         synchronized (mLock) {
-            mInvalidated = true;
+            mAvailability = STATE_INVALID;
+            notifyStateChangedLocked();
         }
     }
 
@@ -376,38 +353,22 @@
         synchronized (mLock) {
             // TODO: This should stop the recognition if it was using an enrolled sound model
             // that's no longer available.
-            if (mKeyphraseMetadata != null) {
-                mEnrolledSoundModel = internalGetKeyphraseSoundModelLocked(mKeyphraseMetadata.id);
+            if (mAvailability == STATE_INVALID
+                    || mAvailability == STATE_HARDWARE_UNAVAILABLE
+                    || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) {
+                Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config");
+                return;
             }
+
+            // Execute a refresh availability task - which should then notify of a change.
+            new RefreshAvailabiltyTask().execute();
         }
     }
 
-    /**
-     * @return The corresponding {@link KeyphraseSoundModel} or null if none is found.
-     */
-    private KeyphraseSoundModel internalGetKeyphraseSoundModelLocked(int keyphraseId) {
-        List<KeyphraseSoundModel> soundModels;
-        try {
-            soundModels = mModelManagementService
-                    .listRegisteredKeyphraseSoundModels(mVoiceInteractionService);
-            if (soundModels == null || soundModels.isEmpty()) {
-                Slog.i(TAG, "No available sound models for keyphrase ID: " + keyphraseId);
-                return null;
-            }
-            for (KeyphraseSoundModel soundModel : soundModels) {
-                if (soundModel.keyphrases == null) {
-                    continue;
-                }
-                for (Keyphrase keyphrase : soundModel.keyphrases) {
-                    if (keyphrase.id == keyphraseId) {
-                        return soundModel;
-                    }
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!");
-        }
-        return null;
+    private void notifyStateChangedLocked() {
+        Message message = Message.obtain(mHandler, MSG_STATE_CHANGED);
+        message.arg1 = mAvailability;
+        message.sendToTarget();
     }
 
     /** @hide */
@@ -437,6 +398,9 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MSG_STATE_CHANGED:
+                    mExternalCallback.onAvailabilityChanged(msg.arg1);
+                    break;
                 case MSG_HOTWORD_DETECTED:
                     mExternalCallback.onDetected((byte[]) msg.obj);
                     break;
@@ -447,4 +411,95 @@
             }
         }
     }
+
+    class RefreshAvailabiltyTask extends AsyncTask<Void, Void, Void> {
+
+        @Override
+        public Void doInBackground(Void... params) {
+            int availability = internalGetInitialAvailability();
+            KeyphraseSoundModel soundModel = null;
+            // Fetch the sound model if the availability is one of the supported ones.
+            if (availability == STATE_NOT_READY
+                    || availability == STATE_KEYPHRASE_UNENROLLED
+                    || availability == STATE_KEYPHRASE_ENROLLED) {
+                soundModel =
+                        internalGetKeyphraseSoundModel(mKeyphraseMetadata.id);
+                if (soundModel == null) {
+                    availability = STATE_KEYPHRASE_UNENROLLED;
+                } else {
+                    availability = STATE_KEYPHRASE_ENROLLED;
+                }
+            }
+
+            synchronized (mLock) {
+                if (DBG) {
+                    Slog.d(TAG, "Hotword availability changed from " + mAvailability
+                            + " -> " + availability);
+                }
+                mAvailability = availability;
+                mEnrolledSoundModel = soundModel;
+                notifyStateChangedLocked();
+            }
+            return null;
+        }
+
+        /**
+         * @return The initial availability without checking the enrollment status.
+         */
+        private int internalGetInitialAvailability() {
+            synchronized (mLock) {
+                // This detector has already been invalidated.
+                if (mAvailability == STATE_INVALID) {
+                    return STATE_INVALID;
+                }
+            }
+
+            ModuleProperties dspModuleProperties = null;
+            try {
+                dspModuleProperties =
+                        mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "RemoteException in getDspProperties!");
+            }
+            // No DSP available
+            if (dspModuleProperties == null) {
+                return STATE_HARDWARE_UNAVAILABLE;
+            }
+            // No enrollment application supports this keyphrase/locale
+            if (mKeyphraseMetadata == null) {
+                return STATE_KEYPHRASE_UNSUPPORTED;
+            }
+            return STATE_NOT_READY;
+        }
+
+        /**
+         * @return The corresponding {@link KeyphraseSoundModel} or null if none is found.
+         */
+        private KeyphraseSoundModel internalGetKeyphraseSoundModel(int keyphraseId) {
+            List<KeyphraseSoundModel> soundModels;
+            try {
+                soundModels = mModelManagementService
+                        .listRegisteredKeyphraseSoundModels(mVoiceInteractionService);
+                if (soundModels == null || soundModels.isEmpty()) {
+                    Slog.i(TAG, "No available sound models for keyphrase ID: " + keyphraseId);
+                    return null;
+                }
+                for (int i = 0; i < soundModels.size(); i++) {
+                    KeyphraseSoundModel soundModel = soundModels.get(i);
+                    if (soundModel.keyphrases == null || soundModel.keyphrases.length == 0) {
+                        continue;
+                    }
+                    for (int j = 0; i < soundModel.keyphrases.length; j++) {
+                        Keyphrase keyphrase = soundModel.keyphrases[j];
+                        if (keyphrase.id == keyphraseId) {
+                            return soundModel;
+                        }
+                    }
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!");
+            }
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 0c2ba26..82e23c4 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -193,17 +193,6 @@
                 mHotwordDetector.onSoundModelsChanged();
             }
         }
-        onSoundModelsChanged();
-    }
-
-    /**
-     * Called when the sound models available for recognition change.
-     * This may be called if a new sound model is available or
-     * an existing one is updated or removed.
-     * Implementations must check the availability of the hotword detector if they own one
-     * by calling {@link AlwaysOnHotwordDetector#getAvailability()} before calling into it.
-     */
-    public void onSoundModelsChanged() {
     }
 
     /**
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 0a1204d..73c73218 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -346,7 +346,9 @@
      * Deep copies the data into native to simplify reference ownership.
      */
     public boolean setOutline(Outline outline) {
-        if (outline == null || outline.isEmpty()) {
+        if (outline == null) {
+            return nSetOutlineNone(mNativeRenderNode);
+        } else if (outline.isEmpty()) {
             return nSetOutlineEmpty(mNativeRenderNode);
         } else if (outline.mRect != null) {
             return nSetOutlineRoundRect(mNativeRenderNode, outline.mRect.left, outline.mRect.top,
@@ -878,6 +880,7 @@
             int right, int bottom, float radius);
     private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath);
     private static native boolean nSetOutlineEmpty(long renderNode);
+    private static native boolean nSetOutlineNone(long renderNode);
     private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
     private static native boolean nSetRevealClip(long renderNode,
             boolean shouldClip, boolean inverseClip, float x, float y, float radius);
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 79557ad..71102e8 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -234,6 +234,8 @@
             setSubtitle(subtitle);
         }
 
+        // Set the default context, since setPopupTheme() may be a no-op.
+        mPopupContext = mContext;
         setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0));
         a.recycle();
     }
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 901d6e6..d91eb69 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -27,6 +27,10 @@
     // be kept in sync with frameworks/native/include/binder/IBatteryStats.h
     void noteStartSensor(int uid, int sensor);
     void noteStopSensor(int uid, int sensor);
+    void noteStartVideo(int uid);
+    void noteStopVideo(int uid);
+    void noteStartAudio(int uid);
+    void noteStopAudio(int uid);
 
     // Remaining methods are only used in Java.
     byte[] getStatistics();
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 760ed45..32cf286 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -79,26 +79,6 @@
     jmethodID setVendorInfo;
 } dhcpResultsFieldIds;
 
-static jint android_net_utils_enableInterface(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    int result;
-
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    result = ::ifc_enable(nameStr);
-    env->ReleaseStringUTFChars(ifname, nameStr);
-    return (jint)result;
-}
-
-static jint android_net_utils_disableInterface(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    int result;
-
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    result = ::ifc_disable(nameStr);
-    env->ReleaseStringUTFChars(ifname, nameStr);
-    return (jint)result;
-}
-
 static jint android_net_utils_resetConnections(JNIEnv* env, jobject clazz,
       jstring ifname, jint mask)
 {
@@ -280,9 +260,6 @@
  */
 static JNINativeMethod gNetworkUtilMethods[] = {
     /* name, signature, funcPtr */
-
-    { "enableInterface", "(Ljava/lang/String;)I",  (void *)android_net_utils_enableInterface },
-    { "disableInterface", "(Ljava/lang/String;)I",  (void *)android_net_utils_disableInterface },
     { "resetConnections", "(Ljava/lang/String;I)I",  (void *)android_net_utils_resetConnections },
     { "runDhcp", "(Ljava/lang/String;Landroid/net/DhcpResults;)Z",  (void *)android_net_utils_runDhcp },
     { "runDhcpRenew", "(Ljava/lang/String;Landroid/net/DhcpResults;)Z",  (void *)android_net_utils_runDhcpRenew },
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index c6ae9cc..1f3909a 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -153,6 +153,14 @@
     return true;
 }
 
+static jboolean android_view_RenderNode_setOutlineNone(JNIEnv* env,
+        jobject clazz, jlong renderNodePtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    renderNode->mutateStagingProperties().mutableOutline().setNone();
+    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
+}
+
 static jboolean android_view_RenderNode_setClipToOutline(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jboolean clipToOutline) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -486,6 +494,7 @@
     { "nSetOutlineRoundRect",  "(JIIIIF)Z", (void*) android_view_RenderNode_setOutlineRoundRect },
     { "nSetOutlineConvexPath", "(JJ)Z",  (void*) android_view_RenderNode_setOutlineConvexPath },
     { "nSetOutlineEmpty",      "(J)Z",   (void*) android_view_RenderNode_setOutlineEmpty },
+    { "nSetOutlineNone",       "(J)Z",   (void*) android_view_RenderNode_setOutlineNone },
     { "nSetClipToOutline",     "(JZ)Z",  (void*) android_view_RenderNode_setClipToOutline },
     { "nSetRevealClip",        "(JZZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
 
diff --git a/core/res/res/drawable-hdpi/spinner_mtrl_am_alpha.9.png b/core/res/res/drawable-hdpi/spinner_mtrl_am_alpha.9.png
index de5819d..de7ac29 100644
--- a/core/res/res/drawable-hdpi/spinner_mtrl_am_alpha.9.png
+++ b/core/res/res/drawable-hdpi/spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinner_mtrl_am_alpha.9.png b/core/res/res/drawable-mdpi/spinner_mtrl_am_alpha.9.png
index f151bfb..bbf5928 100644
--- a/core/res/res/drawable-mdpi/spinner_mtrl_am_alpha.9.png
+++ b/core/res/res/drawable-mdpi/spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_mtrl_am_alpha.9.png b/core/res/res/drawable-xhdpi/spinner_mtrl_am_alpha.9.png
index c868ed1..d4bd169 100644
--- a/core/res/res/drawable-xhdpi/spinner_mtrl_am_alpha.9.png
+++ b/core/res/res/drawable-xhdpi/spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/spinner_mtrl_am_alpha.9.png b/core/res/res/drawable-xxhdpi/spinner_mtrl_am_alpha.9.png
index 7cb1486..2e7bc12 100644
--- a/core/res/res/drawable-xxhdpi/spinner_mtrl_am_alpha.9.png
+++ b/core/res/res/drawable-xxhdpi/spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/spinner_mtrl_am_alpha.9.png b/core/res/res/drawable-xxxhdpi/spinner_mtrl_am_alpha.9.png
index 2ae7234..1086e9d 100644
--- a/core/res/res/drawable-xxxhdpi/spinner_mtrl_am_alpha.9.png
+++ b/core/res/res/drawable-xxxhdpi/spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 00a04ba..73c6a67 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4185,6 +4185,8 @@
         <attr name="dropDownSelector" />
         <!-- Background drawable to use for the dropdown in spinnerMode="dropdown". -->
         <attr name="popupBackground" />
+        <!-- Window elevation to use for the dropdown in spinnerMode="dropdown". -->
+        <attr name="popupElevation" />
         <!-- Vertical offset from the spinner widget for positioning the dropdown in
              spinnerMode="dropdown". -->
         <attr name="dropDownVerticalOffset" />
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index e79f223..a8974c6 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -543,6 +543,7 @@
     <style name="Widget.Material.AutoCompleteTextView" parent="Widget.AutoCompleteTextView">
         <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
         <item name="popupBackground">@drawable/popup_background_material</item>
+        <item name="popupElevation">@dimen/floating_window_z</item>
     </style>
 
     <style name="Widget.Material.CompoundButton" parent="Widget.CompoundButton"/>
@@ -663,6 +664,7 @@
 
     <style name="Widget.Material.PopupWindow.ActionMode">
         <item name="popupBackground">@drawable/popup_background_material</item>
+        <item name="popupElevation">@dimen/floating_window_z</item>
         <item name="popupAnimationStyle">@style/Animation.PopupWindow.ActionMode</item>
     </style>
 
@@ -731,6 +733,7 @@
         <item name="background">@drawable/spinner_background_material</item>
         <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
         <item name="popupBackground">@drawable/popup_background_material</item>
+        <item name="popupElevation">@dimen/floating_window_z</item>
         <item name="dropDownVerticalOffset">0dip</item>
         <item name="dropDownHorizontalOffset">0dip</item>
         <item name="dropDownWidth">wrap_content</item>
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 1709558..3a4c2a7 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -155,6 +155,11 @@
      * Passing a zero radius is equivalent to calling {@link #setRect(int, int, int, int)}
      */
     public void setRoundRect(int left, int top, int right, int bottom, float radius) {
+        if (left >= right || top >= bottom) {
+            setEmpty();
+            return;
+        }
+
         if (mRect == null) mRect = new Rect();
         mRect.set(left, top, right, bottom);
         mRadius = radius;
@@ -173,11 +178,17 @@
      * Sets the outline to the oval defined by input rect.
      */
     public void setOval(int left, int top, int right, int bottom) {
+        if (left >= right || top >= bottom) {
+            setEmpty();
+            return;
+        }
+
         if ((bottom - top) == (right - left)) {
             // represent circle as round rect, for efficiency, and to enable clipping
             setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f);
             return;
         }
+
         if (mPath == null) mPath = new Path();
         mPath.reset();
         mPath.addOval(left, top, right, bottom, Path.Direction.CW);
@@ -196,6 +207,11 @@
      * Sets the Constructs an Outline from a {@link android.graphics.Path#isConvex() convex path}.
      */
     public void setConvexPath(@NonNull Path convexPath) {
+        if (convexPath.isEmpty()) {
+            setEmpty();
+            return;
+        }
+
         if (!convexPath.isConvex()) {
             throw new IllegalArgumentException("path must be convex");
         }
diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h
index 024bdfd..83426e8 100644
--- a/libs/hwui/Outline.h
+++ b/libs/hwui/Outline.h
@@ -49,15 +49,20 @@
         mBounds.set(outline->getBounds());
     }
 
-    bool isEmpty() const {
-        return mType == kOutlineType_None;
+    void setEmpty() {
+        mType = kOutlineType_Empty;
+        mPath.reset();
     }
 
-    void setEmpty() {
+    void setNone() {
         mType = kOutlineType_None;
         mPath.reset();
     }
 
+    bool isEmpty() const {
+        return mType == kOutlineType_Empty;
+    }
+
     void setShouldClip(bool clip) {
         mShouldClip = clip;
     }
@@ -81,7 +86,7 @@
     }
 
     const SkPath* getPath() const {
-        if (mType == kOutlineType_None) return NULL;
+        if (mType == kOutlineType_None || mType == kOutlineType_Empty) return NULL;
 
         return &mPath;
     }
@@ -89,8 +94,9 @@
 private:
     enum OutlineType {
         kOutlineType_None = 0,
-        kOutlineType_ConvexPath = 1,
-        kOutlineType_RoundRect = 2
+        kOutlineType_Empty = 1,
+        kOutlineType_ConvexPath = 2,
+        kOutlineType_RoundRect = 3
     };
 
     bool mShouldClip;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index fe03806..3eb779f 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -581,7 +581,7 @@
 
 template <class T>
 void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler) {
-    if (properties().getAlpha() <= 0.0f || properties().getOutline().isEmpty()) return;
+    if (properties().getAlpha() <= 0.0f || !properties().getOutline().getPath()) return;
 
     mat4 shadowMatrixXY(transformFromParent);
     applyViewPropertyTransforms(shadowMatrixXY);
@@ -776,16 +776,23 @@
  */
 template <class T>
 void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
+    const int level = handler.level();
+    if (mDisplayListData->isEmpty()) {
+        DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, getName());
+        return;
+    }
+
     const bool drawLayer = (mLayer && (&renderer != mLayer->renderer));
     // If we are updating the contents of mLayer, we don't want to apply any of
     // the RenderNode's properties to this issueOperations pass. Those will all
     // be applied when the layer is drawn, aka when this is true.
     const bool useViewProperties = (!mLayer || drawLayer);
-
-    const int level = handler.level();
-    if (mDisplayListData->isEmpty() || (useViewProperties && properties().getAlpha() <= 0)) {
-        DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, getName());
-        return;
+    if (useViewProperties) {
+        const Outline& outline = properties().getOutline();
+        if (properties().getAlpha() <= 0 || (outline.getShouldClip() && outline.isEmpty())) {
+            DISPLAY_LIST_LOGD("%*sRejected display list (%p, %s)", level * 2, "", this, getName());
+            return;
+        }
     }
 
     handler.startMark(getName());
diff --git a/media/java/android/media/browse/IMediaBrowserService.aidl b/media/java/android/media/browse/IMediaBrowserService.aidl
index 4b2cb9d..177bd1b 100644
--- a/media/java/android/media/browse/IMediaBrowserService.aidl
+++ b/media/java/android/media/browse/IMediaBrowserService.aidl
@@ -2,6 +2,7 @@
 
 package android.media.browse;
 
+import android.content.res.Configuration;
 import android.media.browse.IMediaBrowserServiceCallbacks;
 import android.net.Uri;
 import android.os.Bundle;
@@ -17,4 +18,5 @@
 
     void addSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks);
     void removeSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks);
+    void loadThumbnail(in Uri uri, int width, int height, IMediaBrowserServiceCallbacks callbacks);
 }
\ No newline at end of file
diff --git a/media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl b/media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl
index ead7624..1f03a1a 100644
--- a/media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl
+++ b/media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl
@@ -3,8 +3,10 @@
 package android.media.browse;
 
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.media.session.MediaSession;
 import android.net.Uri;
+import android.os.Bundle;
 
 /**
  * Media API allows clients to browse through hierarchy of a user’s media collection,
@@ -16,9 +18,11 @@
      * Invoked when the connected has been established.
      * @param root The root Uri for browsing.
      * @param session The {@link MediaSession.Token media session token} that can be used to control
-     * the playback of the media app.
+     *         the playback of the media app.
+     * @param extra Extras returned by the media service.
      */
-    void onConnect(in Uri root, in MediaSession.Token session);
+    void onConnect(in Uri root, in MediaSession.Token session, in Bundle extras);
     void onConnectFailed();
     void onLoadChildren(in Uri uri, in ParceledListSlice list);
+    void onLoadThumbnail(in Uri uri, int width, int height, in Bitmap bitmap);
 }
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index beec5f9..d17f95d 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -17,11 +17,13 @@
 package android.media.browse;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.pm.ParceledListSlice;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.media.session.MediaSession;
 import android.net.Uri;
@@ -36,7 +38,9 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Browses media content offered by a link MediaBrowserService.
@@ -61,6 +65,8 @@
     private final Handler mHandler = new Handler();
     private final ArrayMap<Uri,Subscription> mSubscriptions =
             new ArrayMap<Uri, MediaBrowser.Subscription>();
+    private final ArrayMap<ThumbnailRequest, HashSet<ThumbnailCallback>> mThumbnailCallbacks =
+            new ArrayMap<ThumbnailRequest, HashSet<ThumbnailCallback>>();
 
     private int mState = CONNECT_STATE_DISCONNECTED;
     private MediaServiceConnection mServiceConnection;
@@ -68,6 +74,7 @@
     private IMediaBrowserServiceCallbacks mServiceCallbacks;
     private Uri mRootUri;
     private MediaSession.Token mMediaSessionToken;
+    private Bundle mExtras;
 
     /**
      * Creates a media browser for the specified media browse service.
@@ -162,7 +169,6 @@
 
     /**
      * Disconnects from the media browse service.
-     * @more
      * After this, no more callbacks will be received.
      */
     public void disconnect() {
@@ -223,7 +229,7 @@
      * </p>
      *
      * @throws IllegalStateException if not connected.
-      */
+     */
     public @NonNull Uri getRoot() {
         if (mState != CONNECT_STATE_CONNECTED) {
             throw new IllegalStateException("getSessionToken() called while not connected (state="
@@ -233,6 +239,19 @@
     }
 
     /**
+     * Gets any extras for the media service.
+     *
+     * @throws IllegalStateException if not connected.
+     */
+    public @Nullable Bundle getExtras() {
+        if (mState != CONNECT_STATE_CONNECTED) {
+            throw new IllegalStateException("getExtras() called while not connected (state="
+                    + getStateLabel(mState) + ")");
+        }
+        return mExtras;
+    }
+
+    /**
      * Gets the media session token associated with the media browser.
      * <p>
      * Note that the session token may become invalid or change when when the
@@ -332,18 +351,45 @@
     /**
      * Loads the thumbnail of a media item.
      *
-     * @param uri The uri of the media item.
+     * @param uri The uri of the thumbnail.
      * @param width The preferred width of the icon in dp.
      * @param height The preferred width of the icon in dp.
-     * @param density The preferred density of the icon. Must be one of the android
-     *      density buckets.
      * @param callback The callback to receive the thumbnail.
-     *
-     * @throws IllegalStateException if not connected. TODO: Is this restriction necessary?
      */
-    public void loadThumbnail(@NonNull Uri uri, int width, int height, int density,
-            @NonNull ThumbnailCallback callback) {
-        throw new RuntimeException("implement me");
+    public void loadThumbnail(final @NonNull Uri uri, final int width, final int height,
+            final @NonNull ThumbnailCallback callback) {
+        if (uri == null) {
+            throw new IllegalArgumentException("thumbnail uri cannot be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("thumbnail callback cannot be null");
+        }
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                HashSet<ThumbnailCallback> callbackSet;
+                ThumbnailRequest request = new ThumbnailRequest(uri, width, height);
+                callbackSet = mThumbnailCallbacks.get(request);
+                if (callbackSet != null) {
+                    callbackSet.add(callback);
+                    mThumbnailCallbacks.put(request, callbackSet);
+                    // same request has been sent. we will wait for the callback.
+                    return;
+                }
+                callbackSet = new HashSet<ThumbnailCallback>();
+                callbackSet.add(callback);
+                mThumbnailCallbacks.put(request, callbackSet);
+                if (mState == CONNECT_STATE_CONNECTED) {
+                    try {
+                        mServiceBinder.loadThumbnail(uri, width, height, mServiceCallbacks);
+                    } catch (RemoteException e) {
+                        // Process is crashing.  We will disconnect, and upon reconnect we will
+                        // automatically reload the thumbnails. So nothing to do here.
+                        Log.d(TAG, "loadThumbnail failed with RemoteException uri=" + uri);
+                    }
+                }
+            }
+        });
     }
 
     /**
@@ -365,7 +411,7 @@
     }
 
     private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
-            final Uri root, final MediaSession.Token session) {
+            final Uri root, final MediaSession.Token session, final Bundle extra) {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -382,6 +428,7 @@
                 }
                 mRootUri = root;
                 mMediaSessionToken = session;
+                mExtras = extra;
                 mState = CONNECT_STATE_CONNECTED;
 
                 if (DBG) {
@@ -402,6 +449,17 @@
                     }
                 }
 
+                for (ThumbnailRequest request : mThumbnailCallbacks.keySet()) {
+                    try {
+                        mServiceBinder.loadThumbnail(request.uri, request.width, request.height,
+                                mServiceCallbacks);
+                    } catch (RemoteException e) {
+                        // Process is crashing.  We will disconnect, and upon reconnect we will
+                        // automatically reload. So nothing to do here.
+                        Log.d(TAG, "loadThumbnail failed with RemoteException uri=" + request.uri);
+                    }
+                }
+
             }
         });
     }
@@ -468,6 +526,31 @@
         });
     }
 
+    private final void onLoadThumbnail(final IMediaBrowserServiceCallbacks callback,
+            final ThumbnailRequest request, final Bitmap bitmap) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                // Check that there hasn't been a disconnect or a different
+                // ServiceConnection.
+                if (!isCurrent(callback, "onLoadThumbnail")) {
+                    return;
+                }
+
+                Set<ThumbnailCallback> callbackSet = mThumbnailCallbacks.get(request);
+                if (callbackSet == null) {
+                    Log.d(TAG, "onLoadThumbnail called for request=" + request +
+                            " but the callback is not registered");
+                    return;
+                }
+                for (ThumbnailCallback thumbnailCallback : callbackSet) {
+                    thumbnailCallback.onThumbnailLoaded(request.uri, bitmap);
+                }
+                mThumbnailCallbacks.remove(request);
+            }
+        });
+    }
+
 
     /**
      * Return true if {@code callback} is the current ServiceCallbacks.  Also logs if it's not.
@@ -567,6 +650,51 @@
         }
     }
 
+    private static class ThumbnailRequest {
+        Uri uri;
+        int width;
+        int height;
+        ThumbnailRequest(@NonNull Uri uri, int width, int height) {
+            if (uri == null) {
+                throw new IllegalArgumentException("thumbnail uri cannot be null");
+            }
+            this.uri = uri;
+            this.width = width;
+            this.height = height;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof ThumbnailRequest)) return false;
+
+            ThumbnailRequest that = (ThumbnailRequest) o;
+
+            if (height != that.height) return false;
+            if (width != that.width) return false;
+            if (!uri.equals(that.uri)) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = uri.hashCode();
+            result = 31 * result + width;
+            result = 31 * result + height;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "ThumbnailRequest{" +
+                    "uri=" + uri +
+                    ", width=" + width +
+                    ", height=" + height +
+                    '}';
+        }
+    }
+
     /**
      * ServiceConnection to the other app.
      */
@@ -666,10 +794,11 @@
          * are the initial data as requested.
          */
         @Override
-        public void onConnect(final Uri root, final MediaSession.Token session) {
+        public void onConnect(final Uri root, final MediaSession.Token session,
+                final Bundle extras) {
             MediaBrowser mediaBrowser = mMediaBrowser.get();
             if (mediaBrowser != null) {
-                mediaBrowser.onServiceConnected(this, root, session);
+                mediaBrowser.onServiceConnected(this, root, session, extras);
             }
         }
 
@@ -691,6 +820,15 @@
                 mediaBrowser.onLoadChildren(this, uri, list);
             }
         }
+
+        @Override
+        public void onLoadThumbnail(final Uri uri, int width, int height, final Bitmap bitmap) {
+            MediaBrowser mediaBrowser = mMediaBrowser.get();
+            if (mediaBrowser != null) {
+                ThumbnailRequest request = new ThumbnailRequest(uri, width, height);
+                mediaBrowser.onLoadThumbnail(this, request, bitmap);
+            }
+        }
     }
 
     private static class Subscription {
diff --git a/media/java/android/media/browse/MediaBrowserItem.java b/media/java/android/media/browse/MediaBrowserItem.java
index 119f687..38e765f 100644
--- a/media/java/android/media/browse/MediaBrowserItem.java
+++ b/media/java/android/media/browse/MediaBrowserItem.java
@@ -32,6 +32,8 @@
  */
 public final class MediaBrowserItem implements Parcelable {
     private final Uri mUri;
+    private final Uri mIconUri;
+    private final int mIconResId;
     private final int mFlags;
     private final CharSequence mTitle;
     private final CharSequence mSummary;
@@ -59,8 +61,8 @@
     /**
      * Initialize a MediaBrowserItem object.
      */
-    private MediaBrowserItem(@NonNull Uri uri, int flags, @NonNull CharSequence title,
-            CharSequence summary, Bundle extras) {
+    private MediaBrowserItem(@NonNull Uri uri, @Nullable Uri iconUri, int iconResId, int flags,
+            @NonNull CharSequence title, CharSequence summary, Bundle extras) {
         if (uri == null) {
             throw new IllegalArgumentException("uri can not be null");
         }
@@ -68,6 +70,8 @@
             throw new IllegalArgumentException("title can not be null");
         }
         mUri = uri;
+        mIconUri = iconUri;
+        mIconResId = iconResId;
         mFlags = flags;
         mTitle = title;
         mSummary = summary;
@@ -79,6 +83,8 @@
      */
     private MediaBrowserItem(Parcel in) {
         mUri = Uri.CREATOR.createFromParcel(in);
+        mIconUri = Uri.CREATOR.createFromParcel(in);
+        mIconResId = in.readInt();
         mFlags = in.readInt();
         mTitle = in.readCharSequence();
         if (in.readInt() != 0) {
@@ -101,6 +107,8 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         mUri.writeToParcel(out, flags);
+        mIconUri.writeToParcel(out, flags);
+        out.writeInt(mIconResId);
         out.writeInt(mFlags);
         out.writeCharSequence(mTitle);
         if (mSummary != null) {
@@ -138,6 +146,20 @@
     }
 
     /**
+     * Gets the Uri of the icon.
+     */
+    public @Nullable Uri getIconUri() {
+        return mIconUri;
+    }
+
+    /**
+     * Gets the resource id of the icon.
+     */
+    public int getIconResId() {
+        return mIconResId;
+    }
+
+    /**
      * Gets the flags of the item.
      */
     public @Flags int getFlags() {
@@ -195,6 +217,8 @@
         private final Uri mUri;
         private final int mFlags;
         private final CharSequence mTitle;
+        private Uri mIconUri;
+        private int mIconResId;
         private CharSequence mSummary;
         private Bundle mExtras;
 
@@ -214,6 +238,26 @@
         }
 
         /**
+         * Sets the uri of the icon.
+         * <p>
+         * If both {@link #setIconUri(Uri)} and {@link #setIconResId(int)} are called,
+         * the resource id will be used to load the icon.
+         * </p>
+         */
+        public @NonNull Builder setIconUri(@Nullable Uri iconUri) {
+            mIconUri = iconUri;
+            return this;
+        }
+
+        /**
+         * Sets the resource id of the icon.
+         */
+        public @NonNull Builder setIconResId(int resId) {
+            mIconResId = resId;
+            return this;
+        }
+
+        /**
          * Sets summary of the item, or null if none.
          */
         public @NonNull Builder setSummary(@Nullable CharSequence summary) {
@@ -234,7 +278,8 @@
         * Builds the item.
         */
         public @NonNull MediaBrowserItem build() {
-            return new MediaBrowserItem(mUri, mFlags, mTitle, mSummary, mExtras);
+            return new MediaBrowserItem(mUri, mIconUri, mIconResId,
+                    mFlags, mTitle, mSummary, mExtras);
         }
     }
 }
diff --git a/media/java/android/media/browse/MediaBrowserService.java b/media/java/android/media/browse/MediaBrowserService.java
index ceb4b03..57befe7 100644
--- a/media/java/android/media/browse/MediaBrowserService.java
+++ b/media/java/android/media/browse/MediaBrowserService.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.media.session.MediaSession;
 import android.net.Uri;
@@ -43,6 +44,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Base class for media browse services.
@@ -81,7 +83,7 @@
         String pkg;
         Bundle rootHints;
         IMediaBrowserServiceCallbacks callbacks;
-        Uri root;
+        BrowserRoot root;
         HashSet<Uri> subscriptions = new HashSet();
     }
 
@@ -130,7 +132,8 @@
                         } else {
                             try {
                                 mConnections.put(b, connection);
-                                callbacks.onConnect(connection.root, mSession);
+                                callbacks.onConnect(connection.root.getRootUri(),
+                                        mSession, connection.root.getExtras());
                             } catch (RemoteException ex) {
                                 Log.w(TAG, "Calling onConnect() failed. Dropping client. "
                                         + "pkg=" + pkg);
@@ -199,6 +202,22 @@
                 }
             });
         }
+
+        @Override
+        public void loadThumbnail(final Uri uri, final int width, final int height,
+                final IMediaBrowserServiceCallbacks callbacks) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    Bitmap bitmap = onGetThumbnail(uri, width, height);
+                    try {
+                        callbacks.onLoadThumbnail(uri, width, height, bitmap);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "RemoteException in calling onLoadThumbnail", e);
+                    }
+                }
+            });
+        }
     }
 
     @Override
@@ -220,7 +239,7 @@
     }
 
     /**
-     * Called to get the root uri for browsing by a particular client.
+     * Called to get the root information for browsing by a particular client.
      * <p>
      * The implementation should verify that the client package has
      * permission to access browse media information before returning
@@ -237,8 +256,8 @@
      * for browsing, or null if none.  The contents of this bundle may affect
      * the information returned when browsing.
      */
-    public abstract @Nullable Uri onGetRoot(@NonNull String clientPackageName, int clientUid,
-            @Nullable Bundle rootHints);
+    protected abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName,
+            int clientUid, @Nullable Bundle rootHints);
 
     /**
      * Called to get information about the children of a media item.
@@ -247,7 +266,7 @@
      * children are to be queried.
      * @return The list of children, or null if the uri is invalid.
      */
-    public abstract @Nullable List<MediaBrowserItem> onLoadChildren(@NonNull Uri parentUri);
+    protected abstract @Nullable List<MediaBrowserItem> onLoadChildren(@NonNull Uri parentUri);
 
     /**
      * Called to get the thumbnail of a particular media item.
@@ -255,14 +274,11 @@
      * @param uri The uri of the media item.
      * @param width The requested width of the icon in dp.
      * @param height The requested height of the icon in dp.
-     * @param density The requested density of the icon. This is the approximate density of the
-     *              screen on which the icon will be displayed.  This density will be one of
-     *              the android density buckets.
+     *
      * @return The file descriptor of the thumbnail, which may then be loaded
      *          using a bitmap factory, or null if the item does not have a thumbnail.
      */
-    public abstract @Nullable Bitmap onGetThumbnail(@NonNull Uri uri,
-            int width, int height, int density);
+    protected abstract @Nullable Bitmap onGetThumbnail(@NonNull Uri uri, int width, int height);
 
     /**
      * Call to set the media session.
@@ -288,15 +304,6 @@
     }
 
     /**
-     * Notifies all connected media browsers that the content of
-     * the browse service has changed in some way.
-     * This will cause browsers to fetch subscribed content again.
-     */
-    public void notifyChange() {
-        throw new RuntimeException("implement me");
-    }
-
-    /**
      * Notifies all connected media browsers that the children of
      * the specified Uri have changed in some way.
      * This will cause browsers to fetch subscribed content again.
@@ -305,7 +312,19 @@
      * children changed.
      */
     public void notifyChildrenChanged(@NonNull Uri parentUri) {
-        throw new RuntimeException("implement me");
+        if (parentUri == null) {
+            throw new IllegalArgumentException("parentUri cannot be null in notifyChildrenChanged");
+        }
+        for (IBinder binder : mConnections.keySet()) {
+            ConnectionRecord connection = mConnections.get(binder);
+            Set<Uri> uris = connection.subscriptions;
+            for (Uri uri : uris) {
+                if (uri.equals(parentUri)) {
+                    performLoadChildren(uri, connection);
+                    break;
+                }
+            }
+        }
     }
 
     /**
@@ -360,4 +379,25 @@
                     + " package=" + connection.pkg);
         }
     }
+
+    public static class BrowserRoot {
+        final private Uri mUri;
+        final private Bundle mExtras;
+        public BrowserRoot(@NonNull Uri uri, @Nullable Bundle extras) {
+            if (uri == null) {
+                throw new IllegalArgumentException("The root uri in BrowserRoot cannot be null. " +
+                        "Use null for BrowserRoot instead.");
+            }
+            mUri = uri;
+            mExtras = extras;
+        }
+
+        Uri getRootUri() {
+            return mUri;
+        }
+
+        Bundle getExtras() {
+            return mExtras;
+        }
+    }
 }
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 627f11c..e9fe09e 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -23,6 +23,7 @@
     <color name="system_bar_background_opaque">#ff000000</color>
     <color name="system_bar_background_semi_transparent">#66000000</color> <!-- 40% black -->
     <color name="system_bar_background_transparent">#00000000</color>
+    <color name="system_bar_background_warning">#fff4511e</color><!-- deep orange 600 -->
     <color name="notification_panel_solid_background">#ff000000</color>
     <drawable name="status_bar_recents_app_thumbnail_background">#88000000</drawable>
     <color name="status_bar_recents_app_label_color">#ffffffff</color>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index fbfad5c..fd636ed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -22,6 +22,7 @@
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
@@ -52,7 +53,6 @@
 
     boolean mIsFullscreen;
 
-    Paint mLayerPaint = new Paint();
     static Paint sHighlightPaint;
 
     public TaskBarView(Context context) {
@@ -167,7 +167,11 @@
             mActivityDescription.setText(t.activityLabel);
         }
         // Try and apply the system ui tint
-        setBackgroundColor(t.colorPrimary);
+        int existingBgColor = (getBackground() instanceof ColorDrawable) ?
+                ((ColorDrawable) getBackground()).getColor() : 0;
+        if (existingBgColor != t.colorPrimary) {
+            setBackgroundColor(t.colorPrimary);
+        }
         mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
                 mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
@@ -252,14 +256,4 @@
             mDismissButton.setAlpha(1f);
         }
     }
-
-    /** Enable the hw layers on this task view */
-    void enableHwLayers() {
-        mDismissButton.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
-    }
-
-    /** Disable the hw layers on this task view */
-    void disableHwLayers() {
-        mDismissButton.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index d84a40f..f135e32 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -100,13 +100,15 @@
     LayoutInflater mInflater;
 
     // A convenience runnable to return all views to the pool
-    // XXX: After this is set, we should mark this task stack view as disabled and check that in synchronize model
     Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
         @Override
         public void run() {
             int childCount = getChildCount();
             for (int i = childCount - 1; i >= 0; i--) {
-                mViewPool.returnViewToPool((TaskView) getChildAt(i));
+                TaskView tv = (TaskView) getChildAt(i);
+                mViewPool.returnViewToPool(tv);
+                // Also hide the view since we don't need it anymore
+                tv.setVisibility(View.INVISIBLE);
             }
         }
     };
@@ -133,33 +135,6 @@
                 }
             }
         });
-        mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() {
-            @Override
-            public void run() {
-                // Enable hw layers on each of the children
-                int childCount = getChildCount();
-                for (int i = 0; i < childCount; i++) {
-                    TaskView tv = (TaskView) getChildAt(i);
-                    tv.enableHwLayers();
-                }
-            }
-        }, new Runnable() {
-            @Override
-            public void run() {
-                // Disable hw layers on each of the children
-                int childCount = getChildCount();
-                for (int i = 0; i < childCount; i++) {
-                    TaskView tv = (TaskView) getChildAt(i);
-                    tv.disableHwLayers();
-                }
-            }
-        }, new Runnable() {
-            @Override
-            public void run() {
-                new Throwable("Invalid hw layers ref count").printStackTrace();
-                Console.logError(getContext(), "Invalid HW layers ref count");
-            }
-        });
     }
 
     /** Sets the callbacks */
@@ -321,8 +296,8 @@
                         if (transform.t < 0) {
                             mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(0), stackScroll, mTmpTransform);
                         } else {
-                            mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(Math.min(tasks.size() - 1, visibleRange[0] + 1)),
-                                    stackScroll, mTmpTransform);
+                            int nextTaskStackScroll = mStackAlgorithm.getStackScrollForTaskIndex(task, 1);
+                            mStackAlgorithm.getStackTransform(nextTaskStackScroll, stackScroll, mTmpTransform);
                         }
                         tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
                     }
@@ -435,17 +410,8 @@
         int curScroll = getStackScroll();
         int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
         if (newScroll != curScroll) {
-            // Enable hw layers on the stack
-            addHwLayersRefCount("animateBoundScroll");
-
             // Start a new scroll animation
-            animateScroll(curScroll, newScroll, new Runnable() {
-                @Override
-                public void run() {
-                    // Disable hw layers on the stack
-                    decHwLayersRefCount("animateBoundScroll");
-                }
-            });
+            animateScroll(curScroll, newScroll, null);
         }
         return mScrollAnimator;
     }
@@ -490,8 +456,6 @@
         if (!mScroller.isFinished()) {
             // Abort the scroller
             mScroller.abortAnimation();
-            // And disable hw layers on the stack
-            decHwLayersRefCount("flingScroll");
         }
     }
 
@@ -615,17 +579,6 @@
         focusTask(mFocusedTaskIndex, true);
     }
 
-    /** Enables the hw layers and increments the hw layer requirement ref count */
-    void addHwLayersRefCount(String reason) {
-        mHwLayersTrigger.increment();
-    }
-
-    /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
-        need them anymore. */
-    void decHwLayersRefCount(String reason) {
-        mHwLayersTrigger.decrement();
-    }
-
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         return mTouchHandler.onInterceptTouchEvent(ev);
@@ -641,11 +594,6 @@
         if (mScroller.computeScrollOffset()) {
             setStackScroll(mScroller.getCurrY());
             invalidate();
-
-            // If we just finished scrolling, then disable the hw layers
-            if (mScroller.isFinished()) {
-                decHwLayersRefCount("finishedFlingScroll");
-            }
         }
     }
 
@@ -969,9 +917,6 @@
         // Detach the view from the hierarchy
         detachViewFromParent(tv);
 
-        // Disable HW layers
-        tv.disableHwLayers();
-
         // Reset the view properties
         tv.resetViewProperties();
     }
@@ -1015,11 +960,6 @@
         } else {
             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
         }
-
-        // Enable hw layers on this view if hw layers are enabled on the stack
-        if (mHwLayersTrigger.getCount() > 0) {
-            tv.enableHwLayers();
-        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 3c89cd7..7f94a0a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -103,12 +103,16 @@
             transformOut.reset();
             return transformOut;
         }
+        return getStackTransform(getStackScrollForTaskIndex(task), stackScroll, transformOut);
+    }
 
+    /** Update/get the transform */
+    public TaskViewTransform getStackTransform(int taskStackScroll, int stackScroll, TaskViewTransform transformOut) {
         // Map the items to an continuous position relative to the specified scroll
         int numPeekCards = StackPeekNumCards;
         float overlapHeight = StackOverlapPct * mTaskRect.height();
         float peekHeight = StackPeekHeightPct * mStackRect.height();
-        float t = (getStackScrollForTaskIndex(task) - stackScroll) / overlapHeight;
+        float t = (taskStackScroll - stackScroll) / overlapHeight;
         float boundedT = Math.max(t, -(numPeekCards + 1));
 
         // Set the scale relative to its position
@@ -116,8 +120,8 @@
         float minScale = StackPeekMinScale;
         float scaleRange = 1f - minScale;
         float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards);
-        float scale = Math.max(minScale, Math.min(1f, minScale + 
-            ((boundedT + (numPeekCards + 1)) * scaleInc)));
+        float scale = Math.max(minScale, Math.min(1f, minScale +
+                ((boundedT + (numPeekCards + 1)) * scaleInc)));
         float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
         // Account for the bar offsets being scaled?
         float scaleBarYOffset = (1f - scale) * mConfig.taskBarHeight;
@@ -169,6 +173,14 @@
     }
 
     /**
+     * Returns the scroll to such that the task transform at that task + index will have t=0.
+     * (If the scroll is not bounded)
+     */
+    int getStackScrollForTaskIndex(Task t, int relativeIndexOffset) {
+        return mTaskOffsetMap.get(t.key) + (int) (relativeIndexOffset * getTaskOverlapHeight());
+    }
+
+    /**
      * Updates the cache of tasks to offsets.
      */
     void updateTaskOffsets(ArrayList<Task> tasks) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 191dc37..b83f9cc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -148,8 +148,6 @@
                     if (parent != null) {
                         parent.requestDisallowInterceptTouchEvent(true);
                     }
-                    // Enable HW layers
-                    mSv.addHwLayersRefCount("stackScroll");
                 }
 
                 mLastMotionX = x;
@@ -160,10 +158,6 @@
             case MotionEvent.ACTION_UP: {
                 // Animate the scroll back if we've cancelled
                 mSv.animateBoundScroll();
-                // Disable HW layers
-                if (mIsScrolling) {
-                    mSv.decHwLayersRefCount("stackScroll");
-                }
                 // Reset the drag state and the velocity tracker
                 mIsScrolling = false;
                 mActivePointerId = INACTIVE_POINTER_ID;
@@ -241,8 +235,6 @@
                         if (parent != null) {
                             parent.requestDisallowInterceptTouchEvent(true);
                         }
-                        // Enable HW layers
-                        mSv.addHwLayersRefCount("stackScroll");
                     }
                 }
                 if (mIsScrolling) {
@@ -271,8 +263,6 @@
                 int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
 
                 if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
-                    // Enable HW layers on the stack
-                    mSv.addHwLayersRefCount("flingScroll");
                     // XXX: Make this animation a function of the velocity AND distance
                     int overscrollRange = (int) (Math.min(1f,
                             Math.abs((float) velocity / mMaximumVelocity)) *
@@ -291,10 +281,6 @@
                     mSv.animateBoundScroll();
                 }
 
-                if (mIsScrolling) {
-                    // Disable HW layers
-                    mSv.decHwLayersRefCount("stackScroll");
-                }
                 mActivePointerId = INACTIVE_POINTER_ID;
                 mIsScrolling = false;
                 mTotalScrollMotion = 0;
@@ -315,10 +301,6 @@
                 break;
             }
             case MotionEvent.ACTION_CANCEL: {
-                if (mIsScrolling) {
-                    // Disable HW layers
-                    mSv.decHwLayersRefCount("stackScroll");
-                }
                 if (mSv.isScrollOutOfBounds()) {
                     // Animate the scroll back into bounds
                     // XXX: Make this animation a function of the velocity OR distance
@@ -351,8 +333,6 @@
         TaskView tv = (TaskView) v;
         // Disable clipping with the stack while we are swiping
         tv.setClipViewInStack(false);
-        // Enable HW layers on that task
-        tv.enableHwLayers();
         // Disallow touch events from this task view
         tv.setTouchEnabled(false);
         // Hide the footer
@@ -372,10 +352,6 @@
     @Override
     public void onChildDismissed(View v) {
         TaskView tv = (TaskView) v;
-        // Disable HW layers on that task
-        if (mSv.mHwLayersTrigger.getCount() == 0) {
-            tv.disableHwLayers();
-        }
         // Re-enable clipping with the stack (we will reuse this view)
         tv.setClipViewInStack(true);
         // Re-enable touch events from this task view
@@ -387,10 +363,6 @@
     @Override
     public void onSnapBackCompleted(View v) {
         TaskView tv = (TaskView) v;
-        // Disable HW layers on that task
-        if (mSv.mHwLayersTrigger.getCount() == 0) {
-            tv.disableHwLayers();
-        }
         // Re-enable clipping with the stack
         tv.setClipViewInStack(true);
         // Re-enable touch events from this task view
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index d1b33f3..259706b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -22,7 +22,10 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
@@ -34,8 +37,6 @@
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.Task;
 
-// XXX: In debug mode, we should override invalidate() and check the layout type (do this in TaskStackView as well)
-
 /* A task view */
 public class TaskView extends FrameLayout implements Task.TaskCallbacks,
         TaskFooterView.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
@@ -53,6 +54,7 @@
     int mDim;
     int mMaxDim;
     AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
+    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.MULTIPLY);
 
     Task mTask;
     boolean mTaskDataLoaded;
@@ -96,9 +98,8 @@
         mMaxDim = mConfig.taskStackMaxDim;
         mClipViewInStack = true;
         mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx);
-        setWillNotDraw(false);
-        setDim(getDim());
         setOutlineProvider(mViewBounds);
+        setDim(getDim());
     }
 
     /** Set callback */
@@ -164,7 +165,7 @@
         }
 
         // Apply the transform
-        toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator,
+        toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false,
                 mUpdateDimListener);
     }
 
@@ -262,7 +263,6 @@
                                 AlternateRecentsComponent.consumeLastScreenshot();
                             }
                         })
-                        .withLayer()
                         .start();
             } else {
                 // Otherwise, just enable the thumbnail clip
@@ -316,7 +316,6 @@
                     .setUpdateListener(null)
                     .setInterpolator(mConfig.quintOutInterpolator)
                     .setDuration(mConfig.taskViewEnterFromHomeDuration)
-                    .withLayer()
                     .withEndAction(new Runnable() {
                         @Override
                         public void run() {
@@ -348,7 +347,6 @@
                 .setUpdateListener(null)
                 .setInterpolator(mConfig.fastOutLinearInInterpolator)
                 .setDuration(mConfig.taskViewExitToHomeDuration)
-                .withLayer()
                 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
                 .start();
         ctx.postAnimationTrigger.increment();
@@ -384,7 +382,6 @@
             .setUpdateListener(null)
             .setInterpolator(mConfig.fastOutSlowInInterpolator)
             .setDuration(mConfig.taskViewRemoveAnimDuration)
-            .withLayer()
             .withEndAction(new Runnable() {
                 @Override
                 public void run() {
@@ -466,7 +463,10 @@
     /** Returns the current dim. */
     public void setDim(int dim) {
         mDim = dim;
-        postInvalidateOnAnimation();
+        int inverse = 255 - mDim;
+        mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse));
+        mLayerPaint.setColorFilter(mDimColorFilter);
+        setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint);
     }
 
     /** Returns the current dim. */
@@ -491,16 +491,6 @@
     /**** View drawing ****/
 
     @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-
-        // Apply the dim if necessary
-        if (mDim > 0) {
-            canvas.drawColor(mDim << 24);
-        }
-    }
-
-    @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         if (mIsStub && (child != mBarView)) {
             // Skip the thumbnail view if we are in stub mode
@@ -509,20 +499,6 @@
         return super.drawChild(canvas, child, drawingTime);
     }
 
-    /** Enable the hw layers on this task view */
-    void enableHwLayers() {
-        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
-        mBarView.enableHwLayers();
-        mFooterView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
-    }
-
-    /** Disable the hw layers on this task view */
-    void disableHwLayers() {
-        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
-        mBarView.disableHwLayers();
-        mFooterView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
-    }
-
     /**** View focus state ****/
 
     /**
@@ -640,7 +616,7 @@
     /**** View.OnClickListener Implementation ****/
 
     @Override
-    public void onClick(final View v) {
+     public void onClick(final View v) {
         // We purposely post the handler delayed to allow for the touch feedback to draw
         final TaskView tv = this;
         postDelayed(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index d583c20..aeb4fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -83,12 +83,12 @@
     }
 
     /** Applies this transform to a view. */
-    public void applyToTaskView(View v, int duration, Interpolator interp,
+    public void applyToTaskView(View v, int duration, Interpolator interp, boolean allowLayers,
                                 ValueAnimator.AnimatorUpdateListener scaleUpdateListener) {
         // Check to see if any properties have changed, and update the task view
         if (duration > 0) {
             ViewPropertyAnimator anim = v.animate();
-            boolean useLayers = false;
+            boolean requiresLayers = false;
 
             // Animate to the final state
             if (hasTranslationYChangedFrom(v.getTranslationY())) {
@@ -102,14 +102,14 @@
                 anim.scaleX(scale)
                     .scaleY(scale)
                     .setUpdateListener(scaleUpdateListener);
-                useLayers = true;
+                requiresLayers = true;
             }
             if (hasAlphaChangedFrom(v.getAlpha())) {
                 // Use layers if we animate alpha
                 anim.alpha(alpha);
-                useLayers = true;
+                requiresLayers = true;
             }
-            if (useLayers) {
+            if (requiresLayers && allowLayers) {
                 anim.withLayer();
             }
             anim.setStartDelay(startDelay)
@@ -146,7 +146,6 @@
         v.setScaleX(1f);
         v.setScaleY(1f);
         v.setAlpha(1f);
-        v.invalidate();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index a41ec22..303454b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -44,6 +44,7 @@
     public static final int MODE_TRANSLUCENT = 2;
     public static final int MODE_LIGHTS_OUT = 3;
     public static final int MODE_TRANSPARENT = 4;
+    public static final int MODE_WARNING = 5;
 
     public static final int LIGHTS_IN_DURATION = 250;
     public static final int LIGHTS_OUT_DURATION = 750;
@@ -100,6 +101,7 @@
         if (mode == MODE_TRANSLUCENT) return "MODE_TRANSLUCENT";
         if (mode == MODE_LIGHTS_OUT) return "MODE_LIGHTS_OUT";
         if (mode == MODE_TRANSPARENT) return "MODE_TRANSPARENT";
+        if (mode == MODE_WARNING) return "MODE_WARNING";
         throw new IllegalArgumentException("Unknown mode " + mode);
     }
 
@@ -115,6 +117,7 @@
         private final int mOpaque;
         private final int mSemiTransparent;
         private final int mTransparent;
+        private final int mWarning;
         private final Drawable mGradient;
         private final TimeInterpolator mInterpolator;
 
@@ -135,10 +138,12 @@
                 mOpaque = 0xff0000ff;
                 mSemiTransparent = 0x7f0000ff;
                 mTransparent = 0x2f0000ff;
+                mWarning = 0xffff0000;
             } else {
                 mOpaque = res.getColor(R.color.system_bar_background_opaque);
                 mSemiTransparent = res.getColor(R.color.system_bar_background_semi_transparent);
                 mTransparent = res.getColor(R.color.system_bar_background_transparent);
+                mWarning = res.getColor(R.color.system_bar_background_warning);
             }
             mGradient = res.getDrawable(gradientResourceId);
             mInterpolator = new LinearInterpolator();
@@ -189,7 +194,9 @@
         @Override
         public void draw(Canvas canvas) {
             int targetGradientAlpha = 0, targetColor = 0;
-            if (mMode == MODE_TRANSLUCENT) {
+            if (mMode == MODE_WARNING) {
+                targetColor = mWarning;
+            } else if (mMode == MODE_TRANSLUCENT) {
                 targetColor = mSemiTransparent;
             } else if (mMode == MODE_SEMI_TRANSPARENT) {
                 targetColor = mSemiTransparent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 59becd3..e4e67c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -28,6 +28,8 @@
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeInterpolator;
@@ -125,6 +127,7 @@
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
 import com.android.systemui.statusbar.policy.CastControllerImpl;
 import com.android.systemui.statusbar.policy.FlashlightController;
@@ -705,6 +708,16 @@
         // Other icons
         mLocationController = new LocationControllerImpl(mContext); // will post a notification
         mBatteryController = new BatteryController(mContext);
+        mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
+            @Override
+            public void onPowerSaveChanged() {
+                mHandler.post(mCheckBarModes);
+            }
+            @Override
+            public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+                // noop
+            }
+        });
         mNetworkController = new NetworkControllerImpl(mContext);
         mHotspotController = new HotspotControllerImpl(mContext);
         mBluetoothController = new BluetoothControllerImpl(mContext);
@@ -2310,7 +2323,12 @@
     }
 
     private void checkBarMode(int mode, int windowState, BarTransitions transitions) {
-        final boolean anim = (mScreenOn == null || mScreenOn) && windowState != WINDOW_STATE_HIDDEN;
+        final boolean powerSave = mBatteryController.isPowerSave();
+        final boolean anim = (mScreenOn == null || mScreenOn) && windowState != WINDOW_STATE_HIDDEN
+                && !powerSave;
+        if (powerSave && getBarState() != StatusBarState.KEYGUARD) {
+            mode = MODE_WARNING;
+        }
         transitions.transitionTo(mode, anim);
     }
 
@@ -2325,7 +2343,8 @@
         @Override
         public void run() {
             checkBarModes();
-        }};
+        }
+    };
 
     @Override
     public void setInteracting(int barWindow, boolean interacting) {
@@ -2644,6 +2663,9 @@
         if (mUserSwitcherController != null) {
             mUserSwitcherController.dump(fd, pw, args);
         }
+        if (mBatteryController != null) {
+            mBatteryController.dump(fd, pw, args);
+        }
     }
 
     private String hunStateToString(Entry entry) {
@@ -3111,6 +3133,8 @@
             int barMode = "opaque".equals(mode) ? MODE_OPAQUE :
                     "translucent".equals(mode) ? MODE_TRANSLUCENT :
                     "semi-transparent".equals(mode) ? MODE_SEMI_TRANSPARENT :
+                    "transparent".equals(mode) ? MODE_TRANSPARENT :
+                    "warning".equals(mode) ? MODE_WARNING :
                     -1;
             if (barMode != -1) {
                 boolean animate = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index b2709bc..ffb4dc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -363,6 +363,11 @@
         }
     }
 
+    @Override
+    public void onPowerSaveChanged() {
+        // could not care less
+    }
+
     private void updateClickTargets() {
         setClickable(!mKeyguardShowing || mExpanded);
         mDateTime.setClickable(mExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index ccfe18d..1e65543 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -16,41 +16,49 @@
 
 package com.android.systemui.statusbar.policy;
 
-import com.android.internal.app.IBatteryStats;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.StatusBarHeaderView;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.BatteryManager;
-import android.os.BatteryStats;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.text.format.Formatter;
+import android.os.PowerManager;
 import android.util.Log;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 public class BatteryController extends BroadcastReceiver {
-    private static final String TAG = "StatusBar.BatteryController";
+    private static final String TAG = "BatteryController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+    private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+    private final PowerManager mPowerManager;
 
     private int mLevel;
     private boolean mPluggedIn;
     private boolean mCharging;
     private boolean mCharged;
-
-    public interface BatteryStateChangeCallback {
-        public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging);
-    }
+    private boolean mPowerSave;
 
     public BatteryController(Context context) {
+        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
         context.registerReceiver(this, filter);
+
+        updatePowerSave();
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("BatteryController state:");
+        pw.print("  mLevel="); pw.println(mLevel);
+        pw.print("  mPluggedIn="); pw.println(mPluggedIn);
+        pw.print("  mCharging="); pw.println(mCharging);
+        pw.print("  mCharged="); pw.println(mCharged);
+        pw.print("  mPowerSave="); pw.println(mPowerSave);
     }
 
     public void addStateChangedCallback(BatteryStateChangeCallback cb) {
@@ -75,9 +83,40 @@
             mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
             mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
 
-            for (BatteryStateChangeCallback cb : mChangeCallbacks) {
-                cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
-            }
+            fireBatteryLevelChanged();
+        } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
+            updatePowerSave();
         }
     }
+
+    public boolean isPowerSave() {
+        return mPowerSave;
+    }
+
+    private void updatePowerSave() {
+        final boolean powerSave = mPowerManager.isPowerSaveMode();
+        if (powerSave == mPowerSave) return;
+        mPowerSave = powerSave;
+        if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
+        firePowerSaveChanged();
+    }
+
+    private void fireBatteryLevelChanged() {
+        final int N = mChangeCallbacks.size();
+        for (int i = 0; i < N; i++) {
+            mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
+        }
+    }
+
+    private void firePowerSaveChanged() {
+        final int N = mChangeCallbacks.size();
+        for (int i = 0; i < N; i++) {
+            mChangeCallbacks.get(i).onPowerSaveChanged();
+        }
+    }
+
+    public interface BatteryStateChangeCallback {
+        void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging);
+        void onPowerSaveChanged();
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index fc63490..56b8c92 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -6409,54 +6409,61 @@
             mIsSystemRestore = isFullSystemRestore;
             mFinished = false;
 
-            if (filterSet == null) {
-                // We want everything and a pony
-                List<PackageInfo> apps
-                        = PackageManagerBackupAgent.getStorableApplications(mPackageManager);
-                filterSet = packagesToNames(apps);
-                if (DEBUG) {
-                    Slog.i(TAG, "Full restore; asking for " + filterSet.length + " apps");
-                }
-            }
-
-            mAcceptSet = new ArrayList<PackageInfo>(filterSet.length);
-
-            // Pro tem, we insist on moving the settings provider package to last place.
-            // Keep track of whether it's in the list, and bump it down if so.  We also
-            // want to do the system package itself first if it's called for.
-            boolean hasSystem = false;
-            boolean hasSettings = false;
-            for (int i = 0; i < filterSet.length; i++) {
-                try {
-                    PackageInfo info = mPackageManager.getPackageInfo(filterSet[i], 0);
-                    if ("android".equals(info.packageName)) {
-                        hasSystem = true;
-                        continue;
+            if (targetPackage != null) {
+                // Single package restore
+                mAcceptSet = new ArrayList<PackageInfo>();
+                mAcceptSet.add(targetPackage);
+            } else {
+                // Everything possible, or a target set
+                if (filterSet == null) {
+                    // We want everything and a pony
+                    List<PackageInfo> apps =
+                            PackageManagerBackupAgent.getStorableApplications(mPackageManager);
+                    filterSet = packagesToNames(apps);
+                    if (DEBUG) {
+                        Slog.i(TAG, "Full restore; asking for " + filterSet.length + " apps");
                     }
-                    if (SETTINGS_PACKAGE.equals(info.packageName)) {
-                        hasSettings = true;
-                        continue;
-                    }
+                }
 
-                    if (appIsEligibleForBackup(info.applicationInfo)) {
-                        mAcceptSet.add(info);
+                mAcceptSet = new ArrayList<PackageInfo>(filterSet.length);
+
+                // Pro tem, we insist on moving the settings provider package to last place.
+                // Keep track of whether it's in the list, and bump it down if so.  We also
+                // want to do the system package itself first if it's called for.
+                boolean hasSystem = false;
+                boolean hasSettings = false;
+                for (int i = 0; i < filterSet.length; i++) {
+                    try {
+                        PackageInfo info = mPackageManager.getPackageInfo(filterSet[i], 0);
+                        if ("android".equals(info.packageName)) {
+                            hasSystem = true;
+                            continue;
+                        }
+                        if (SETTINGS_PACKAGE.equals(info.packageName)) {
+                            hasSettings = true;
+                            continue;
+                        }
+
+                        if (appIsEligibleForBackup(info.applicationInfo)) {
+                            mAcceptSet.add(info);
+                        }
+                    } catch (NameNotFoundException e) {
+                        // requested package name doesn't exist; ignore it
                     }
-                } catch (NameNotFoundException e) {
-                    // requested package name doesn't exist; ignore it
                 }
-            }
-            if (hasSystem) {
-                try {
-                    mAcceptSet.add(0, mPackageManager.getPackageInfo("android", 0));
-                } catch (NameNotFoundException e) {
-                    // won't happen; we know a priori that it's valid
+                if (hasSystem) {
+                    try {
+                        mAcceptSet.add(0, mPackageManager.getPackageInfo("android", 0));
+                    } catch (NameNotFoundException e) {
+                        // won't happen; we know a priori that it's valid
+                    }
                 }
-            }
-            if (hasSettings) {
-                try {
-                    mAcceptSet.add(mPackageManager.getPackageInfo(SETTINGS_PACKAGE, 0));
-                } catch (NameNotFoundException e) {
-                    // this one is always valid too
+                if (hasSettings) {
+                    try {
+                        mAcceptSet.add(mPackageManager.getPackageInfo(SETTINGS_PACKAGE, 0));
+                    } catch (NameNotFoundException e) {
+                        // this one is always valid too
+                    }
                 }
             }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ca9f6eb..abee0c6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -41,7 +41,6 @@
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 
 import android.app.AlarmManager;
-import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -52,9 +51,7 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -224,10 +221,6 @@
 
     AlarmManager mAlarmManager;
 
-    // used in recursive route setting to add gateways for the host for which
-    // a host route was requested.
-    private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10;
-
     private Tethering mTethering;
 
     private KeyStore mKeyStore;
@@ -261,11 +254,6 @@
     private CaptivePortalTracker mCaptivePortalTracker;
 
     /**
-     * The link properties that define the current links
-     */
-    private LinkProperties mCurrentLinkProperties[];
-
-    /**
      * A per Net list of the PID's that requested access to the net
      * used both as a refcount and for per-PID DNS selection
      */
@@ -297,15 +285,6 @@
     private static final int ENABLED  = 1;
     private static final int DISABLED = 0;
 
-    private static final boolean ADD = true;
-    private static final boolean REMOVE = false;
-
-    private static final boolean TO_DEFAULT_TABLE = true;
-    private static final boolean TO_SECONDARY_TABLE = false;
-
-    private static final boolean EXEMPT = true;
-    private static final boolean UNEXEMPT = false;
-
     /**
      * used internally as a delayed event to make us switch back to the
      * default network
@@ -450,19 +429,6 @@
 
     private InetAddress mDefaultDns;
 
-    // Lock for protecting access to mAddedRoutes and mExemptAddresses
-    private final Object mRoutesLock = new Object();
-
-    // this collection is used to refcount the added routes - if there are none left
-    // it's time to remove the route from the route table
-    @GuardedBy("mRoutesLock")
-    private Collection<RouteInfo> mAddedRoutes = new ArrayList<RouteInfo>();
-
-    // this collection corresponds to the entries of mAddedRoutes that have routing exemptions
-    // used to handle cleanup of exempt rules
-    @GuardedBy("mRoutesLock")
-    private Collection<LinkAddress> mExemptAddresses = new ArrayList<LinkAddress>();
-
     // used in DBG mode to track inet condition reports
     private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
     private ArrayList mInetLog;
@@ -479,8 +445,6 @@
 
     private SettingsObserver mSettingsObserver;
 
-    private AppOpsManager mAppOpsManager;
-
     private UserManager mUserManager;
 
     NetworkConfig[] mNetConfigs;
@@ -514,8 +478,6 @@
     // sequence number of NetworkRequests
     private int mNextNetworkRequestId = 1;
 
-    private static final int UID_UNUSED = -1;
-
     /**
      * Implements support for the legacy "one network per network type" model.
      *
@@ -701,7 +663,6 @@
 
         mNetTrackers = new NetworkStateTracker[
                 ConnectivityManager.MAX_NETWORK_TYPE+1];
-        mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1];
 
         mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1];
         mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
@@ -884,8 +845,6 @@
         filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
         mContext.registerReceiver(mProvisioningReceiver, filter);
 
-        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
     }
 
@@ -1710,42 +1669,6 @@
     }
 
     /**
-     * Check if the address falls into any of currently running VPN's route's.
-     */
-    private boolean isAddressUnderVpn(InetAddress address) {
-        synchronized (mVpns) {
-            synchronized (mRoutesLock) {
-                int uid = UserHandle.getCallingUserId();
-                Vpn vpn = mVpns.get(uid);
-                if (vpn == null) {
-                    return false;
-                }
-
-                // Check if an exemption exists for this address.
-                for (LinkAddress destination : mExemptAddresses) {
-                    if (!NetworkUtils.addressTypeMatches(address, destination.getAddress())) {
-                        continue;
-                    }
-
-                    int prefix = destination.getPrefixLength();
-                    InetAddress addrMasked = NetworkUtils.getNetworkPart(address, prefix);
-                    InetAddress destMasked = NetworkUtils.getNetworkPart(destination.getAddress(),
-                            prefix);
-
-                    if (addrMasked.equals(destMasked)) {
-                        return false;
-                    }
-                }
-
-                // Finally check if the address is covered by the VPN.
-                return vpn.isAddressCovered(address);
-            }
-        }
-    }
-
-    /**
-     * @deprecated use requestRouteToHostAddress instead
-     *
      * Ensure that a network route exists to deliver traffic to the specified
      * host via the specified network interface.
      * @param networkType the type of the network over which traffic to the
@@ -1754,32 +1677,12 @@
      * desired
      * @return {@code true} on success, {@code false} on failure
      */
-    public boolean requestRouteToHost(int networkType, int hostAddress, String packageName) {
-        InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
-
-        if (inetAddress == null) {
-            return false;
-        }
-
-        return requestRouteToHostAddress(networkType, inetAddress.getAddress(), packageName);
-    }
-
-    /**
-     * Ensure that a network route exists to deliver traffic to the specified
-     * host via the specified network interface.
-     * @param networkType the type of the network over which traffic to the
-     * specified host is to be routed
-     * @param hostAddress the IP address of the host to which the route is
-     * desired
-     * @return {@code true} on success, {@code false} on failure
-     */
-    public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress,
-            String packageName) {
+    public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
         enforceChangePermission();
         if (mProtectedNetworks.contains(networkType)) {
             enforceConnectivityInternalPermission();
         }
-        boolean exempt;
+
         InetAddress addr;
         try {
             addr = InetAddress.getByAddress(hostAddress);
@@ -1787,26 +1690,6 @@
             if (DBG) log("requestRouteToHostAddress got " + e.toString());
             return false;
         }
-        // System apps may request routes bypassing the VPN to keep other networks working.
-        if (Binder.getCallingUid() == Process.SYSTEM_UID) {
-            exempt = true;
-        } else {
-            mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
-            try {
-                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(packageName,
-                        0);
-                exempt = (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-            } catch (NameNotFoundException e) {
-                throw new IllegalArgumentException("Failed to find calling package details", e);
-            }
-        }
-
-        // Non-exempt routeToHost's can only be added if the host is not covered by the VPN.
-        // This can be either because the VPN's routes do not cover the destination or a
-        // system application added an exemption that covers this destination.
-        if (!exempt && isAddressUnderVpn(addr)) {
-            return false;
-        }
 
         if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
             if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType);
@@ -1822,13 +1705,13 @@
             }
             return false;
         }
+
         DetailedState netState;
         synchronized (nai) {
             netState = nai.networkInfo.getDetailedState();
         }
 
-        if ((netState != DetailedState.CONNECTED &&
-                netState != DetailedState.CAPTIVE_PORTAL_CHECK)) {
+        if (netState != DetailedState.CONNECTED && netState != DetailedState.CAPTIVE_PORTAL_CHECK) {
             if (VDBG) {
                 log("requestRouteToHostAddress on down network "
                         + "(" + networkType + ") - dropped"
@@ -1836,6 +1719,7 @@
             }
             return false;
         }
+
         final int uid = Binder.getCallingUid();
         final long token = Binder.clearCallingIdentity();
         try {
@@ -1845,7 +1729,7 @@
                 lp = nai.linkProperties;
                 netId = nai.network.netId;
             }
-            boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt, netId, uid);
+            boolean ok = addLegacyRouteToHost(lp, addr, netId, uid);
             if (DBG) log("requestRouteToHostAddress ok=" + ok);
             return ok;
         } finally {
@@ -1853,17 +1737,7 @@
         }
     }
 
-    private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
-            boolean exempt, int netId) {
-        return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt, netId, false, UID_UNUSED);
-    }
-
-    private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable, int netId) {
-        return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT, netId, false, UID_UNUSED);
-    }
-
-    private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
-            boolean toDefaultTable, boolean exempt, int netId, int uid) {
+    private boolean addLegacyRouteToHost(LinkProperties lp, InetAddress addr, int netId, int uid) {
         RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
         if (bestRoute == null) {
             bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
@@ -1878,125 +1752,14 @@
                 bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
             }
         }
-        return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt, netId, true, uid);
-    }
-
-    /*
-     * TODO: Clean all this stuff up. Once we have UID-based routing, stuff will break due to
-     *       incorrect tracking of mAddedRoutes, so a cleanup becomes necessary and urgent. But at
-     *       the same time, there'll be no more need to track mAddedRoutes or mExemptAddresses,
-     *       or even have the concept of an exempt address, or do things like "selectBestRoute", or
-     *       determine "default" vs "secondary" table, etc., so the cleanup becomes possible.
-     */
-    private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
-            boolean toDefaultTable, boolean exempt, int netId, boolean legacy, int uid) {
-        if ((lp == null) || (r == null)) {
-            if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
+        if (VDBG) log("Adding " + bestRoute + " for interface " + bestRoute.getInterface());
+        try {
+            mNetd.addLegacyRouteForNetId(netId, bestRoute, uid);
+        } catch (Exception e) {
+            // never crash - catch them all
+            if (DBG) loge("Exception trying to add a route: " + e);
             return false;
         }
-
-        if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
-            loge("Error modifying route - too much recursion");
-            return false;
-        }
-
-        String ifaceName = r.getInterface();
-        if(ifaceName == null) {
-            loge("Error modifying route - no interface name");
-            return false;
-        }
-        if (r.hasGateway()) {
-            RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), r.getGateway());
-            if (bestRoute != null) {
-                if (bestRoute.getGateway().equals(r.getGateway())) {
-                    // if there is no better route, add the implied hostroute for our gateway
-                    bestRoute = RouteInfo.makeHostRoute(r.getGateway(), ifaceName);
-                } else {
-                    // if we will connect to our gateway through another route, add a direct
-                    // route to it's gateway
-                    bestRoute = RouteInfo.makeHostRoute(r.getGateway(),
-                                                        bestRoute.getGateway(),
-                                                        ifaceName);
-                }
-                modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt, netId,
-                        legacy, uid);
-            }
-        }
-        if (doAdd) {
-            if (VDBG) log("Adding " + r + " for interface " + ifaceName);
-            try {
-                if (toDefaultTable) {
-                    synchronized (mRoutesLock) {
-                        // only track default table - only one apps can effect
-                        mAddedRoutes.add(r);
-                        if (legacy) {
-                            mNetd.addLegacyRouteForNetId(netId, r, uid);
-                        } else {
-                            mNetd.addRoute(netId, r);
-                        }
-                        if (exempt) {
-                            LinkAddress dest = r.getDestinationLinkAddress();
-                            if (!mExemptAddresses.contains(dest)) {
-                                mNetd.setHostExemption(dest);
-                                mExemptAddresses.add(dest);
-                            }
-                        }
-                    }
-                } else {
-                    if (legacy) {
-                        mNetd.addLegacyRouteForNetId(netId, r, uid);
-                    } else {
-                        mNetd.addRoute(netId, r);
-                    }
-                }
-            } catch (Exception e) {
-                // never crash - catch them all
-                if (DBG) loge("Exception trying to add a route: " + e);
-                return false;
-            }
-        } else {
-            // if we remove this one and there are no more like it, then refcount==0 and
-            // we can remove it from the table
-            if (toDefaultTable) {
-                synchronized (mRoutesLock) {
-                    mAddedRoutes.remove(r);
-                    if (mAddedRoutes.contains(r) == false) {
-                        if (VDBG) log("Removing " + r + " for interface " + ifaceName);
-                        try {
-                            if (legacy) {
-                                mNetd.removeLegacyRouteForNetId(netId, r, uid);
-                            } else {
-                                mNetd.removeRoute(netId, r);
-                            }
-                            LinkAddress dest = r.getDestinationLinkAddress();
-                            if (mExemptAddresses.contains(dest)) {
-                                mNetd.clearHostExemption(dest);
-                                mExemptAddresses.remove(dest);
-                            }
-                        } catch (Exception e) {
-                            // never crash - catch them all
-                            if (VDBG) loge("Exception trying to remove a route: " + e);
-                            return false;
-                        }
-                    } else {
-                        if (VDBG) log("not removing " + r + " as it's still in use");
-                    }
-                }
-            } else {
-                if (VDBG) log("Removing " + r + " for interface " + ifaceName);
-                try {
-                    if (legacy) {
-                        mNetd.removeLegacyRouteForNetId(netId, r, uid);
-                    } else {
-                        mNetd.removeRoute(netId, r);
-                    }
-                } catch (Exception e) {
-                    // never crash - catch them all
-                    if (VDBG) loge("Exception trying to remove a route: " + e);
-                    return false;
-                }
-            }
-        }
         return true;
     }
 
@@ -2607,6 +2370,7 @@
      *
      * TODO - delete when we're sure all this functionallity is captured.
      */
+    /*
     private void handleConnectivityChange(int netType, LinkProperties curLp, boolean doReset) {
         int resetMask = doReset ? NetworkUtils.RESET_ALL_ADDRESSES : 0;
         boolean exempt = ConnectivityManager.isNetworkTypeExempt(netType);
@@ -2615,10 +2379,8 @@
                     + " resetMask=" + resetMask);
         }
 
-        /*
-         * If a non-default network is enabled, add the host routes that
-         * will allow it's DNS servers to be accessed.
-         */
+        // If a non-default network is enabled, add the host routes that
+        // will allow it's DNS servers to be accessed.
         handleDnsConfigurationChange(netType);
 
         LinkProperties newLp = null;
@@ -2719,7 +2481,7 @@
 
         // TODO: Temporary notifying upstread change to Tethering.
         //       @see bug/4455071
-        /** Notify TetheringService if interface name has been changed. */
+        //  Notify TetheringService if interface name has been changed.
         if (TextUtils.equals(mNetTrackers[netType].getNetworkInfo().getReason(),
                              PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
             if (isTetheringSupported()) {
@@ -2727,6 +2489,7 @@
             }
         }
     }
+    */
 
     /**
      * Add and remove routes using the old properties (null if not previously connected),
@@ -2736,6 +2499,7 @@
      * host routes should be set to the dns servers
      * returns a boolean indicating the routes changed
      */
+    /*
     private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp,
             boolean isLinkDefault, boolean exempt, int netId) {
         Collection<RouteInfo> routesToAdd = null;
@@ -2789,6 +2553,7 @@
 
         return routesChanged;
     }
+    */
 
     /**
      * Reads the network specific MTU size from reources.
@@ -3264,10 +3029,12 @@
                         // any activity by applications trying to use this
                         // connection will fail until the provisioning network
                         // is enabled.
+                        /*
                         for (RouteInfo r : lp.getRoutes()) {
                             removeRoute(lp, r, TO_DEFAULT_TABLE,
                                         mNetTrackers[info.getType()].getNetwork().netId);
                         }
+                        */
                     } else if (state == NetworkInfo.State.DISCONNECTED) {
                     } else if (state == NetworkInfo.State.SUSPENDED) {
                     } else if (state == NetworkInfo.State.CONNECTED) {
@@ -3283,8 +3050,10 @@
                     // TODO: Temporary allowing network configuration
                     //       change not resetting sockets.
                     //       @see bug/4455071
+                    /*
                     handleConnectivityChange(info.getType(), mCurrentLinkProperties[info.getType()],
                             false);
+                    */
                     break;
                 }
                 case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: {
@@ -4683,7 +4452,7 @@
 
                             // Make a route to host so we check the specific interface.
                             if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
-                                    hostAddr.getAddress(), null)) {
+                                    hostAddr.getAddress())) {
                                 // Wait a short time to be sure the route is established ??
                                 log("isMobileOk:"
                                         + " wait to establish route to hostAddr=" + hostAddr);
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index f65a154..db423b0 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -117,7 +117,7 @@
     public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
 
     class NetdResponseCode {
-        /* Keep in sync with system/netd/ResponseCode.h */
+        /* Keep in sync with system/netd/server/ResponseCode.h */
         public static final int InterfaceListResult       = 110;
         public static final int TetherInterfaceListResult = 111;
         public static final int TetherDnsFwdTgtListResult = 112;
@@ -1745,36 +1745,6 @@
     }
 
     @Override
-    public void setHostExemption(LinkAddress host) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute("interface", "fwmark", "exempt", "add", host);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
-    public void clearHostExemption(LinkAddress host) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute("interface", "fwmark", "exempt", "remove", host);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
-    public void flushNetworkDnsCache(int netId) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute("resolver", "flushnet", netId);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
     public void setFirewallEnabled(boolean enabled) {
         enforceSystemUid();
         try {
@@ -2019,18 +1989,9 @@
 
     @Override
     public void addLegacyRouteForNetId(int netId, RouteInfo routeInfo, int uid) {
-        modifyLegacyRouteForNetId("add", netId, routeInfo, uid);
-    }
-
-    @Override
-    public void removeLegacyRouteForNetId(int netId, RouteInfo routeInfo, int uid) {
-        modifyLegacyRouteForNetId("remove", netId, routeInfo, uid);
-    }
-
-    private void modifyLegacyRouteForNetId(String action, int netId, RouteInfo routeInfo, int uid) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
-        final Command cmd = new Command("network", "route", "legacy", uid, action, netId);
+        final Command cmd = new Command("network", "route", "legacy", uid, "add", netId);
 
         // create triplet: interface dest-ip-addr/prefixlength gateway-ip-addr
         final LinkAddress la = routeInfo.getDestinationLinkAddress();
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index fe97c71..cf7e65c 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -603,7 +603,7 @@
         return mUniqueId;
     }
 
-    /* These should be in sync with system/netd/mDnsResponseCode.h */
+    /* These should be in sync with system/netd/server/ResponseCode.h */
     class NativeResponseCode {
         public static final int SERVICE_DISCOVERY_FAILED    =   602;
         public static final int SERVICE_FOUND               =   603;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 36d67ee..9a86136 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -77,6 +77,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.FgThread;
+
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
 
@@ -109,7 +110,7 @@
 
     private static final int TIMEOUT_DELAY_MS = 1000 * 60;
     private static final String DATABASE_NAME = "accounts.db";
-    private static final int DATABASE_VERSION = 5;
+    private static final int DATABASE_VERSION = 6;
 
     private final Context mContext;
 
@@ -130,6 +131,7 @@
     private static final String ACCOUNTS_TYPE = "type";
     private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
     private static final String ACCOUNTS_PASSWORD = "password";
+    private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name";
 
     private static final String TABLE_AUTHTOKENS = "authtokens";
     private static final String AUTHTOKENS_ID = "_id";
@@ -196,6 +198,20 @@
         /** protected by the {@link #cacheLock} */
         private final HashMap<Account, HashMap<String, String>> authTokenCache =
                 new HashMap<Account, HashMap<String, String>>();
+        /**
+         * protected by the {@link #cacheLock}
+         *
+         * Caches the previous names associated with an account. Previous names
+         * should be cached because we expect that when an Account is renamed,
+         * many clients will receive a LOGIN_ACCOUNTS_CHANGED broadcast and
+         * want to know if the accounts they care about have been renamed.
+         *
+         * The previous names are wrapped in an {@link AtomicReference} so that
+         * we can distinguish between those accounts with no previous names and
+         * those whose previous names haven't been cached (yet).
+         */
+        private final HashMap<Account, AtomicReference<String>> previousNameCache =
+                new HashMap<Account, AtomicReference<String>>();
 
         UserAccounts(Context context, int userId) {
             this.userId = userId;
@@ -517,6 +533,57 @@
     }
 
     @Override
+    public String getPreviousName(Account account) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "getPreviousName: " + account
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (account == null) throw new IllegalArgumentException("account is null");
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            return readPreviousNameInternal(accounts, account);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private String readPreviousNameInternal(UserAccounts accounts, Account account) {
+        if  (account == null) {
+            return null;
+        }
+        synchronized (accounts.cacheLock) {
+            AtomicReference<String> previousNameRef = accounts.previousNameCache.get(account);
+            if (previousNameRef == null) {
+                final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+                Cursor cursor = db.query(
+                        TABLE_ACCOUNTS,
+                        new String[]{ ACCOUNTS_PREVIOUS_NAME },
+                        ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+                        new String[] { account.name, account.type },
+                        null,
+                        null,
+                        null);
+                try {
+                    if (cursor.moveToNext()) {
+                        String previousName = cursor.getString(0);
+                        previousNameRef = new AtomicReference<String>(previousName);
+                        accounts.previousNameCache.put(account, previousNameRef);
+                        return previousName;
+                    } else {
+                        return null;
+                    }
+                } finally {
+                    cursor.close();
+                }
+            } else {
+                return previousNameRef.get();
+            }
+        }
+    }
+
+    @Override
     public String getUserData(Account account, String key) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "getUserData: " + account
@@ -859,6 +926,119 @@
     }
 
     @Override
+    public void renameAccount(
+            IAccountManagerResponse response, Account accountToRename, String newName) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "renameAccount: " + accountToRename + " -> " + newName
+                + ", caller's uid " + Binder.getCallingUid()
+                + ", pid " + Binder.getCallingPid());
+        }
+        if (accountToRename == null) throw new IllegalArgumentException("account is null");
+        checkAuthenticateAccountsPermission(accountToRename);
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            Account resultingAccount = renameAccountInternal(accounts, accountToRename, newName);
+            Bundle result = new Bundle();
+            result.putString(AccountManager.KEY_ACCOUNT_NAME, resultingAccount.name);
+            result.putString(AccountManager.KEY_ACCOUNT_TYPE, resultingAccount.type);
+            try {
+                response.onResult(result);
+            } catch (RemoteException e) {
+                Log.w(TAG, e.getMessage());
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private Account renameAccountInternal(
+            UserAccounts accounts, Account accountToRename, String newName) {
+        Account resultAccount = null;
+        /*
+         * Cancel existing notifications. Let authenticators
+         * re-post notifications as required. But we don't know if
+         * the authenticators have bound their notifications to
+         * now stale account name data.
+         *
+         * With a rename api, we might not need to do this anymore but it
+         * shouldn't hurt.
+         */
+        cancelNotification(
+                getSigninRequiredNotificationId(accounts, accountToRename),
+                 new UserHandle(accounts.userId));
+        synchronized(accounts.credentialsPermissionNotificationIds) {
+            for (Pair<Pair<Account, String>, Integer> pair:
+                    accounts.credentialsPermissionNotificationIds.keySet()) {
+                if (accountToRename.equals(pair.first.first)) {
+                    int id = accounts.credentialsPermissionNotificationIds.get(pair);
+                    cancelNotification(id, new UserHandle(accounts.userId));
+                }
+            }
+        }
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            db.beginTransaction();
+            boolean isSuccessful = false;
+            Account renamedAccount = new Account(newName, accountToRename.type);
+            try {
+                final ContentValues values = new ContentValues();
+                values.put(ACCOUNTS_NAME, newName);
+                values.put(ACCOUNTS_PREVIOUS_NAME, accountToRename.name);
+                final long accountId = getAccountIdLocked(db, accountToRename);
+                if (accountId >= 0) {
+                    final String[] argsAccountId = { String.valueOf(accountId) };
+                    db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
+                    db.setTransactionSuccessful();
+                    isSuccessful = true;
+                }
+            } finally {
+                db.endTransaction();
+                if (isSuccessful) {
+                    /*
+                     * Database transaction was successful. Clean up cached
+                     * data associated with the account in the user profile.
+                     */
+                    insertAccountIntoCacheLocked(accounts, renamedAccount);
+                    /*
+                     * Extract the data and token caches before removing the
+                     * old account to preserve the user data associated with
+                     * the account.
+                     */
+                    HashMap<String, String> tmpData = accounts.userDataCache.get(accountToRename);
+                    HashMap<String, String> tmpTokens = accounts.authTokenCache.get(accountToRename);
+                    removeAccountFromCacheLocked(accounts, accountToRename);
+                    /*
+                     * Update the cached data associated with the renamed
+                     * account.
+                     */
+                    accounts.userDataCache.put(renamedAccount, tmpData);
+                    accounts.authTokenCache.put(renamedAccount, tmpTokens);
+                    accounts.previousNameCache.put(
+                          renamedAccount,
+                          new AtomicReference<String>(accountToRename.name));
+                    resultAccount = renamedAccount;
+
+                    if (accounts.userId == UserHandle.USER_OWNER) {
+                        /*
+                         * Owner's account was renamed, rename the account for
+                         * those users with which the account was shared.
+                         */
+                        List<UserInfo> users = mUserManager.getUsers(true);
+                        for (UserInfo user : users) {
+                            if (!user.isPrimary() && user.isRestricted()) {
+                                renameSharedAccountAsUser(accountToRename, newName, user.id);
+                            }
+                        }
+                    }
+                    sendAccountsChangedBroadcast(accounts.userId);
+                }
+            }
+        }
+        return resultAccount;
+    }
+
+    @Override
     public void removeAccount(IAccountManagerResponse response, Account account) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "removeAccount: " + account
@@ -2061,6 +2241,26 @@
     }
 
     @Override
+    public boolean renameSharedAccountAsUser(Account account, String newName, int userId) {
+        userId = handleIncomingUser(userId);
+        UserAccounts accounts = getUserAccounts(userId);
+        SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+        final ContentValues values = new ContentValues();
+        values.put(ACCOUNTS_NAME, newName);
+        values.put(ACCOUNTS_PREVIOUS_NAME, account.name);
+        int r = db.update(
+                TABLE_SHARED_ACCOUNTS,
+                values,
+                ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+                new String[] { account.name, account.type });
+        if (r > 0) {
+            // Recursively rename the account.
+            renameAccountInternal(accounts, account, newName);
+        }
+        return r > 0;
+    }
+
+    @Override
     public boolean removeSharedAccountAsUser(Account account, int userId) {
         userId = handleIncomingUser(userId);
         UserAccounts accounts = getUserAccounts(userId);
@@ -2557,6 +2757,7 @@
                     + ACCOUNTS_NAME + " TEXT NOT NULL, "
                     + ACCOUNTS_TYPE + " TEXT NOT NULL, "
                     + ACCOUNTS_PASSWORD + " TEXT, "
+                    + ACCOUNTS_PREVIOUS_NAME + " TEXT, "
                     + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
 
             db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " (  "
@@ -2592,6 +2793,10 @@
                     + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
         }
 
+        private void addOldAccountNameColumn(SQLiteDatabase db) {
+            db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
+        }
+
         private void createAccountsDeletionTrigger(SQLiteDatabase db) {
             db.execSQL(""
                     + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
@@ -2642,6 +2847,11 @@
                 oldVersion++;
             }
 
+            if (oldVersion == 5) {
+                addOldAccountNameColumn(db);
+                oldVersion++;
+            }
+
             if (oldVersion != newVersion) {
                 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
             }
@@ -3050,6 +3260,7 @@
         }
         accounts.userDataCache.remove(account);
         accounts.authTokenCache.remove(account);
+        accounts.previousNameCache.remove(account);
     }
 
     /**
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 01a2fc2..d948942 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -724,14 +724,6 @@
         return retVal;
     }
 
-    //TODO: Temporary handling upstream change triggered without
-    //      CONNECTIVITY_ACTION. Only to accomodate interface
-    //      switch during HO.
-    //      @see bug/4455071
-    public void handleTetherIfaceChange() {
-        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
-    }
-
     class TetherInterfaceSM extends StateMachine {
         // notification from the master SM that it's not in tether mode
         static final int CMD_TETHER_MODE_DEAD            =  1;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index d15254b..7b68d55 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -488,18 +488,6 @@
         return tun;
     }
 
-    /**
-     * Check if a given address is covered by the VPN's routing rules.
-     */
-    public boolean isAddressCovered(InetAddress address) {
-        synchronized (Vpn.this) {
-            if (!isRunningLocked()) {
-                return false;
-            }
-            return RouteInfo.selectBestRoute(mConfig.routes, address) != null;
-        }
-    }
-
     private boolean isRunningLocked() {
         return mVpnUsers != null;
     }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index a2dd15e..b0ccd62 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -776,8 +776,8 @@
                 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0) {
                     if (DEBUG) {
                         Log.d(TAG, "No active session to adjust, skipping media only volume event");
-                        return;
                     }
+                    return;
                 }
                 try {
                     mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 8e625c5..a896550 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -90,9 +90,6 @@
             "com.android.mms",
             "com.android.example.notificationshowcase"
             ));
-    private static final Set<String> ALARM_PACKAGES = new HashSet<String>(Arrays.asList(
-            "com.google.android.deskclock"
-            ));
     private static final Set<String> SYSTEM_PACKAGES = new HashSet<String>(Arrays.asList(
             "android",
             "com.android.systemui"
@@ -313,7 +310,8 @@
     }
 
     private boolean isAlarm(NotificationRecord record) {
-        return ALARM_PACKAGES.contains(record.sbn.getPackageName());
+        return record.isCategory(Notification.CATEGORY_ALARM)
+                || record.isCategory(Notification.CATEGORY_EVENT);
     }
 
     private boolean isCall(NotificationRecord record) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 29ef9c1..eb90085 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5675,13 +5675,28 @@
             throw new SecurityException("Requires READ_FRAME_BUFFER permission");
         }
 
+        final DisplayContent displayContent = getDisplayContentLocked(displayId);
+        if (displayContent == null) {
+            if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
+                    + ": returning null. No Display for displayId=" + displayId);
+            return null;
+        }
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        int dw = displayInfo.logicalWidth;
+        int dh = displayInfo.logicalHeight;
+        if (dw == 0 || dh == 0) {
+            if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
+                    + ": returning null. logical widthxheight=" + dw + "x" + dh);
+            return null;
+        }
+
         Bitmap rawss = null;
 
         int maxLayer = 0;
         final Rect frame = new Rect();
+        final Rect stackBounds = new Rect();
 
         float scale = 0;
-        int dw, dh;
         int rot = Surface.ROTATION_0;
 
         boolean screenshotReady;
@@ -5697,7 +5712,15 @@
         int retryCount = 0;
         WindowState appWin = null;
 
-        do {
+        final boolean appIsImTarget = mInputMethodTarget != null
+                && mInputMethodTarget.mAppToken != null
+                && mInputMethodTarget.mAppToken.appToken != null
+                && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken;
+
+        final int aboveAppLayer = (mPolicy.windowTypeToLayerLw(TYPE_APPLICATION) + 1)
+                * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
+
+        while (true) {
             if (retryCount++ > 0) {
                 try {
                     Thread.sleep(100);
@@ -5705,28 +5728,9 @@
                 }
             }
             synchronized(mWindowMap) {
-                final DisplayContent displayContent = getDisplayContentLocked(displayId);
-                if (displayContent == null) {
-                    return null;
-                }
-                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-                dw = displayInfo.logicalWidth;
-                dh = displayInfo.logicalHeight;
-
-                int aboveAppLayer = mPolicy.windowTypeToLayerLw(TYPE_APPLICATION)
-                        * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
-                aboveAppLayer += TYPE_LAYER_MULTIPLIER;
-
-                boolean isImeTarget = mInputMethodTarget != null
-                        && mInputMethodTarget.mAppToken != null
-                        && mInputMethodTarget.mAppToken.appToken != null
-                        && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken;
-
                 // Figure out the part of the screen that is actually the app.
-                boolean including = false;
                 appWin = null;
                 final WindowList windows = displayContent.getWindowList();
-                final Rect stackBounds = new Rect();
                 for (int i = windows.size() - 1; i >= 0; i--) {
                     WindowState ws = windows.get(i);
                     if (!ws.mHasSurface) {
@@ -5735,27 +5739,22 @@
                     if (ws.mLayer >= aboveAppLayer) {
                         continue;
                     }
-                    // When we will skip windows: when we are not including
-                    // ones behind a window we didn't skip, and we are actually
-                    // taking a screenshot of a specific app.
-                    if (!including && appToken != null) {
-                        // Also, we can possibly skip this window if it is not
-                        // an IME target or the application for the screenshot
-                        // is not the current IME target.
-                        if (!ws.mIsImWindow || !isImeTarget) {
-                            // And finally, this window is of no interest if it
-                            // is not associated with the screenshot app.
-                            if (ws.mAppToken == null || ws.mAppToken.token != appToken) {
-                                continue;
-                            }
-                            appWin = ws;
-                            ws.getStackBounds(stackBounds);
+                    if (ws.mIsImWindow) {
+                        if (!appIsImTarget) {
+                            continue;
                         }
+                    } else if (ws.mIsWallpaper) {
+                        // Fall through.
+                    } else if (appToken != null) {
+                        if (ws.mAppToken == null || ws.mAppToken.token != appToken) {
+                            // This app window is of no interest if it is not associated with the
+                            // screenshot app.
+                            continue;
+                        }
+                        appWin = ws;
                     }
 
-                    // We keep on including windows until we go past a full-screen
-                    // window.
-                    including = !ws.mIsImWindow && !ws.isFullscreen(dw, dh);
+                    // Include this window.
 
                     final WindowStateAnimator winAnim = ws.mWinAnimator;
                     if (maxLayer < winAnim.mSurfaceLayer) {
@@ -5774,6 +5773,7 @@
                         int right = wf.right - cr.right;
                         int bottom = wf.bottom - cr.bottom;
                         frame.union(left, top, right, bottom);
+                        ws.getStackBounds(stackBounds);
                         frame.intersect(stackBounds);
                     }
 
@@ -5789,60 +5789,49 @@
                             "Screenshot: Couldn't find a surface matching " + appToken);
                     return null;
                 }
+
                 if (!screenshotReady) {
+                    if (retryCount > MAX_SCREENSHOT_RETRIES) {
+                        Slog.i(TAG, "Screenshot max retries " + retryCount + " of " + appToken +
+                                " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" +
+                                appWin.mWinAnimator.mDrawState)));
+                        return null;
+                    }
+
                     // Delay and hope that window gets drawn.
                     if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot: No image ready for " + appToken
                             + ", " + appWin + " drawState=" + appWin.mWinAnimator.mDrawState);
                     continue;
                 }
 
-                // Constrain frame to the screen size.
-                frame.intersect(0, 0, dw, dh);
+                // Screenshot is ready to be taken. Everything from here below will continue
+                // through the bottom of the loop and return a value. We only stay in the loop
+                // because we don't want to release the mWindowMap lock until the screenshot is
+                // taken.
 
-                if (frame.isEmpty() || maxLayer == 0) {
+                if (maxLayer == 0) {
                     if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
-                            + ": returning null frame=" + frame.toShortString() + " maxLayer="
-                            + maxLayer);
+                            + ": returning null maxLayer=" + maxLayer);
                     return null;
                 }
 
-                // The screenshot API does not apply the current screen rotation.
-                rot = getDefaultDisplayContentLocked().getDisplay().getRotation();
-                int fw = frame.width();
-                int fh = frame.height();
-
+                // Constrain frame to the screen size.
+                frame.intersect(0, 0, dw, dh);
                 // Constrain thumbnail to smaller of screen width or height. Assumes aspect
                 // of thumbnail is the same as the screen (in landscape) or square.
-                scale = Math.max(width / (float) fw, height / (float) fh);
-                /*
-                float targetWidthScale = width / (float) fw;
-                float targetHeightScale = height / (float) fh;
-                if (fw <= fh) {
-                    scale = targetWidthScale;
-                    // If aspect of thumbnail is the same as the screen (in landscape),
-                    // select the slightly larger value so we fill the entire bitmap
-                    if (targetHeightScale > scale && (int) (targetHeightScale * fw) == width) {
-                        scale = targetHeightScale;
-                    }
-                } else {
-                    scale = targetHeightScale;
-                    // If aspect of thumbnail is the same as the screen (in landscape),
-                    // select the slightly larger value so we fill the entire bitmap
-                    if (targetWidthScale > scale && (int) (targetWidthScale * fh) == height) {
-                        scale = targetWidthScale;
-                    }
-                }
-                */
+                scale = Math.max(width / (float) frame.width(), height / (float) frame.height());
+                dw = (int)(dw * scale);
+                dh = (int)(dh * scale);
 
-                // The screen shot will contain the entire screen.
-                dw = (int)(dw*scale);
-                dh = (int)(dh*scale);
+                // The screenshot API does not apply the current screen rotation.
+                rot = getDefaultDisplayContentLocked().getDisplay().getRotation();
                 if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
-                    int tmp = dw;
+                    final int tmp = dw;
                     dw = dh;
                     dh = tmp;
                     rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90;
                 }
+
                 if (DEBUG_SCREENSHOT) {
                     Slog.i(TAG, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
                             + maxLayer + " appToken=" + appToken);
@@ -5853,27 +5842,31 @@
                                 + " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer);
                     }
                 }
-                // 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).
-                // TODO: Replace 'new Rect()' with the portion of the screen to capture for the
-                // screenshot.
-                rawss = SurfaceControl.screenshot(new Rect(), dw, dh, minLayer, maxLayer, false);
-            }
-        } while (!screenshotReady && retryCount <= MAX_SCREENSHOT_RETRIES);
-        if (retryCount > MAX_SCREENSHOT_RETRIES)  Slog.i(TAG, "Screenshot max retries " +
-                retryCount + " of " + appToken + " appWin=" + (appWin == null ?
-                        "null" : (appWin + " drawState=" + appWin.mWinAnimator.mDrawState)));
 
-        if (rawss == null) {
-            Slog.w(TAG, "Screenshot failure taking screenshot for (" + dw + "x" + dh
-                    + ") to layer " + maxLayer);
-            return null;
+                ScreenRotationAnimation screenRotationAnimation =
+                        mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
+                final boolean inRotation = screenRotationAnimation != null &&
+                        screenRotationAnimation.isAnimating();
+                if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG,
+                        "Taking screenshot while rotating");
+
+                rawss = SurfaceControl.screenshot(new Rect(), dw, dh, minLayer, maxLayer,
+                        inRotation);
+                if (rawss == null) {
+                    Slog.w(TAG, "Screenshot failure taking screenshot for (" + dw + "x" + dh
+                            + ") to layer " + maxLayer);
+                    return null;
+                }
+            }
+
+            break;
         }
 
-        Bitmap bm = Bitmap.createBitmap(width, height, force565 ? Config.RGB_565 : rawss.getConfig());
-        bm.eraseColor(0xFF000000);
+        Bitmap bm = Bitmap.createBitmap(width, height, force565 ?
+                Config.RGB_565 : rawss.getConfig());
+        if (DEBUG_SCREENSHOT) {
+            bm.eraseColor(0xFF000000);
+        }
         frame.scale(scale);
         Matrix matrix = new Matrix();
         ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix);
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 88aaafc..9e3dec8 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -162,12 +162,10 @@
         mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
         nextConnBroadcast.get();
 
-        // verify that both routes were added and DNS was flushed
+        // verify that both routes were added
         int mobileNetId = mMobile.tracker.getNetwork().netId;
         verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4));
         verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6));
-        verify(mNetManager).flushNetworkDnsCache(mobileNetId);
-
     }
 
     public void testMobileWifiHandoff() throws Exception {
@@ -204,7 +202,6 @@
         int wifiNetId = mWifi.tracker.getNetwork().netId;
         verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V4));
         verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V6));
-        verify(mNetManager).flushNetworkDnsCache(wifiNetId);
         verify(mMobile.tracker).teardown();
 
         int mobileNetId = mMobile.tracker.getNetwork().netId;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index 50be1dc..1e0d6de 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -100,29 +100,32 @@
     }
 
     public boolean addOrUpdateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
-        SQLiteDatabase db = getWritableDatabase();
-        ContentValues values = new ContentValues();
-        // Generate a random ID for the model.
-        values.put(SoundModelContract.KEY_ID, soundModel.uuid.toString());
-        values.put(SoundModelContract.KEY_DATA, soundModel.data);
-        values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE);
-
-        boolean status = true;
-        if (db.insertWithOnConflict(
-                SoundModelContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1) {
-            for (Keyphrase keyphrase : soundModel.keyphrases) {
-                status &= addOrUpdateKeyphrase(db, soundModel.uuid, keyphrase);
+        synchronized(this) {
+            SQLiteDatabase db = getWritableDatabase();
+            ContentValues values = new ContentValues();
+            // Generate a random ID for the model.
+            values.put(SoundModelContract.KEY_ID, soundModel.uuid.toString());
+            values.put(SoundModelContract.KEY_DATA, soundModel.data);
+            values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE);
+    
+            boolean status = true;
+            if (db.insertWithOnConflict(SoundModelContract.TABLE, null, values,
+                    SQLiteDatabase.CONFLICT_REPLACE) != -1) {
+                for (Keyphrase keyphrase : soundModel.keyphrases) {
+                    status &= addOrUpdateKeyphraseLocked(db, soundModel.uuid, keyphrase);
+                }
+                db.close();
+                return status;
+            } else {
+                Slog.w(TAG, "Failed to persist sound model to database");
+                db.close();
+                return false;
             }
-            db.close();
-            return status;
-        } else {
-            Slog.w(TAG, "Failed to persist sound model to database");
-            db.close();
-            return false;
         }
     }
 
-    private boolean addOrUpdateKeyphrase(SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) {
+    private boolean addOrUpdateKeyphraseLocked(
+            SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) {
         ContentValues values = new ContentValues();
         values.put(KeyphraseContract.KEY_ID, keyphrase.id);
         values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModes);
@@ -143,62 +146,66 @@
      * Deletes the sound model and associated keyphrases.
      */
     public boolean deleteKeyphraseSoundModel(UUID uuid) {
-        SQLiteDatabase db = getWritableDatabase();
-        String modelId = uuid.toString();
-        String soundModelClause = SoundModelContract.KEY_ID + "=" + modelId;
-        boolean status = true;
-        if (db.delete(SoundModelContract.TABLE, soundModelClause, null) == 0) {
-            Slog.w(TAG, "No sound models deleted from the database");
-            status = false;
+        synchronized(this) {
+            SQLiteDatabase db = getWritableDatabase();
+            String modelId = uuid.toString();
+            String soundModelClause = SoundModelContract.KEY_ID + "=" + modelId;
+            boolean status = true;
+            if (db.delete(SoundModelContract.TABLE, soundModelClause, null) == 0) {
+                Slog.w(TAG, "No sound models deleted from the database");
+                status = false;
+            }
+            String keyphraseClause = KeyphraseContract.KEY_SOUND_MODEL_ID + "=" + modelId;
+            if (db.delete(KeyphraseContract.TABLE, keyphraseClause, null) == 0) {
+                Slog.w(TAG, "No keyphrases deleted from the database");
+                status = false;
+            }
+            db.close();
+            return status;
         }
-        String keyphraseClause = KeyphraseContract.KEY_SOUND_MODEL_ID + "=" + modelId;
-        if (db.delete(KeyphraseContract.TABLE, keyphraseClause, null) == 0) {
-            Slog.w(TAG, "No keyphrases deleted from the database");
-            status = false;
-        }
-        db.close();
-        return status;
     }
 
     /**
      * Lists all the keyphrase sound models currently registered with the system.
      */
     public List<KeyphraseSoundModel> getKephraseSoundModels() {
-        List<KeyphraseSoundModel> models = new ArrayList<>();
-        String selectQuery = "SELECT  * FROM " + SoundModelContract.TABLE;
-        SQLiteDatabase db = getReadableDatabase();
-        Cursor c = db.rawQuery(selectQuery, null);
-
-        // looping through all rows and adding to list
-        if (c.moveToFirst()) {
-            do {
-                int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
-                if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
-                    // Ignore non-keyphrase sound models.
-                    continue;
-                }
-                String id = c.getString(c.getColumnIndex(SoundModelContract.KEY_ID));
-                byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
-                // Get all the keyphrases for this this sound model.
-                // Validate the sound model.
-                if (id == null) {
-                    Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID");
-                    continue;
-                }
-                KeyphraseSoundModel model = new KeyphraseSoundModel(
-                        UUID.fromString(id), data, getKeyphrasesForSoundModel(db, id));
-                if (DBG) {
-                    Slog.d(TAG, "Adding model: " + model);
-                }
-                models.add(model);
-            } while (c.moveToNext());
+        synchronized(this) {
+            List<KeyphraseSoundModel> models = new ArrayList<>();
+            String selectQuery = "SELECT  * FROM " + SoundModelContract.TABLE;
+            SQLiteDatabase db = getReadableDatabase();
+            Cursor c = db.rawQuery(selectQuery, null);
+    
+            // looping through all rows and adding to list
+            if (c.moveToFirst()) {
+                do {
+                    int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
+                    if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
+                        // Ignore non-keyphrase sound models.
+                        continue;
+                    }
+                    String id = c.getString(c.getColumnIndex(SoundModelContract.KEY_ID));
+                    byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
+                    // Get all the keyphrases for this this sound model.
+                    // Validate the sound model.
+                    if (id == null) {
+                        Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID");
+                        continue;
+                    }
+                    KeyphraseSoundModel model = new KeyphraseSoundModel(
+                            UUID.fromString(id), data, getKeyphrasesForSoundModelLocked(db, id));
+                    if (DBG) {
+                        Slog.d(TAG, "Adding model: " + model);
+                    }
+                    models.add(model);
+                } while (c.moveToNext());
+            }
+            c.close();
+            db.close();
+            return models;
         }
-        c.close();
-        db.close();
-        return models;
     }
 
-    private Keyphrase[] getKeyphrasesForSoundModel(SQLiteDatabase db, String modelId) {
+    private Keyphrase[] getKeyphrasesForSoundModelLocked(SQLiteDatabase db, String modelId) {
         List<Keyphrase> keyphrases = new ArrayList<>();
         String selectQuery = "SELECT  * FROM " + KeyphraseContract.TABLE
                 + " WHERE " + KeyphraseContract.KEY_SOUND_MODEL_ID + " = '" + modelId + "'";
@@ -243,7 +250,7 @@
     }
 
 
-    private String getCommaSeparatedString(int[] users) {
+    private static String getCommaSeparatedString(int[] users) {
         if (users == null || users.length == 0) {
             return "";
         }
@@ -255,7 +262,7 @@
         return csv.substring(0, csv.length() - 1);
     }
 
-    private int[] getArrayForCommaSeparatedString(String text) {
+    private static int[] getArrayForCommaSeparatedString(String text) {
         if (TextUtils.isEmpty(text)) {
             return null;
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 7b2e4f1..5d9e107 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -266,13 +266,13 @@
                                 + Manifest.permission.MANAGE_VOICE_KEYPHRASES);
                     }
                 }
+            }
 
-                final long caller = Binder.clearCallingIdentity();
-                try {
-                    return mDbHelper.getKephraseSoundModels();
-                } finally {
-                    Binder.restoreCallingIdentity(caller);
-                }
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mDbHelper.getKephraseSoundModels();
+            } finally {
+                Binder.restoreCallingIdentity(caller);
             }
         }
 
@@ -287,29 +287,31 @@
                 if (model == null) {
                     throw new IllegalArgumentException("Model must not be null");
                 }
+            }
 
-                final long caller = Binder.clearCallingIdentity();
-                try {
-                    boolean success = false;
-                    if (model.keyphrases == null) {
-                        // If the keyphrases are not present in the model, delete the model.
-                        success = mDbHelper.deleteKeyphraseSoundModel(model.uuid);
-                    } else {
-                        // Else update the model.
-                        success = mDbHelper.addOrUpdateKeyphraseSoundModel(model);
-                    }
-                    if (success) {
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                boolean success = false;
+                if (model.keyphrases == null) {
+                    // If the keyphrases are not present in the model, delete the model.
+                    success = mDbHelper.deleteKeyphraseSoundModel(model.uuid);
+                } else {
+                    // Else update the model.
+                    success = mDbHelper.addOrUpdateKeyphraseSoundModel(model);
+                }
+                if (success) {
+                    synchronized (this) {
                         // Notify the voice interaction service of a change in sound models.
                         if (mImpl != null && mImpl.mService != null) {
                             mImpl.notifySoundModelsChangedLocked();
                         }
-                        return SoundTriggerHelper.STATUS_OK;
-                    } else {
-                        return SoundTriggerHelper.STATUS_ERROR;
                     }
-                } finally {
-                    Binder.restoreCallingIdentity(caller);
+                    return SoundTriggerHelper.STATUS_OK;
+                } else {
+                    return SoundTriggerHelper.STATUS_ERROR;
                 }
+            } finally {
+                Binder.restoreCallingIdentity(caller);
             }
         }
 
diff --git a/telecomm/java/android/telecomm/RemoteCallVideoClient.java b/telecomm/java/android/telecomm/RemoteCallVideoClient.java
index 8024831..08d1391 100644
--- a/telecomm/java/android/telecomm/RemoteCallVideoClient.java
+++ b/telecomm/java/android/telecomm/RemoteCallVideoClient.java
@@ -26,17 +26,20 @@
 /**
  * Remote class to invoke callbacks in InCallUI related to supporting video in calls.
  */
-public class RemoteCallVideoClient implements IBinder.DeathRecipient {
+public class RemoteCallVideoClient {
     private final ICallVideoClient mCallVideoClient;
 
+    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            mCallVideoClient.asBinder().unlinkToDeath(this, 0);
+        }
+    };
+
+    /** {@hide} */
     RemoteCallVideoClient(ICallVideoClient callVideoProvider) throws RemoteException {
         mCallVideoClient = callVideoProvider;
-        mCallVideoClient.asBinder().linkToDeath(this, 0);
-    }
-
-    @Override
-    public void binderDied() {
-        mCallVideoClient.asBinder().unlinkToDeath(this, 0);
+        mCallVideoClient.asBinder().linkToDeath(mDeathRecipient, 0);
     }
 
     /**
@@ -49,9 +52,11 @@
      *
      * @param videoCallProfile The requested video call profile.
      */
-    public void receiveSessionModifyRequest(VideoCallProfile videoCallProfile)
-            throws RemoteException {
-        mCallVideoClient.receiveSessionModifyRequest(videoCallProfile);
+    public void receiveSessionModifyRequest(VideoCallProfile videoCallProfile) {
+        try {
+            mCallVideoClient.receiveSessionModifyRequest(videoCallProfile);
+        } catch (RemoteException e) {
+        }
     }
 
     /**
@@ -66,9 +71,13 @@
      * @param requestedProfile The original request which was sent to the remote device.
      * @param responseProfile The actual profile changes made by the remote device.
      */
-    public void receiveSessionModifyResponse(int status, VideoCallProfile requestedProfile,
-            VideoCallProfile responseProfile) throws RemoteException {
-        mCallVideoClient.receiveSessionModifyResponse(status, requestedProfile, responseProfile);
+    public void receiveSessionModifyResponse(
+            int status, VideoCallProfile requestedProfile, VideoCallProfile responseProfile) {
+        try {
+            mCallVideoClient.receiveSessionModifyResponse(
+                    status, requestedProfile, responseProfile);
+        } catch (RemoteException e) {
+        }
     }
 
     /**
@@ -81,8 +90,11 @@
      *
      * @param event The event.
      */
-    public void handleCallSessionEvent(int event) throws RemoteException {
-        mCallVideoClient.handleCallSessionEvent(event);
+    public void handleCallSessionEvent(int event) {
+        try {
+            mCallVideoClient.handleCallSessionEvent(event);
+        } catch (RemoteException e) {
+        }
     }
 
     /**
@@ -92,8 +104,11 @@
      * @param width  The updated peer video width.
      * @param height The updated peer video height.
      */
-    public void updatePeerDimensions(int width, int height) throws RemoteException {
-        mCallVideoClient.updatePeerDimensions(width, height);
+    public void updatePeerDimensions(int width, int height) {
+        try {
+            mCallVideoClient.updatePeerDimensions(width, height);
+        } catch (RemoteException e) {
+        }
     }
 
     /**
@@ -101,8 +116,11 @@
      *
      * @param dataUsage The updated data usage.
      */
-    public void updateCallDataUsage(int dataUsage) throws RemoteException {
-        mCallVideoClient.updateCallDataUsage(dataUsage);
+    public void updateCallDataUsage(int dataUsage) {
+        try {
+            mCallVideoClient.updateCallDataUsage(dataUsage);
+        } catch (RemoteException e) {
+        }
     }
 
     /**
@@ -110,8 +128,10 @@
      *
      * @param callCameraCapabilities The changed camera capabilities.
      */
-    public void handleCameraCapabilitiesChange(CallCameraCapabilities callCameraCapabilities)
-            throws RemoteException {
-        mCallVideoClient.handleCameraCapabilitiesChange(callCameraCapabilities);
+    public void handleCameraCapabilitiesChange(CallCameraCapabilities callCameraCapabilities) {
+        try {
+            mCallVideoClient.handleCameraCapabilitiesChange(callCameraCapabilities);
+        } catch (RemoteException e) {
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java b/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java
index 9ca156f..e5216b5 100644
--- a/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java
+++ b/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java
@@ -31,6 +31,7 @@
 import android.media.MediaPlayer.OnPreparedListener;
 import android.media.browse.MediaBrowserItem;
 import android.media.browse.MediaBrowserService;
+import android.media.browse.MediaBrowserService.BrowserRoot;
 import android.media.session.MediaSession;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
@@ -115,24 +116,27 @@
     }
 
     @Override
-    public Uri onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        return BROWSE_URI;
+    protected BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+        return new BrowserRoot(BROWSE_URI, null);
     }
 
     @Override
-    public List<MediaBrowserItem> onLoadChildren(Uri parentUri) {
+    protected List<MediaBrowserItem> onLoadChildren(Uri parentUri) {
         final ArrayList<MediaBrowserItem> results = new ArrayList();
 
         for (int i=0; i<10; i++) {
-            results.add(new MediaBrowserItem.Builder(Uri.withAppendedPath(BASE_URI, Integer.toString(i)),
-                    MediaBrowserItem.FLAG_BROWSABLE, "Title " + i).setSummary("Summary " + i).build());
+            results.add(new MediaBrowserItem.Builder(
+                    Uri.withAppendedPath(BASE_URI, Integer.toString(i)),
+                    MediaBrowserItem.FLAG_BROWSABLE, "Title " + i)
+                    .setSummary("Summary " + i)
+                    .build());
         }
 
         return results;
     }
 
     @Override
-    public Bitmap onGetThumbnail(Uri uri, int width, int height, int density) {
+    protected Bitmap onGetThumbnail(Uri uri, int width, int height) {
         return null;
     }
 
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index e750bb6..cc710f9 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -30,6 +30,12 @@
 
     private final Callback mHotwordCallback = new Callback() {
         @Override
+        public void onAvailabilityChanged(int status) {
+            Log.i(TAG, "onAvailabilityChanged(" + status + ")");
+            hotwordAvailabilityChangeHelper(status);
+        }
+
+        @Override
         public void onDetected(byte[] data) {
             Log.i(TAG, "onDetected");
         }
@@ -51,17 +57,6 @@
                 + Arrays.toString(getKeyphraseEnrollmentInfo().listKeyphraseMetadata()));
 
         mHotwordDetector = createAlwaysOnHotwordDetector("Hello There", "en-US", mHotwordCallback);
-        testHotwordAvailabilityStates();
-    }
-
-    @Override
-    public void onSoundModelsChanged() {
-        int availability = mHotwordDetector.getAvailability();
-        Log.i(TAG, "Hotword availability = " + availability);
-        if (availability == AlwaysOnHotwordDetector.STATE_INVALID) {
-            mHotwordDetector = createAlwaysOnHotwordDetector("Hello There", "en-US", mHotwordCallback);
-        }
-        testHotwordAvailabilityStates();
     }
 
     @Override
@@ -73,12 +68,13 @@
         return START_NOT_STICKY;
     }
 
-    private void testHotwordAvailabilityStates() {
-        int availability = mHotwordDetector.getAvailability();
+    private void hotwordAvailabilityChangeHelper(int availability) {
         Log.i(TAG, "Hotword availability = " + availability);
         switch (availability) {
             case AlwaysOnHotwordDetector.STATE_INVALID:
                 Log.i(TAG, "STATE_INVALID");
+                mHotwordDetector =
+                        createAlwaysOnHotwordDetector("Hello There", "en-US", mHotwordCallback);
                 break;
             case AlwaysOnHotwordDetector.STATE_HARDWARE_UNAVAILABLE:
                 Log.i(TAG, "STATE_HARDWARE_UNAVAILABLE");