Merge "docs: Updated the link to buy a Nexus phone. Bug: 13671312" into klp-docs
diff --git a/Android.mk b/Android.mk
index 54ca200..3473e18 100644
--- a/Android.mk
+++ b/Android.mk
@@ -251,17 +251,22 @@
 	media/java/android/media/IAudioService.aidl \
 	media/java/android/media/IAudioFocusDispatcher.aidl \
 	media/java/android/media/IAudioRoutesObserver.aidl \
+	media/java/android/media/IMediaRouterClient.aidl \
+	media/java/android/media/IMediaRouterService.aidl \
 	media/java/android/media/IMediaScannerListener.aidl \
 	media/java/android/media/IMediaScannerService.aidl \
 	media/java/android/media/IRemoteControlClient.aidl \
 	media/java/android/media/IRemoteControlDisplay.aidl \
+	media/java/android/media/IRemoteDisplayCallback.aidl \
+	media/java/android/media/IRemoteDisplayProvider.aidl \
 	media/java/android/media/IRemoteVolumeObserver.aidl \
 	media/java/android/media/IRingtonePlayer.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
 	telephony/java/com/android/internal/telephony/ITelephony.aidl \
-	telephony/java/com/android/internal/telephony/ISms.aidl \
+	telephony/java/com/android/internal/telephony/ITelephonyListener.aidl \
 	telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \
+	telephony/java/com/android/internal/telephony/ISms.aidl \
 	telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
 	wifi/java/android/net/wifi/IWifiManager.aidl \
 	wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
@@ -597,6 +602,32 @@
 $(INTERNAL_PLATFORM_API_FILE): $(full_target)
 $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE))
 
+# ====  the private api stubs ===================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=$(framework_docs_LOCAL_API_CHECK_SRC_FILES)
+LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES)
+LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
+LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
+LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR)
+LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR)
+LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
+
+LOCAL_MODULE := private-api-stubs
+
+LOCAL_DROIDDOC_OPTIONS:=\
+		$(framework_docs_LOCAL_DROIDDOC_OPTIONS) \
+		-stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android_private_stubs_current_intermediates/src \
+        -showAnnotation android.annotation.PrivateApi \
+		-nodocs
+
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
+
+LOCAL_UNINSTALLABLE_MODULE := true
+
+include $(BUILD_DROIDDOC)
+
 # ====  check javadoc comments but don't generate docs ========
 include $(CLEAR_VARS)
 
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index c18f542..0344d26 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -107,7 +107,7 @@
                 "       am switch-user <USER_ID>\n" +
                 "       am stop-user <USER_ID>\n" +
                 "       am stack create <TASK_ID> <RELATIVE_STACK_BOX_ID> <POSITION> <WEIGHT>\n" +
-                "       am stack movetask <STACK_ID> <TASK_ID> [true|false]\n" +
+                "       am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" +
                 "       am stack resize <STACK_ID> <WEIGHT>\n" +
                 "       am stack boxes\n" +
                 "       am stack box <STACK_BOX_ID>\n" +
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 2666b41..db3d8bb 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -187,6 +187,12 @@
     }
 
     private void doWipe() {
+        String transport = nextArg();
+        if (transport == null) {
+            showUsage();
+            return;
+        }
+
         String pkg = nextArg();
         if (pkg == null) {
             showUsage();
@@ -194,8 +200,8 @@
         }
 
         try {
-            mBmgr.clearBackupData(pkg);
-            System.out.println("Wiped backup data for " + pkg);
+            mBmgr.clearBackupData(transport, pkg);
+            System.out.println("Wiped backup data for " + pkg + " on " + transport);
         } catch (RemoteException e) {
             System.err.println(e.toString());
             System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -446,7 +452,7 @@
         System.err.println("       bmgr restore TOKEN PACKAGE...");
         System.err.println("       bmgr restore PACKAGE");
         System.err.println("       bmgr run");
-        System.err.println("       bmgr wipe PACKAGE");
+        System.err.println("       bmgr wipe TRANSPORT PACKAGE");
         System.err.println("");
         System.err.println("The 'backup' command schedules a backup pass for the named package.");
         System.err.println("Note that the backup pass will effectively be a no-op if the package");
@@ -462,8 +468,8 @@
         System.err.println("");
         System.err.println("The 'list transports' command reports the names of the backup transports");
         System.err.println("currently available on the device.  These names can be passed as arguments");
-        System.err.println("to the 'transport' command.  The currently selected transport is indicated");
-        System.err.println("with a '*' character.");
+        System.err.println("to the 'transport' and 'wipe' commands.  The currently selected transport");
+        System.err.println("is indicated with a '*' character.");
         System.err.println("");
         System.err.println("The 'list sets' command reports the token and name of each restore set");
         System.err.println("available to the device via the current transport.");
@@ -491,7 +497,8 @@
         System.err.println("data changes.");
         System.err.println("");
         System.err.println("The 'wipe' command causes all backed-up data for the given package to be");
-        System.err.println("erased from the current transport's storage.  The next backup operation");
+        System.err.println("erased from the given transport's storage.  The next backup operation");
         System.err.println("that the given application performs will rewrite its entire data set.");
+        System.err.println("Transport names to use here are those reported by 'list transports'.");
     }
 }
diff --git a/core/java/android/annotation/PrivateApi.java b/core/java/android/annotation/PrivateApi.java
new file mode 100644
index 0000000..985eafe
--- /dev/null
+++ b/core/java/android/annotation/PrivateApi.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates an API is exposed for use by bundled applications.
+ * <p>
+ * These APIs are not guaranteed to remain consistent release-to-release,
+ * and are not for use by apps linking against the SDK.
+ * @hide
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface PrivateApi {
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 193724d..d6db8c2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4857,7 +4857,11 @@
 
         mFragments.dump(prefix, fd, writer, args);
 
-        getWindow().getDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
+        if (getWindow() != null &&
+                getWindow().peekDecorView() != null &&
+                getWindow().peekDecorView().getViewRootImpl() != null) {
+            getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
+        }
 
         mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
     }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index bb04063..7ca3459 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2272,9 +2272,12 @@
     public static void dumpPackageStateStatic(FileDescriptor fd, String packageName) {
         FileOutputStream fout = new FileOutputStream(fd);
         PrintWriter pw = new FastPrintWriter(fout);
-        dumpService(pw, fd, Context.ACTIVITY_SERVICE, new String[] { "package", packageName });
+        dumpService(pw, fd, Context.ACTIVITY_SERVICE, new String[] {
+                "-a", "package", packageName });
         pw.println();
-        dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName });
+        dumpService(pw, fd, "meminfo", new String[] { "--local", packageName });
+        pw.println();
+        dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { "-a", packageName });
         pw.println();
         dumpService(pw, fd, "usagestats", new String[] { "--packages", packageName });
         pw.println();
@@ -2296,7 +2299,7 @@
             pw.flush();
             tp = new TransferPipe();
             tp.setBufferPrefix("  ");
-            service.dump(tp.getWriteFd().getFileDescriptor(), args);
+            service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
             tp.go(fd);
         } catch (Throwable e) {
             if (tp != null) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5e3dc02..4825c56 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -152,7 +152,7 @@
     private static final int LOG_ON_PAUSE_CALLED = 30021;
     private static final int LOG_ON_RESUME_CALLED = 30022;
 
-    static ContextImpl mSystemContext = null;
+    private ContextImpl mSystemContext;
 
     static IPackageManager sPackageManager;
 
@@ -1645,7 +1645,7 @@
                                 ? mBoundApplication.processName : null)
                         + ")");
                 packageInfo =
-                    new LoadedApk(this, aInfo, compatInfo, this, baseLoader,
+                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                             securityViolation, includeCode &&
                             (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
                 if (includeCode) {
@@ -1698,26 +1698,15 @@
     public ContextImpl getSystemContext() {
         synchronized (this) {
             if (mSystemContext == null) {
-                ContextImpl context =
-                    ContextImpl.createSystemContext(this);
-                LoadedApk info = new LoadedApk(this, "android", context, null,
-                        CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);
-                context.init(info, null, this);
-                context.getResources().updateConfiguration(mResourcesManager.getConfiguration(),
-                        mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY));
-                mSystemContext = context;
-                //Slog.i(TAG, "Created system resources " + context.getResources()
-                //        + ": " + context.getResources().getConfiguration());
+                mSystemContext = ContextImpl.createSystemContext(this);
             }
+            return mSystemContext;
         }
-        return mSystemContext;
     }
 
     public void installSystemApplicationInfo(ApplicationInfo info) {
         synchronized (this) {
-            ContextImpl context = getSystemContext();
-            context.init(new LoadedApk(this, "android", context, info,
-                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO), null, this);
+            getSystemContext().installSystemApplicationInfo(info);
 
             // give ourselves a default profiler
             mProfiler = new Profiler();
@@ -2203,8 +2192,7 @@
 
     private Context createBaseContextForActivity(ActivityClientRecord r,
             final Activity activity) {
-        ContextImpl appContext = new ContextImpl();
-        appContext.init(r.packageInfo, r.token, this);
+        ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
         appContext.setOuterContext(activity);
 
         // For debugging purposes, if the activity's package name contains the value of
@@ -2489,8 +2477,7 @@
                 agent = (BackupAgent) cl.loadClass(classname).newInstance();
 
                 // set up the agent's context
-                ContextImpl context = new ContextImpl();
-                context.init(packageInfo, null, this);
+                ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
                 context.setOuterContext(agent);
                 agent.attach(context);
 
@@ -2562,11 +2549,10 @@
         try {
             if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
 
-            ContextImpl context = new ContextImpl();
-            context.init(packageInfo, null, this);
+            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
+            context.setOuterContext(service);
 
             Application app = packageInfo.makeApplication(false, mInstrumentation);
-            context.setOuterContext(service);
             service.attach(context, this, data.info.name, data.token, app,
                     ActivityManagerNative.getDefault());
             service.onCreate();
@@ -4162,8 +4148,7 @@
         }
         updateDefaultDensity();
 
-        final ContextImpl appContext = new ContextImpl();
-        appContext.init(data.info, null, this);
+        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
         if (!Process.isIsolated()) {
             final File cacheDir = appContext.getCacheDir();
 
@@ -4274,8 +4259,7 @@
             instrApp.nativeLibraryDir = ii.nativeLibraryDir;
             LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                     appContext.getClassLoader(), false, true);
-            ContextImpl instrContext = new ContextImpl();
-            instrContext.init(pi, null, this);
+            ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
 
             try {
                 java.lang.ClassLoader cl = instrContext.getClassLoader();
@@ -4890,8 +4874,8 @@
                                                     UserHandle.myUserId());
             try {
                 mInstrumentation = new Instrumentation();
-                ContextImpl context = new ContextImpl();
-                context.init(getSystemContext().mPackageInfo, null, this);
+                ContextImpl context = ContextImpl.createAppContext(
+                        this, getSystemContext().mPackageInfo);
                 Application app = Instrumentation.newApplication(Application.class, context);
                 mAllApplications.add(app);
                 mInitialApplication = app;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 190ddb4..d9cad3b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -183,22 +183,31 @@
      */
     private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;
 
-    /*package*/ LoadedApk mPackageInfo;
-    private String mBasePackageName;
-    private String mOpPackageName;
-    private Resources mResources;
-    /*package*/ ActivityThread mMainThread;
+    final ActivityThread mMainThread;
+    final LoadedApk mPackageInfo;
+
+    private final IBinder mActivityToken;
+
+    private final UserHandle mUser;
+
+    private final ApplicationContentResolver mContentResolver;
+
+    private final String mBasePackageName;
+    private final String mOpPackageName;
+
+    private final ResourcesManager mResourcesManager;
+    private final Resources mResources;
+    private final Display mDisplay; // may be null if default display
+    private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
+    private final Configuration mOverrideConfiguration;
+
+    private final boolean mRestricted;
+
     private Context mOuterContext;
-    private IBinder mActivityToken = null;
-    private ApplicationContentResolver mContentResolver;
     private int mThemeResource = 0;
     private Resources.Theme mTheme = null;
     private PackageManager mPackageManager;
-    private Display mDisplay; // may be null if default display
     private Context mReceiverRestrictedContext = null;
-    private boolean mRestricted;
-    private UserHandle mUser;
-    private ResourcesManager mResourcesManager;
 
     private final Object mSync = new Object();
 
@@ -220,8 +229,6 @@
 
     private static final String[] EMPTY_FILE_LIST = {};
 
-    final private DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
-
     /**
      * Override this class when the system service constructor needs a
      * ContextImpl.  Else, use StaticServiceFetcher below.
@@ -356,10 +363,11 @@
                             ctx.mMainThread.getHandler());
                 }});
 
-        registerService(CONNECTIVITY_SERVICE, new StaticServiceFetcher() {
-                public Object createStaticService() {
+        registerService(CONNECTIVITY_SERVICE, new ServiceFetcher() {
+                public Object createService(ContextImpl ctx) {
                     IBinder b = ServiceManager.getService(CONNECTIVITY_SERVICE);
-                    return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b));
+                    return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b),
+                        ctx.getPackageName());
                 }});
 
         registerService(COUNTRY_DETECTOR, new StaticServiceFetcher() {
@@ -1878,20 +1886,17 @@
     @Override
     public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
             throws NameNotFoundException {
+        final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
         if (packageName.equals("system") || packageName.equals("android")) {
-            final ContextImpl context = new ContextImpl(mMainThread.getSystemContext());
-            context.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
-            context.init(mPackageInfo, null, mMainThread, mResources, mBasePackageName, user);
-            return context;
+            return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+                    user, restricted, mDisplay, mOverrideConfiguration);
         }
 
-        LoadedApk pi =
-            mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags,
-                    user.getIdentifier());
+        LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
+                flags, user.getIdentifier());
         if (pi != null) {
-            ContextImpl c = new ContextImpl();
-            c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
-            c.init(pi, null, mMainThread, mResources, mBasePackageName, user);
+            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
+                    user, restricted, mDisplay, mOverrideConfiguration);
             if (c.mResources != null) {
                 return c;
             }
@@ -1899,7 +1904,7 @@
 
         // Should be a better exception.
         throw new PackageManager.NameNotFoundException(
-            "Application package " + packageName + " not found");
+                "Application package " + packageName + " not found");
     }
 
     @Override
@@ -1908,12 +1913,8 @@
             throw new IllegalArgumentException("overrideConfiguration must not be null");
         }
 
-        ContextImpl c = new ContextImpl();
-        c.init(mPackageInfo, null, mMainThread);
-        c.mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
-                getDisplayId(), overrideConfiguration, mResources.getCompatibilityInfo(),
-                mActivityToken);
-        return c;
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+                mUser, mRestricted, mDisplay, overrideConfiguration);
     }
 
     @Override
@@ -1922,15 +1923,8 @@
             throw new IllegalArgumentException("display must not be null");
         }
 
-        int displayId = display.getDisplayId();
-
-        ContextImpl context = new ContextImpl();
-        context.init(mPackageInfo, null, mMainThread);
-        context.mDisplay = display;
-        DisplayAdjustments daj = getDisplayAdjustments(displayId);
-        context.mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
-                displayId, null, daj.getCompatibilityInfo(), null);
-        return context;
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+                mUser, mRestricted, display, mOverrideConfiguration);
     }
 
     private int getDisplayId() {
@@ -1972,43 +1966,76 @@
     }
 
     static ContextImpl createSystemContext(ActivityThread mainThread) {
-        final ContextImpl context = new ContextImpl();
-        context.init(Resources.getSystem(), mainThread, Process.myUserHandle());
+        LoadedApk packageInfo = new LoadedApk(mainThread);
+        ContextImpl context = new ContextImpl(null, mainThread,
+                packageInfo, null, null, false, null, null);
+        context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
+                context.mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY));
         return context;
     }
 
-    ContextImpl() {
+    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
+        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
+        return new ContextImpl(null, mainThread,
+                packageInfo, null, null, false, null, null);
+    }
+
+    static ContextImpl createActivityContext(ActivityThread mainThread,
+            LoadedApk packageInfo, IBinder activityToken) {
+        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
+        if (activityToken == null) throw new IllegalArgumentException("activityInfo");
+        return new ContextImpl(null, mainThread,
+                packageInfo, activityToken, null, false, null, null);
+    }
+
+    private ContextImpl(ContextImpl container, ActivityThread mainThread,
+            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
+            Display display, Configuration overrideConfiguration) {
         mOuterContext = this;
-    }
 
-    /**
-     * Create a new ApplicationContext from an existing one.  The new one
-     * works and operates the same as the one it is copying.
-     *
-     * @param context Existing application context.
-     */
-    public ContextImpl(ContextImpl context) {
-        mPackageInfo = context.mPackageInfo;
-        mBasePackageName = context.mBasePackageName;
-        mOpPackageName = context.mOpPackageName;
-        mResources = context.mResources;
-        mMainThread = context.mMainThread;
-        mContentResolver = context.mContentResolver;
-        mUser = context.mUser;
-        mDisplay = context.mDisplay;
-        mOuterContext = this;
-        mDisplayAdjustments.setCompatibilityInfo(mPackageInfo.getCompatibilityInfo());
-    }
+        mMainThread = mainThread;
+        mActivityToken = activityToken;
+        mRestricted = restricted;
 
-    final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) {
-        init(packageInfo, activityToken, mainThread, null, null, Process.myUserHandle());
-    }
+        if (user == null) {
+            user = Process.myUserHandle();
+        }
+        mUser = user;
 
-    final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread,
-            Resources container, String basePackageName, UserHandle user) {
         mPackageInfo = packageInfo;
-        if (basePackageName != null) {
-            mBasePackageName = mOpPackageName = basePackageName;
+        mContentResolver = new ApplicationContentResolver(this, mainThread, user);
+        mResourcesManager = ResourcesManager.getInstance();
+        mDisplay = display;
+        mOverrideConfiguration = overrideConfiguration;
+
+        final int displayId = getDisplayId();
+        CompatibilityInfo compatInfo = null;
+        if (container != null) {
+            compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo();
+        }
+        if (compatInfo == null && displayId == Display.DEFAULT_DISPLAY) {
+            compatInfo = packageInfo.getCompatibilityInfo();
+        }
+        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
+        mDisplayAdjustments.setActivityToken(activityToken);
+
+        Resources resources = packageInfo.getResources(mainThread);
+        if (resources != null) {
+            if (activityToken != null
+                    || displayId != Display.DEFAULT_DISPLAY
+                    || overrideConfiguration != null
+                    || (compatInfo != null && compatInfo.applicationScale
+                            != resources.getCompatibilityInfo().applicationScale)) {
+                resources = mResourcesManager.getTopLevelResources(
+                        packageInfo.getResDir(), displayId,
+                        overrideConfiguration, compatInfo, activityToken);
+            }
+        }
+        mResources = resources;
+
+        if (container != null) {
+            mBasePackageName = container.mBasePackageName;
+            mOpPackageName = container.mOpPackageName;
         } else {
             mBasePackageName = packageInfo.mPackageName;
             ApplicationInfo ainfo = packageInfo.getApplicationInfo();
@@ -2022,44 +2049,10 @@
                 mOpPackageName = mBasePackageName;
             }
         }
-        mResources = mPackageInfo.getResources(mainThread);
-        mResourcesManager = ResourcesManager.getInstance();
-
-        CompatibilityInfo compatInfo =
-                container == null ? null : container.getCompatibilityInfo();
-        if (mResources != null &&
-                ((compatInfo != null && compatInfo.applicationScale !=
-                        mResources.getCompatibilityInfo().applicationScale)
-                || activityToken != null)) {
-            if (DEBUG) {
-                Log.d(TAG, "loaded context has different scaling. Using container's" +
-                        " compatiblity info:" + container.getDisplayMetrics());
-            }
-            if (compatInfo == null) {
-                compatInfo = packageInfo.getCompatibilityInfo();
-            }
-            mDisplayAdjustments.setCompatibilityInfo(compatInfo);
-            mDisplayAdjustments.setActivityToken(activityToken);
-            mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
-                    Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
-        } else {
-            mDisplayAdjustments.setCompatibilityInfo(packageInfo.getCompatibilityInfo());
-            mDisplayAdjustments.setActivityToken(activityToken);
-        }
-        mMainThread = mainThread;
-        mActivityToken = activityToken;
-        mContentResolver = new ApplicationContentResolver(this, mainThread, user);
-        mUser = user;
     }
 
-    final void init(Resources resources, ActivityThread mainThread, UserHandle user) {
-        mPackageInfo = null;
-        mBasePackageName = null;
-        mOpPackageName = null;
-        mResources = resources;
-        mMainThread = mainThread;
-        mContentResolver = new ApplicationContentResolver(this, mainThread, user);
-        mUser = user;
+    void installSystemApplicationInfo(ApplicationInfo info) {
+        mPackageInfo.installSystemApplicationInfo(info);
     }
 
     final void scheduleFinalCleanup(String who, String what) {
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 22a21cd..aab6ed8 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -205,7 +205,9 @@
         try {
             mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() {
                 public void onKeyguardExitResult(boolean success) throws RemoteException {
-                    callback.onKeyguardExitResult(success);
+                    if (callback != null) {
+                        callback.onKeyguardExitResult(success);
+                    }
                 }
             });
         } catch (RemoteException e) {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 4239a5d..8b0fd87 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -72,7 +72,7 @@
     private static final String TAG = "LoadedApk";
 
     private final ActivityThread mActivityThread;
-    private final ApplicationInfo mApplicationInfo;
+    private ApplicationInfo mApplicationInfo;
     final String mPackageName;
     private final String mAppDir;
     private final String mResDir;
@@ -110,8 +110,7 @@
      * so MUST NOT call back out to the activity manager.
      */
     public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
-            CompatibilityInfo compatInfo,
-            ActivityThread mainThread, ClassLoader baseLoader,
+            CompatibilityInfo compatInfo, ClassLoader baseLoader,
             boolean securityViolation, boolean includeCode) {
         mActivityThread = activityThread;
         mApplicationInfo = aInfo;
@@ -132,31 +131,17 @@
         mSecurityViolation = securityViolation;
         mIncludeCode = includeCode;
         mDisplayAdjustments.setCompatibilityInfo(compatInfo);
-
-        if (mAppDir == null) {
-            if (ActivityThread.mSystemContext == null) {
-                ActivityThread.mSystemContext =
-                    ContextImpl.createSystemContext(mainThread);
-                ResourcesManager resourcesManager = ResourcesManager.getInstance();
-                ActivityThread.mSystemContext.getResources().updateConfiguration(
-                        resourcesManager.getConfiguration(),
-                        resourcesManager.getDisplayMetricsLocked(
-                                 Display.DEFAULT_DISPLAY, mDisplayAdjustments), compatInfo);
-                //Slog.i(TAG, "Created system resources "
-                //        + mSystemContext.getResources() + ": "
-                //        + mSystemContext.getResources().getConfiguration());
-            }
-            mClassLoader = ActivityThread.mSystemContext.getClassLoader();
-            mResources = ActivityThread.mSystemContext.getResources();
-        }
     }
 
-    public LoadedApk(ActivityThread activityThread, String name,
-            Context systemContext, ApplicationInfo info, CompatibilityInfo compatInfo) {
+    /**
+     * Create information about the system package.
+     * Must call {@link #installSystemApplicationInfo} later.
+     */
+    LoadedApk(ActivityThread activityThread) {
         mActivityThread = activityThread;
-        mApplicationInfo = info != null ? info : new ApplicationInfo();
-        mApplicationInfo.packageName = name;
-        mPackageName = name;
+        mApplicationInfo = new ApplicationInfo();
+        mApplicationInfo.packageName = "android";
+        mPackageName = "android";
         mAppDir = null;
         mResDir = null;
         mSharedLibraries = null;
@@ -166,9 +151,16 @@
         mBaseClassLoader = null;
         mSecurityViolation = false;
         mIncludeCode = true;
-        mClassLoader = systemContext.getClassLoader();
-        mResources = systemContext.getResources();
-        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
+        mClassLoader = ClassLoader.getSystemClassLoader();
+        mResources = Resources.getSystem();
+    }
+
+    /**
+     * Sets application info about the system package.
+     */
+    void installSystemApplicationInfo(ApplicationInfo info) {
+        assert info.packageName.equals("android");
+        mApplicationInfo = info;
     }
 
     public String getPackageName() {
@@ -506,8 +498,7 @@
 
         try {
             java.lang.ClassLoader cl = getClassLoader();
-            ContextImpl appContext = new ContextImpl();
-            appContext.init(this, null, mActivityThread);
+            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
             app = mActivityThread.mInstrumentation.newApplication(
                     cl, appClass, appContext);
             appContext.setOuterContext(app);
diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java
index 63b641c..dffa969 100644
--- a/core/java/android/app/MediaRouteActionProvider.java
+++ b/core/java/android/app/MediaRouteActionProvider.java
@@ -16,10 +16,7 @@
 
 package android.app;
 
-import com.android.internal.app.MediaRouteChooserDialogFragment;
-
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteInfo;
 import android.util.Log;
@@ -30,22 +27,38 @@
 
 import java.lang.ref.WeakReference;
 
+/**
+ * The media route action provider displays a {@link MediaRouteButton media route button}
+ * in the application's {@link ActionBar} to allow the user to select routes and
+ * to control the currently selected route.
+ * <p>
+ * The application must specify the kinds of routes that the user should be allowed
+ * to select by specifying the route types with the {@link #setRouteTypes} method.
+ * </p><p>
+ * Refer to {@link MediaRouteButton} for a description of the button that will
+ * appear in the action bar menu.  Note that instead of disabling the button
+ * when no routes are available, the action provider will instead make the
+ * menu item invisible.  In this way, the button will only be visible when it
+ * is possible for the user to discover and select a matching route.
+ * </p>
+ */
 public class MediaRouteActionProvider extends ActionProvider {
     private static final String TAG = "MediaRouteActionProvider";
 
-    private Context mContext;
-    private MediaRouter mRouter;
-    private MenuItem mMenuItem;
-    private MediaRouteButton mView;
+    private final Context mContext;
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+
     private int mRouteTypes;
+    private MediaRouteButton mButton;
     private View.OnClickListener mExtendedSettingsListener;
-    private RouterCallback mCallback;
 
     public MediaRouteActionProvider(Context context) {
         super(context);
+
         mContext = context;
         mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
-        mCallback = new RouterCallback(this);
+        mCallback = new MediaRouterCallback(this);
 
         // Start with live audio by default.
         // TODO Update this when new route types are added; segment by API level
@@ -53,80 +66,74 @@
         setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO);
     }
 
+    /**
+     * Sets the types of routes that will be shown in the media route chooser dialog
+     * launched by this button.
+     *
+     * @param types The route types to match.
+     */
     public void setRouteTypes(int types) {
-        if (mRouteTypes == types) return;
-        if (mRouteTypes != 0) {
-            mRouter.removeCallback(mCallback);
+        if (mRouteTypes != types) {
+            // FIXME: We currently have no way of knowing whether the action provider
+            // is still needed by the UI.  Unfortunately this means the action provider
+            // may leak callbacks until garbage collection occurs.  This may result in
+            // media route providers doing more work than necessary in the short term
+            // while trying to discover routes that are no longer of interest to the
+            // application.  To solve this problem, the action provider will need some
+            // indication from the framework that it is being destroyed.
+            if (mRouteTypes != 0) {
+                mRouter.removeCallback(mCallback);
+            }
+            mRouteTypes = types;
+            if (types != 0) {
+                mRouter.addCallback(types, mCallback,
+                        MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+            }
+            refreshRoute();
+
+            if (mButton != null) {
+                mButton.setRouteTypes(mRouteTypes);
+            }
         }
-        mRouteTypes = types;
-        if (types != 0) {
-            mRouter.addCallback(types, mCallback);
-        }
-        if (mView != null) {
-            mView.setRouteTypes(mRouteTypes);
+    }
+
+    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+        mExtendedSettingsListener = listener;
+        if (mButton != null) {
+            mButton.setExtendedSettingsClickListener(listener);
         }
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     public View onCreateActionView() {
         throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead.");
     }
 
     @Override
     public View onCreateActionView(MenuItem item) {
-        if (mMenuItem != null || mView != null) {
+        if (mButton != null) {
             Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " +
                     "with a menu item. Don't reuse MediaRouteActionProvider instances! " +
                     "Abandoning the old one...");
         }
-        mMenuItem = item;
-        mView = new MediaRouteButton(mContext);
-        mView.setCheatSheetEnabled(true);
-        mView.setRouteTypes(mRouteTypes);
-        mView.setExtendedSettingsClickListener(mExtendedSettingsListener);
-        mView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+
+        mButton = new MediaRouteButton(mContext);
+        mButton.setCheatSheetEnabled(true);
+        mButton.setRouteTypes(mRouteTypes);
+        mButton.setExtendedSettingsClickListener(mExtendedSettingsListener);
+        mButton.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
                 ViewGroup.LayoutParams.MATCH_PARENT));
-        return mView;
+        return mButton;
     }
 
     @Override
     public boolean onPerformDefaultAction() {
-        final FragmentManager fm = getActivity().getFragmentManager();
-        // See if one is already attached to this activity.
-        MediaRouteChooserDialogFragment dialogFragment =
-                (MediaRouteChooserDialogFragment) fm.findFragmentByTag(
-                MediaRouteChooserDialogFragment.FRAGMENT_TAG);
-        if (dialogFragment != null) {
-            Log.w(TAG, "onPerformDefaultAction(): Chooser dialog already showing!");
-            return false;
+        if (mButton != null) {
+            return mButton.showDialogInternal();
         }
-
-        dialogFragment = new MediaRouteChooserDialogFragment();
-        dialogFragment.setExtendedSettingsClickListener(mExtendedSettingsListener);
-        dialogFragment.setRouteTypes(mRouteTypes);
-        dialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG);
-        return true;
-    }
-
-    private Activity getActivity() {
-        // Gross way of unwrapping the Activity so we can get the FragmentManager
-        Context context = mContext;
-        while (context instanceof ContextWrapper && !(context instanceof Activity)) {
-            context = ((ContextWrapper) context).getBaseContext();
-        }
-        if (!(context instanceof Activity)) {
-            throw new IllegalStateException("The MediaRouteActionProvider's Context " +
-                    "is not an Activity.");
-        }
-
-        return (Activity) context;
-    }
-
-    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
-        mExtendedSettingsListener = listener;
-        if (mView != null) {
-            mView.setExtendedSettingsClickListener(listener);
-        }
+        return false;
     }
 
     @Override
@@ -136,36 +143,43 @@
 
     @Override
     public boolean isVisible() {
-        return mRouter.getRouteCount() > 1;
+        return mRouter.isRouteAvailable(mRouteTypes,
+                MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
     }
 
-    private static class RouterCallback extends MediaRouter.SimpleCallback {
-        private WeakReference<MediaRouteActionProvider> mAp;
+    private void refreshRoute() {
+        refreshVisibility();
+    }
 
-        RouterCallback(MediaRouteActionProvider ap) {
-            mAp = new WeakReference<MediaRouteActionProvider>(ap);
+    private static class MediaRouterCallback extends MediaRouter.SimpleCallback {
+        private final WeakReference<MediaRouteActionProvider> mProviderWeak;
+
+        public MediaRouterCallback(MediaRouteActionProvider provider) {
+            mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider);
         }
 
         @Override
         public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            final MediaRouteActionProvider ap = mAp.get();
-            if (ap == null) {
-                router.removeCallback(this);
-                return;
-            }
-
-            ap.refreshVisibility();
+            refreshRoute(router);
         }
 
         @Override
         public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            final MediaRouteActionProvider ap = mAp.get();
-            if (ap == null) {
-                router.removeCallback(this);
-                return;
-            }
+            refreshRoute(router);
+        }
 
-            ap.refreshVisibility();
+        @Override
+        public void onRouteChanged(MediaRouter router, RouteInfo info) {
+            refreshRoute(router);
+        }
+
+        private void refreshRoute(MediaRouter router) {
+            MediaRouteActionProvider provider = mProviderWeak.get();
+            if (provider != null) {
+                provider.refreshRoute();
+            } else {
+                router.removeCallback(this);
+            }
         }
     }
 }
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index 7e0a27a..a7982f4 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -17,7 +17,7 @@
 package android.app;
 
 import com.android.internal.R;
-import com.android.internal.app.MediaRouteChooserDialogFragment;
+import com.android.internal.app.MediaRouteDialogPresenter;
 
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -30,7 +30,6 @@
 import android.media.MediaRouter.RouteInfo;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.SoundEffectConstants;
@@ -38,17 +37,15 @@
 import android.widget.Toast;
 
 public class MediaRouteButton extends View {
-    private static final String TAG = "MediaRouteButton";
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
 
-    private MediaRouter mRouter;
-    private final MediaRouteCallback mRouterCallback = new MediaRouteCallback();
     private int mRouteTypes;
 
     private boolean mAttachedToWindow;
 
     private Drawable mRemoteIndicator;
     private boolean mRemoteActive;
-    private boolean mToggleMode;
     private boolean mCheatSheetEnabled;
     private boolean mIsConnecting;
 
@@ -56,12 +53,13 @@
     private int mMinHeight;
 
     private OnClickListener mExtendedSettingsClickListener;
-    private MediaRouteChooserDialogFragment mDialogFragment;
 
+    // The checked state is used when connected to a remote route.
     private static final int[] CHECKED_STATE_SET = {
         R.attr.state_checked
     };
 
+    // The activated state is used while connecting to a remote route.
     private static final int[] ACTIVATED_STATE_SET = {
         R.attr.state_activated
     };
@@ -78,6 +76,7 @@
         super(context, attrs, defStyleAttr);
 
         mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mCallback = new MediaRouterCallback();
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0);
@@ -98,19 +97,87 @@
         setRouteTypes(routeTypes);
     }
 
-    private void setRemoteIndicatorDrawable(Drawable d) {
-        if (mRemoteIndicator != null) {
-            mRemoteIndicator.setCallback(null);
-            unscheduleDrawable(mRemoteIndicator);
+    /**
+     * Gets the media route types for filtering the routes that the user can
+     * select using the media route chooser dialog.
+     *
+     * @return The route types.
+     */
+    public int getRouteTypes() {
+        return mRouteTypes;
+    }
+
+    /**
+     * Sets the types of routes that will be shown in the media route chooser dialog
+     * launched by this button.
+     *
+     * @param types The route types to match.
+     */
+    public void setRouteTypes(int types) {
+        if (mRouteTypes != types) {
+            if (mAttachedToWindow && mRouteTypes != 0) {
+                mRouter.removeCallback(mCallback);
+            }
+
+            mRouteTypes = types;
+
+            if (mAttachedToWindow && types != 0) {
+                mRouter.addCallback(types, mCallback,
+                        MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+            }
+
+            refreshRoute();
         }
-        mRemoteIndicator = d;
-        if (d != null) {
-            d.setCallback(this);
-            d.setState(getDrawableState());
-            d.setVisible(getVisibility() == VISIBLE, false);
+    }
+
+    public void setExtendedSettingsClickListener(OnClickListener listener) {
+        mExtendedSettingsClickListener = listener;
+    }
+
+    /**
+     * Show the route chooser or controller dialog.
+     * <p>
+     * If the default route is selected or if the currently selected route does
+     * not match the {@link #getRouteTypes route types}, then shows the route chooser dialog.
+     * Otherwise, shows the route controller dialog to offer the user
+     * a choice to disconnect from the route or perform other control actions
+     * such as setting the route's volume.
+     * </p><p>
+     * This will attach a {@link DialogFragment} to the containing Activity.
+     * </p>
+     */
+    public void showDialog() {
+        showDialogInternal();
+    }
+
+    boolean showDialogInternal() {
+        if (!mAttachedToWindow) {
+            return false;
         }
 
-        refreshDrawableState();
+        DialogFragment f = MediaRouteDialogPresenter.showDialogFragment(getActivity(),
+                mRouteTypes, mExtendedSettingsClickListener);
+        return f != null;
+    }
+
+    private Activity getActivity() {
+        // Gross way of unwrapping the Activity so we can get the FragmentManager
+        Context context = getContext();
+        while (context instanceof ContextWrapper) {
+            if (context instanceof Activity) {
+                return (Activity)context;
+            }
+            context = ((ContextWrapper)context).getBaseContext();
+        }
+        throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
+    }
+
+    /**
+     * Sets whether to enable showing a toast with the content descriptor of the
+     * button when the button is long pressed.
+     */
+    void setCheatSheetEnabled(boolean enable) {
+        mCheatSheetEnabled = enable;
     }
 
     @Override
@@ -120,29 +187,7 @@
         if (!handled) {
             playSoundEffect(SoundEffectConstants.CLICK);
         }
-
-        if (mToggleMode) {
-            if (mRemoteActive) {
-                mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute());
-            } else {
-                final int N = mRouter.getRouteCount();
-                for (int i = 0; i < N; i++) {
-                    final RouteInfo route = mRouter.getRouteAt(i);
-                    if ((route.getSupportedTypes() & mRouteTypes) != 0 &&
-                            route != mRouter.getDefaultRoute()) {
-                        mRouter.selectRouteInt(mRouteTypes, route);
-                    }
-                }
-            }
-        } else {
-            showDialog();
-        }
-
-        return handled;
-    }
-
-    void setCheatSheetEnabled(boolean enable) {
-        mCheatSheetEnabled = enable;
+        return showDialogInternal() || handled;
     }
 
     @Override
@@ -183,85 +228,9 @@
         }
         cheatSheet.show();
         performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-
         return true;
     }
 
-    public void setRouteTypes(int types) {
-        if (types == mRouteTypes) {
-            // Already registered; nothing to do.
-            return;
-        }
-
-        if (mAttachedToWindow && mRouteTypes != 0) {
-            mRouter.removeCallback(mRouterCallback);
-        }
-
-        mRouteTypes = types;
-
-        if (mAttachedToWindow) {
-            updateRouteInfo();
-            mRouter.addCallback(types, mRouterCallback);
-        }
-    }
-
-    private void updateRouteInfo() {
-        updateRemoteIndicator();
-        updateRouteCount();
-    }
-
-    public int getRouteTypes() {
-        return mRouteTypes;
-    }
-
-    void updateRemoteIndicator() {
-        final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes);
-        final boolean isRemote = selected != mRouter.getDefaultRoute();
-        final boolean isConnecting = selected != null &&
-                selected.getStatusCode() == RouteInfo.STATUS_CONNECTING;
-
-        boolean needsRefresh = false;
-        if (mRemoteActive != isRemote) {
-            mRemoteActive = isRemote;
-            needsRefresh = true;
-        }
-        if (mIsConnecting != isConnecting) {
-            mIsConnecting = isConnecting;
-            needsRefresh = true;
-        }
-
-        if (needsRefresh) {
-            refreshDrawableState();
-        }
-    }
-
-    void updateRouteCount() {
-        final int N = mRouter.getRouteCount();
-        int count = 0;
-        boolean hasVideoRoutes = false;
-        for (int i = 0; i < N; i++) {
-            final RouteInfo route = mRouter.getRouteAt(i);
-            final int routeTypes = route.getSupportedTypes();
-            if ((routeTypes & mRouteTypes) != 0) {
-                if (route instanceof RouteGroup) {
-                    count += ((RouteGroup) route).getRouteCount();
-                } else {
-                    count++;
-                }
-                if ((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0) {
-                    hasVideoRoutes = true;
-                }
-            }
-        }
-
-        setEnabled(count != 0);
-
-        // Only allow toggling if we have more than just user routes.
-        // Don't toggle if we support video routes, we may have to let the dialog scan.
-        mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 &&
-                !hasVideoRoutes;
-    }
-
     @Override
     protected int[] onCreateDrawableState(int extraSpace) {
         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
@@ -289,6 +258,21 @@
         }
     }
 
+    private void setRemoteIndicatorDrawable(Drawable d) {
+        if (mRemoteIndicator != null) {
+            mRemoteIndicator.setCallback(null);
+            unscheduleDrawable(mRemoteIndicator);
+        }
+        mRemoteIndicator = d;
+        if (d != null) {
+            d.setCallback(this);
+            d.setState(getDrawableState());
+            d.setVisible(getVisibility() == VISIBLE, false);
+        }
+
+        refreshDrawableState();
+    }
+
     @Override
     protected boolean verifyDrawable(Drawable who) {
         return super.verifyDrawable(who) || who == mRemoteIndicator;
@@ -297,12 +281,16 @@
     @Override
     public void jumpDrawablesToCurrentState() {
         super.jumpDrawablesToCurrentState();
-        if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState();
+
+        if (mRemoteIndicator != null) {
+            mRemoteIndicator.jumpToCurrentState();
+        }
     }
 
     @Override
     public void setVisibility(int visibility) {
         super.setVisibility(visibility);
+
         if (mRemoteIndicator != null) {
             mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false);
         }
@@ -311,19 +299,22 @@
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
+
         mAttachedToWindow = true;
         if (mRouteTypes != 0) {
-            mRouter.addCallback(mRouteTypes, mRouterCallback);
-            updateRouteInfo();
+            mRouter.addCallback(mRouteTypes, mCallback,
+                    MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
         }
+        refreshRoute();
     }
 
     @Override
     public void onDetachedFromWindow() {
-        if (mRouteTypes != 0) {
-            mRouter.removeCallback(mRouterCallback);
-        }
         mAttachedToWindow = false;
+        if (mRouteTypes != 0) {
+            mRouter.removeCallback(mCallback);
+        }
+
         super.onDetachedFromWindow();
     }
 
@@ -386,93 +377,71 @@
         final int drawLeft = left + (right - left - drawWidth) / 2;
         final int drawTop = top + (bottom - top - drawHeight) / 2;
 
-        mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight);
+        mRemoteIndicator.setBounds(drawLeft, drawTop,
+                drawLeft + drawWidth, drawTop + drawHeight);
         mRemoteIndicator.draw(canvas);
     }
 
-    public void setExtendedSettingsClickListener(OnClickListener listener) {
-        mExtendedSettingsClickListener = listener;
-        if (mDialogFragment != null) {
-            mDialogFragment.setExtendedSettingsClickListener(listener);
-        }
-    }
+    private void refreshRoute() {
+        if (mAttachedToWindow) {
+            final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
+            final boolean isRemote = !route.isDefault() && route.matchesTypes(mRouteTypes);
+            final boolean isConnecting = isRemote && route.isConnecting();
 
-    /**
-     * Asynchronously show the route chooser dialog.
-     * This will attach a {@link DialogFragment} to the containing Activity.
-     */
-    public void showDialog() {
-        final FragmentManager fm = getActivity().getFragmentManager();
-        if (mDialogFragment == null) {
-            // See if one is already attached to this activity.
-            mDialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag(
-                    MediaRouteChooserDialogFragment.FRAGMENT_TAG);
-        }
-        if (mDialogFragment != null) {
-            Log.w(TAG, "showDialog(): Already showing!");
-            return;
-        }
-
-        mDialogFragment = new MediaRouteChooserDialogFragment();
-        mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener);
-        mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() {
-            @Override
-            public void onDetached(MediaRouteChooserDialogFragment detachedFragment) {
-                mDialogFragment = null;
+            boolean needsRefresh = false;
+            if (mRemoteActive != isRemote) {
+                mRemoteActive = isRemote;
+                needsRefresh = true;
             }
-        });
-        mDialogFragment.setRouteTypes(mRouteTypes);
-        mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG);
+            if (mIsConnecting != isConnecting) {
+                mIsConnecting = isConnecting;
+                needsRefresh = true;
+            }
+
+            if (needsRefresh) {
+                refreshDrawableState();
+            }
+
+            setEnabled(mRouter.isRouteAvailable(mRouteTypes,
+                    MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
+        }
     }
 
-    private Activity getActivity() {
-        // Gross way of unwrapping the Activity so we can get the FragmentManager
-        Context context = getContext();
-        while (context instanceof ContextWrapper && !(context instanceof Activity)) {
-            context = ((ContextWrapper) context).getBaseContext();
-        }
-        if (!(context instanceof Activity)) {
-            throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
-        }
-
-        return (Activity) context;
-    }
-
-    private class MediaRouteCallback extends MediaRouter.SimpleCallback {
-        @Override
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
-            updateRemoteIndicator();
-        }
-
-        @Override
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
-            updateRemoteIndicator();
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo info) {
-            updateRemoteIndicator();
-        }
-
+    private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
         @Override
         public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            updateRouteCount();
+            refreshRoute();
         }
 
         @Override
         public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            updateRouteCount();
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, RouteInfo info) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+            refreshRoute();
         }
 
         @Override
         public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
                 int index) {
-            updateRouteCount();
+            refreshRoute();
         }
 
         @Override
         public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
-            updateRouteCount();
+            refreshRoute();
         }
     }
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c63e586..cce6fc4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1778,7 +1778,7 @@
             RemoteViews button = new RemoteViews(mContext.getPackageName(),
                     tombstone ? R.layout.notification_action_tombstone
                               : R.layout.notification_action);
-            button.setTextViewCompoundDrawables(R.id.action0, action.icon, 0, 0, 0);
+            button.setTextViewCompoundDrawablesRelative(R.id.action0, action.icon, 0, 0, 0);
             button.setTextViewText(R.id.action0, action.title);
             if (!tombstone) {
                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 45467b8..7129e9e 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -95,8 +95,8 @@
      */
     public static final int FLAG_ONE_SHOT = 1<<30;
     /**
-     * Flag indicating that if the described PendingIntent already
-     * exists, then simply return null instead of creating it.
+     * Flag indicating that if the described PendingIntent does not
+     * already exist, then simply return null instead of creating it.
      * For use with {@link #getActivity}, {@link #getBroadcast}, and
      * {@link #getService}.
      */
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 7bcf43e..2045ed8 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -58,10 +58,7 @@
             | DISABLE_SYSTEM_INFO | DISABLE_RECENT | DISABLE_HOME | DISABLE_BACK | DISABLE_CLOCK
             | DISABLE_SEARCH;
 
-    public static final int NAVIGATION_HINT_BACK_NOP      = 1 << 0;
-    public static final int NAVIGATION_HINT_HOME_NOP      = 1 << 1;
-    public static final int NAVIGATION_HINT_RECENT_NOP    = 1 << 2;
-    public static final int NAVIGATION_HINT_BACK_ALT      = 1 << 3;
+    public static final int NAVIGATION_HINT_BACK_ALT      = 1 << 0;
 
     public static final int WINDOW_STATUS_BAR = 1;
     public static final int WINDOW_NAVIGATION_BAR = 2;
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 607930c..91b0d7c 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -146,7 +146,9 @@
     @Override
     public void shutdown() {
         synchronized (mLock) {
-            throwIfCalledByNotTrustedUidLocked();
+            if (isConnectedLocked()) {
+                throwIfCalledByNotTrustedUidLocked();
+            }
             throwIfShutdownLocked();
             mIsShutdown = true;
             if (isConnectedLocked()) {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ced72f8..f291e82 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -45,9 +45,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.util.DisplayMetrics;
 import android.util.Log;
-import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
 import java.io.BufferedInputStream;
@@ -294,9 +292,8 @@
 
                     try {
                         BitmapFactory.Options options = new BitmapFactory.Options();
-                        Bitmap bm = BitmapFactory.decodeFileDescriptor(
+                        return BitmapFactory.decodeFileDescriptor(
                                 fd.getFileDescriptor(), null, options);
-                        return generateBitmap(context, bm, width, height);
                     } catch (OutOfMemoryError e) {
                         Log.w(TAG, "Can't decode file", e);
                     } finally {
@@ -323,8 +320,7 @@
 
                     try {
                         BitmapFactory.Options options = new BitmapFactory.Options();
-                        Bitmap bm = BitmapFactory.decodeStream(is, null, options);
-                        return generateBitmap(context, bm, width, height);
+                        return BitmapFactory.decodeStream(is, null, options);
                     } catch (OutOfMemoryError e) {
                         Log.w(TAG, "Can't decode stream", e);
                     } finally {
@@ -1029,62 +1025,4 @@
     public void clear() throws IOException {
         setResource(com.android.internal.R.drawable.default_wallpaper);
     }
-    
-    static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) {
-        if (bm == null) {
-            return null;
-        }
-
-        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
-        DisplayMetrics metrics = new DisplayMetrics();
-        wm.getDefaultDisplay().getMetrics(metrics);
-        bm.setDensity(metrics.noncompatDensityDpi);
-
-        if (width <= 0 || height <= 0
-                || (bm.getWidth() == width && bm.getHeight() == height)) {
-            return bm;
-        }
-
-        // This is the final bitmap we want to return.
-        try {
-            Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            newbm.setDensity(metrics.noncompatDensityDpi);
-
-            Canvas c = new Canvas(newbm);
-            Rect targetRect = new Rect();
-            targetRect.right = bm.getWidth();
-            targetRect.bottom = bm.getHeight();
-
-            int deltaw = width - targetRect.right;
-            int deltah = height - targetRect.bottom;
-
-            if (deltaw > 0 || deltah > 0) {
-                // We need to scale up so it covers the entire area.
-                float scale;
-                if (deltaw > deltah) {
-                    scale = width / (float)targetRect.right;
-                } else {
-                    scale = height / (float)targetRect.bottom;
-                }
-                targetRect.right = (int)(targetRect.right*scale);
-                targetRect.bottom = (int)(targetRect.bottom*scale);
-                deltaw = width - targetRect.right;
-                deltah = height - targetRect.bottom;
-            }
-
-            targetRect.offset(deltaw/2, deltah/2);
-
-            Paint paint = new Paint();
-            paint.setFilterBitmap(true);
-            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
-            c.drawBitmap(bm, null, targetRect, paint);
-
-            bm.recycle();
-            c.setBitmap(null);
-            return newbm;
-        } catch (OutOfMemoryError e) {
-            Log.w(TAG, "Can't generate default bitmap", e);
-            return bm;
-        }
-    }
 }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index bb4f5f1..12ee3b6 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -43,14 +43,14 @@
     void dataChanged(String packageName);
 
     /**
-     * Erase all backed-up data for the given package from the storage
+     * Erase all backed-up data for the given package from the given storage
      * destination.
      *
      * Any application can invoke this method for its own package, but
      * only callers who hold the android.permission.BACKUP permission
      * may invoke it for arbitrary packages.
      */
-    void clearBackupData(String packageName);
+    void clearBackupData(String transportName, String packageName);
 
     /**
      * Notifies the Backup Manager Service that an agent has become available.  This
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index e2bc80a..75b007c 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -20,21 +20,23 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
 import android.util.Pair;
+
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.HashMap;
-import java.util.LinkedList;
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
@@ -214,6 +216,22 @@
             "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
 
     /**
+     * Broadcast Action: Indicate BLE Advertising is started.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_BLUETOOTH_ADVERTISING_STARTED =
+            "android.bluetooth.adapter.action.ADVERTISING_STARTED";
+
+    /**
+     * Broadcast Action: Indicated BLE Advertising is stopped.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_BLUETOOTH_ADVERTISING_STOPPED =
+            "android.bluetooth.adapter.action.ADVERTISING_STOPPED";
+
+    /**
      * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED}
      * intents to request the current scan mode. Possible values are:
      * {@link #SCAN_MODE_NONE},
@@ -251,7 +269,6 @@
      */
     public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23;
 
-
     /**
      * Broadcast Action: The local Bluetooth adapter has started the remote
      * device discovery process.
@@ -350,9 +367,27 @@
     /** The profile is in disconnecting state */
     public static final int STATE_DISCONNECTING = 3;
 
+    /** States for Bluetooth LE advertising */
+    /** @hide */
+    public static final int STATE_ADVERTISE_STARTING = 0;
+    /** @hide */
+    public static final int STATE_ADVERTISE_STARTED = 1;
+    /** @hide */
+    public static final int STATE_ADVERTISE_STOPPING = 2;
+    /** @hide */
+    public static final int STATE_ADVERTISE_STOPPED = 3;
+    /**
+     * Force stopping advertising without callback in case the advertising app dies.
+     * @hide
+     */
+    public static final int STATE_ADVERTISE_FORCE_STOPPING = 4;
+
     /** @hide */
     public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
 
+    /** @hide */
+    public static final int ADVERTISE_CALLBACK_SUCCESS = 0;
+
     private static final int ADDRESS_LENGTH = 17;
 
     /**
@@ -365,6 +400,10 @@
     private IBluetooth mService;
 
     private final Map<LeScanCallback, GattCallbackWrapper> mLeScanClients;
+    private BluetoothAdvScanData mBluetoothAdvScanData = null;
+    private GattCallbackWrapper mAdvertisingGattCallback;
+    private final Handler mHandler;  // Handler to post the advertise callback to run on main thread.
+    private final Object mLock = new Object();
 
     /**
      * Get a handle to the default local Bluetooth adapter.
@@ -400,6 +439,7 @@
         } catch (RemoteException e) {Log.e(TAG, "", e);}
         mManagerService = managerService;
         mLeScanClients = new HashMap<LeScanCallback, GattCallbackWrapper>();
+        mHandler = new Handler(Looper.getMainLooper());
     }
 
     /**
@@ -438,6 +478,129 @@
     }
 
     /**
+     * Returns a {@link BluetoothAdvScanData} object representing advertising data.
+     * Data will be reset when bluetooth service is turned off.
+     * @hide
+     */
+    public BluetoothAdvScanData getAdvScanData() {
+      try {
+          IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+          if (iGatt == null) {
+              // BLE is not supported
+              Log.e(TAG, "failed to start, iGatt null");
+              return null;
+          }
+          if (mBluetoothAdvScanData == null) {
+              mBluetoothAdvScanData = new BluetoothAdvScanData(iGatt, BluetoothAdvScanData.AD);
+          }
+          return mBluetoothAdvScanData;
+      } catch (RemoteException e) {
+          Log.e(TAG, "failed to get advScanData, error: " + e);
+          return null;
+      }
+    }
+
+    /**
+     * Interface for BLE advertising callback.
+     *
+     * @hide
+     */
+    public interface AdvertiseCallback {
+        /**
+         * Callback when advertise starts.
+         * @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure.
+         */
+        void onAdvertiseStart(int status);
+        /**
+         * Callback when advertise stops.
+         * @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure.
+         */
+        void onAdvertiseStop(int status);
+    }
+
+    /**
+     * Start BLE advertising using current {@link BluetoothAdvScanData}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
+     *
+     * @param callback - {@link AdvertiseCallback}
+     * @return true if BLE advertising succeeds, false otherwise.
+     * @hide
+     */
+    public boolean startAdvertising(final AdvertiseCallback callback) {
+        if (getState() != STATE_ON) return false;
+        try {
+            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+            if (iGatt == null) {
+                // BLE is not supported.
+                return false;
+            }
+            // Restart/reset advertising packets if advertising is in progress.
+            if (isAdvertising()) {
+                // Invalid advertising callback.
+                if (mAdvertisingGattCallback == null || mAdvertisingGattCallback.mLeHandle == -1) {
+                    Log.e(TAG, "failed to restart advertising, invalid callback");
+                    return false;
+                }
+                iGatt.startAdvertising(mAdvertisingGattCallback.mLeHandle);
+                // Run the callback from main thread.
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        // callback with status success.
+                        callback.onAdvertiseStart(ADVERTISE_CALLBACK_SUCCESS);
+                    }
+                });
+                return true;
+            }
+            UUID uuid = UUID.randomUUID();
+            GattCallbackWrapper wrapper =
+                new GattCallbackWrapper(this, null, null, callback);
+            iGatt.registerClient(new ParcelUuid(uuid), wrapper);
+            if (!wrapper.advertiseStarted()) {
+                return false;
+            }
+            synchronized (mLock) {
+                mAdvertisingGattCallback = wrapper;
+            }
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
+     * Stop BLE advertising.
+     *
+     * @param callback - {@link AdvertiseCallback}
+     * @return true if BLE advertising stops, false otherwise.
+     * @hide
+     */
+    public boolean stopAdvertising(AdvertiseCallback callback) {
+        try {
+            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+            if (iGatt == null) {
+                // BLE is not supported
+                return false;
+            }
+            if (mAdvertisingGattCallback == null) {
+                // no callback.
+                return false;
+            }
+            // Make sure same callback is used for start and stop advertising.
+            if (callback != mAdvertisingGattCallback.mAdvertiseCallback) {
+                Log.e(TAG, "must use the same callback for star/stop advertising");
+                return false;
+            }
+            mAdvertisingGattCallback.stopAdvertising();
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
      * Return true if Bluetooth is currently enabled and ready for use.
      * <p>Equivalent to:
      * <code>getBluetoothState() == STATE_ON</code>
@@ -849,6 +1012,23 @@
     }
 
     /**
+     * Returns whether BLE is currently advertising.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+     *
+     * @hide
+     */
+    public boolean isAdvertising() {
+        if (getState() != STATE_ON) return false;
+        try {
+            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+            return iGatt.isAdvertising();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
      * Return the set of {@link BluetoothDevice} objects that are bonded
      * (paired) to the local adapter.
      * <p>If Bluetooth state is not {@link #STATE_ON}, this API
@@ -1272,6 +1452,8 @@
                 if (VDBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
                 synchronized (mManagerCallback) {
                     mService = null;
+                    // Reset bluetooth adv scan data when Gatt service is down.
+                    mBluetoothAdvScanData = null;
                     for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
                         try {
                             if (cb != null) {
@@ -1547,7 +1729,9 @@
         private static final int LE_CALLBACK_REG_TIMEOUT = 2000;
         private static final int LE_CALLBACK_REG_WAIT_COUNT = 5;
 
+        private final AdvertiseCallback mAdvertiseCallback;
         private final LeScanCallback mLeScanCb;
+
         // mLeHandle 0: not registered
         //           -1: scan stopped
         //           >0: registered and scan started
@@ -1561,16 +1745,34 @@
             mLeScanCb = leScanCb;
             mScanFilter = uuid;
             mLeHandle = 0;
+            mAdvertiseCallback = null;
+        }
+
+        public GattCallbackWrapper(BluetoothAdapter bluetoothAdapter, LeScanCallback leScanCb,
+            UUID[] uuid, AdvertiseCallback callback) {
+          mBluetoothAdapter = new WeakReference<BluetoothAdapter>(bluetoothAdapter);
+          mLeScanCb = leScanCb;
+          mScanFilter = uuid;
+          mLeHandle = 0;
+          mAdvertiseCallback = callback;
         }
 
         public boolean scanStarted() {
+            return waitForRegisteration(LE_CALLBACK_REG_WAIT_COUNT);
+        }
+
+        public boolean advertiseStarted() {
+            // Wait for registeration callback.
+            return waitForRegisteration(1);
+        }
+
+        private boolean waitForRegisteration(int maxWaitCount) {
             boolean started = false;
             synchronized(this) {
                 if (mLeHandle == -1) return false;
-
                 int count = 0;
                 // wait for callback registration and LE scan to start
-                while (mLeHandle == 0 && count < LE_CALLBACK_REG_WAIT_COUNT) {
+                while (mLeHandle == 0 && count < maxWaitCount) {
                     try {
                         wait(LE_CALLBACK_REG_TIMEOUT);
                     } catch (InterruptedException e) {
@@ -1583,6 +1785,27 @@
             return started;
         }
 
+        public void stopAdvertising() {
+            synchronized (this) {
+                if (mLeHandle <= 0) {
+                    Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
+                    return;
+                }
+                BluetoothAdapter adapter = mBluetoothAdapter.get();
+                if (adapter != null) {
+                    try {
+                        IBluetoothGatt iGatt = adapter.getBluetoothManager().getBluetoothGatt();
+                        iGatt.stopAdvertising();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Failed to stop advertising" + e);
+                    }
+                } else {
+                    Log.e(TAG, "stopAdvertising, BluetoothAdapter is null");
+                }
+                notifyAll();
+            }
+        }
+
         public void stopLeScan() {
             synchronized(this) {
                 if (mLeHandle <= 0) {
@@ -1624,14 +1847,18 @@
                         BluetoothAdapter adapter = mBluetoothAdapter.get();
                         if (adapter != null) {
                             iGatt = adapter.getBluetoothManager().getBluetoothGatt();
-                            if (mScanFilter == null) {
-                                iGatt.startScan(mLeHandle, false);
+                            if (mAdvertiseCallback != null) {
+                                iGatt.startAdvertising(mLeHandle);
                             } else {
-                                ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
-                                for(int i = 0; i != uuids.length; ++i) {
-                                    uuids[i] = new ParcelUuid(mScanFilter[i]);
-                                }
-                                iGatt.startScanWithUuids(mLeHandle, false, uuids);
+                              if (mScanFilter == null) {
+                                  iGatt.startScan(mLeHandle, false);
+                              } else {
+                                  ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
+                                  for(int i = 0; i != uuids.length; ++i) {
+                                      uuids[i] = new ParcelUuid(mScanFilter[i]);
+                                  }
+                                  iGatt.startScanWithUuids(mLeHandle, false, uuids);
+                              }
                             }
                         } else {
                             Log.e(TAG, "onClientRegistered, BluetoothAdapter null");
@@ -1642,7 +1869,7 @@
                         mLeHandle = -1;
                     }
                     if (mLeHandle == -1) {
-                        // registration succeeded but start scan failed
+                        // registration succeeded but start scan or advertise failed
                         if (iGatt != null) {
                             try {
                                 iGatt.unregisterClient(mLeHandle);
@@ -1670,7 +1897,7 @@
          * @hide
          */
         public void onScanResult(String address, int rssi, byte[] advData) {
-            if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
+            if (VDBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
 
             // Check null in case the scan has been stopped
             synchronized(this) {
@@ -1759,9 +1986,33 @@
             // no op
         }
 
-        public void onListen(int status) {
-            // no op
+        public void onAdvertiseStateChange(int advertiseState, int status) {
+            Log.d(TAG, "on advertise call back, state: " + advertiseState + " status: " + status);
+            if (advertiseState == STATE_ADVERTISE_STARTED) {
+                mAdvertiseCallback.onAdvertiseStart(status);
+            } else {
+                synchronized (this) {
+                    if (status == ADVERTISE_CALLBACK_SUCCESS) {
+                        BluetoothAdapter adapter = mBluetoothAdapter.get();
+                        if (adapter != null) {
+                            try {
+                                IBluetoothGatt iGatt =
+                                        adapter.getBluetoothManager().getBluetoothGatt();
+                                Log.d(TAG, "unregistering client " + mLeHandle);
+                                iGatt.unregisterClient(mLeHandle);
+                                // Reset advertise app handle.
+                                mLeHandle = -1;
+                                adapter.mAdvertisingGattCallback = null;
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Failed to unregister client" + e);
+                            }
+                        } else {
+                            Log.e(TAG, "cannot unregister client, BluetoothAdapter is null");
+                        }
+                    }
+                }
+                mAdvertiseCallback.onAdvertiseStop(status);
+            }
         }
     }
-
 }
diff --git a/core/java/android/bluetooth/BluetoothAdvScanData.java b/core/java/android/bluetooth/BluetoothAdvScanData.java
new file mode 100644
index 0000000..df2c256
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAdvScanData.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * This class provides the public APIs to set advertising and scan response data when BLE device
+ * operates in peripheral mode. <br>
+ * The exact format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11
+ * @hide
+ */
+public final class BluetoothAdvScanData {
+
+  /**
+   * Available data types of {@link BluetoothAdvScanData}.
+   */
+  public static final int AD = 0;  // Advertising Data
+  public static final int SCAN_RESPONSE = 1;  // Scan Response
+  public static final int EIR = 2;  // Extended Inquiry Response
+
+  private static final String TAG = "BluetoothAdvScanData";
+
+  /**
+   * Data type of BluetoothAdvScanData.
+   */
+  private final int mDataType;
+  /**
+   * Bluetooth Gatt Service.
+   */
+  private IBluetoothGatt mBluetoothGatt;
+
+  /**
+   * @param mBluetoothGatt
+   * @param dataType
+   */
+  public BluetoothAdvScanData(IBluetoothGatt mBluetoothGatt, int dataType) {
+    this.mBluetoothGatt = mBluetoothGatt;
+    this.mDataType = dataType;
+  }
+
+  /**
+   * @return advertising data type.
+   */
+  public int getDataType() {
+    return mDataType;
+  }
+
+  /**
+   * Set manufactureCode and manufactureData.
+   * Returns true if manufacturer data is set, false if there is no enough room to set
+   * manufacturer data or the data is already set.
+   * @param manufacturerCode - unique identifier for the manufacturer
+   * @param manufacturerData - data associated with the specific manufacturer.
+   */
+  public boolean setManufacturerData(int manufacturerCode, byte[] manufacturerData) {
+    if (mDataType != AD) return false;
+    try {
+      return mBluetoothGatt.setAdvManufacturerCodeAndData(manufacturerCode, manufacturerData);
+    } catch (RemoteException e) {
+      Log.e(TAG, "Unable to set manufacturer id and data.", e);
+      return false;
+    }
+  }
+
+  /**
+   * Set service data.  Note the service data can only be set when the data type is {@code AD};
+   * @param serviceData
+   */
+  public boolean setServiceData(byte[] serviceData) {
+
+    if (mDataType != AD) return false;
+    if (serviceData == null) return false;
+    try {
+      return mBluetoothGatt.setAdvServiceData(serviceData);
+    } catch (RemoteException e) {
+      Log.e(TAG, "Unable to set service data.", e);
+      return false;
+    }
+  }
+
+  /**
+   * Returns an immutable list of service uuids that will be advertised.
+   */
+  public List<ParcelUuid> getServiceUuids() {
+    try {
+      return Collections.unmodifiableList(mBluetoothGatt.getAdvServiceUuids());
+    } catch (RemoteException e) {
+      Log.e(TAG, "Unable to get service uuids.", e);
+      return null;
+    }
+  }
+
+  /**
+   * Returns manufacturer data.
+   */
+  public byte[] getManufacturerData() {
+    if (mBluetoothGatt == null) return null;
+    try {
+      return mBluetoothGatt.getAdvManufacturerData();
+    } catch (RemoteException e) {
+      Log.e(TAG, "Unable to get manufacturer data.", e);
+      return null;
+    }
+  }
+
+  /**
+   * Returns service data.
+   */
+  public byte[] getServiceData() {
+    if (mBluetoothGatt == null) return null;
+    try {
+      return mBluetoothGatt.getAdvServiceData();
+    } catch (RemoteException e) {
+      Log.e(TAG, "Unable to get service data.", e);
+      return null;
+    }
+  }
+
+  /**
+   * Remove manufacturer data based on given manufacturer code.
+   * @param manufacturerCode
+   */
+  public void removeManufacturerCodeAndData(int manufacturerCode) {
+    if (mBluetoothGatt != null) {
+      try {
+        mBluetoothGatt.removeAdvManufacturerCodeAndData(manufacturerCode);
+      } catch (RemoteException e) {
+        Log.e(TAG, "Unable to remove manufacturer : " + manufacturerCode, e);
+      }
+    }
+  }
+}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index a2bb78c..ae6ad3b 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -555,11 +555,12 @@
             }
 
             /**
-             * Listen command status callback
+             * Advertise state change callback
              * @hide
              */
-            public void onListen(int status) {
-                if (DBG) Log.d(TAG, "onListen() - status=" + status);
+            public void onAdvertiseStateChange(int state, int status) {
+                if (DBG) Log.d(TAG, "onAdvertiseStateChange() - state = "
+                        + state + " status=" + status);
             }
         };
 
@@ -693,71 +694,6 @@
         return true;
     }
 
-   /**
-     * Starts or stops sending of advertisement packages to listen for connection
-     * requests from a central devices.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
-     * @param start Start or stop advertising
-     */
-    /*package*/ void listen(boolean start) {
-        if (mContext == null || !mContext.getResources().
-            getBoolean(com.android.internal.R.bool.config_bluetooth_le_peripheral_mode_supported)) {
-            throw new UnsupportedOperationException("BluetoothGatt#listen is blocked");
-        }
-        if (DBG) Log.d(TAG, "listen() - start: " + start);
-        if (mService == null || mClientIf == 0) return;
-
-        try {
-            mService.clientListen(mClientIf, start);
-        } catch (RemoteException e) {
-            Log.e(TAG,"",e);
-        }
-    }
-
-    /**
-     * Sets the advertising data contained in the adv. response packet.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
-     * @param advData true to set adv. data, false to set scan response
-     * @param includeName Inlucde the name in the adv. response
-     * @param includeTxPower Include TX power value
-     * @param minInterval Minimum desired scan interval (optional)
-     * @param maxInterval Maximum desired scan interval (optional)
-     * @param appearance The appearance flags for the device (optional)
-     * @param manufacturerData Manufacturer specific data including company ID (optional)
-     */
-    /*package*/ void setAdvData(boolean advData, boolean includeName, boolean includeTxPower,
-                           Integer minInterval, Integer maxInterval,
-                           Integer appearance, Byte[] manufacturerData) {
-        if (mContext == null || !mContext.getResources().
-            getBoolean(com.android.internal.R.bool.config_bluetooth_le_peripheral_mode_supported)) {
-            throw new UnsupportedOperationException("BluetoothGatt#setAdvData is blocked");
-        }
-        if (DBG) Log.d(TAG, "setAdvData()");
-        if (mService == null || mClientIf == 0) return;
-
-        byte[] data = new byte[0];
-        if (manufacturerData != null) {
-            data = new byte[manufacturerData.length];
-            for(int i = 0; i != manufacturerData.length; ++i) {
-                data[i] = manufacturerData[i];
-            }
-        }
-
-        try {
-            mService.setAdvData(mClientIf, !advData,
-                includeName, includeTxPower,
-                minInterval != null ? minInterval : 0,
-                maxInterval != null ? maxInterval : 0,
-                appearance != null ? appearance : 0, data);
-        } catch (RemoteException e) {
-            Log.e(TAG,"",e);
-        }
-    }
-
     /**
      * Disconnects an established connection, or cancels a connection attempt
      * currently in progress.
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 58ee54f..153215c 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -537,7 +537,7 @@
         try {
             mService.beginServiceDeclaration(mServerIf, service.getType(),
                 service.getInstanceId(), service.getHandles(),
-                new ParcelUuid(service.getUuid()));
+                new ParcelUuid(service.getUuid()), service.isAdvertisePreferred());
 
             List<BluetoothGattService> includedServices = service.getIncludedServices();
             for (BluetoothGattService includedService : includedServices) {
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
index 1e66369..52bc0f7 100644
--- a/core/java/android/bluetooth/BluetoothGattService.java
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -15,8 +15,6 @@
  */
 package android.bluetooth;
 
-import android.bluetooth.BluetoothDevice;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
@@ -82,6 +80,11 @@
     protected List<BluetoothGattService> mIncludedServices;
 
     /**
+     * Whether the service uuid should be advertised.
+     */
+    private boolean mAdvertisePreferred;
+
+    /**
      * Create a new BluetoothGattService.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
@@ -263,4 +266,20 @@
         }
         return null;
     }
+
+    /**
+     * Returns whether the uuid of the service should be advertised.
+     * @hide
+     */
+    public boolean isAdvertisePreferred() {
+      return mAdvertisePreferred;
+    }
+
+    /**
+     * Set whether the service uuid should be advertised.
+     * @hide
+     */
+    public void setAdvertisePreferred(boolean advertisePreferred) {
+      this.mAdvertisePreferred = advertisePreferred;
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index abdf949..4b28516 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -73,6 +73,9 @@
     public static final ParcelUuid MAS =
             ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
 
+    public static final ParcelUuid BASE_UUID =
+            ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
+
 
     public static final ParcelUuid[] RESERVED_UUIDS = {
         AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
@@ -211,4 +214,17 @@
         long value = (uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32;
         return (int)value;
     }
+
+    /**
+     * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid.
+     * @param parcelUuid
+     * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise.
+     */
+    public static boolean isShortUuid(ParcelUuid parcelUuid) {
+      UUID uuid = parcelUuid.getUuid();
+      if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
+        return false;
+      }
+      return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L);
+    }
 }
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index df393db..784cdcc 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -37,10 +37,15 @@
     void unregisterClient(in int clientIf);
     void clientConnect(in int clientIf, in String address, in boolean isDirect);
     void clientDisconnect(in int clientIf, in String address);
-    void clientListen(in int clientIf, in boolean start);
-    void setAdvData(in int clientIf, in boolean setScanRsp, in boolean inclName,
-                            in boolean inclTxPower, in int minInterval, in int maxInterval,
-                            in int appearance, in byte[] manufacturerData);
+    void startAdvertising(in int appIf);
+    void stopAdvertising();
+    boolean setAdvServiceData(in byte[] serviceData);
+    byte[] getAdvServiceData();
+    boolean setAdvManufacturerCodeAndData(int manufactureCode, in byte[] manufacturerData);
+    byte[] getAdvManufacturerData();
+    List<ParcelUuid> getAdvServiceUuids();
+    void removeAdvManufacturerCodeAndData(int manufacturerCode);
+    boolean isAdvertising();
     void refreshDevice(in int clientIf, in String address);
     void discoverServices(in int clientIf, in String address);
     void readCharacteristic(in int clientIf, in String address, in int srvcType,
@@ -75,7 +80,7 @@
     void serverDisconnect(in int serverIf, in String address);
     void beginServiceDeclaration(in int serverIf, in int srvcType,
                             in int srvcInstanceId, in int minHandles,
-                            in ParcelUuid srvcId);
+                            in ParcelUuid srvcId, boolean advertisePreferred);
     void addIncludedService(in int serverIf, in int srvcType,
                             in int srvcInstanceId, in ParcelUuid srvcId);
     void addCharacteristic(in int serverIf, in ParcelUuid charId,
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index 60c297b..7c69a06 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -63,5 +63,5 @@
                              in int charInstId, in ParcelUuid charUuid,
                              in byte[] value);
     void onReadRemoteRssi(in String address, in int rssi, in int status);
-    void onListen(in int status);
+    oneway void onAdvertiseStateChange(in int advertiseState, in int status);
 }
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index b061fc5..9d0ab3a 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -398,67 +398,6 @@
             return AppOpsManager.MODE_ALLOWED;
         }
 
-        private void enforceReadPermissionInner(Uri uri) throws SecurityException {
-            final Context context = getContext();
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            String missingPerm = null;
-
-            if (UserHandle.isSameApp(uid, mMyUid)) {
-                return;
-            }
-
-            if (mExported) {
-                final String componentPerm = getReadPermission();
-                if (componentPerm != null) {
-                    if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) {
-                        return;
-                    } else {
-                        missingPerm = componentPerm;
-                    }
-                }
-
-                // track if unprotected read is allowed; any denied
-                // <path-permission> below removes this ability
-                boolean allowDefaultRead = (componentPerm == null);
-
-                final PathPermission[] pps = getPathPermissions();
-                if (pps != null) {
-                    final String path = uri.getPath();
-                    for (PathPermission pp : pps) {
-                        final String pathPerm = pp.getReadPermission();
-                        if (pathPerm != null && pp.match(path)) {
-                            if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) {
-                                return;
-                            } else {
-                                // any denied <path-permission> means we lose
-                                // default <provider> access.
-                                allowDefaultRead = false;
-                                missingPerm = pathPerm;
-                            }
-                        }
-                    }
-                }
-
-                // if we passed <path-permission> checks above, and no default
-                // <provider> permission, then allow access.
-                if (allowDefaultRead) return;
-            }
-
-            // last chance, check against any uri grants
-            if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
-                    == PERMISSION_GRANTED) {
-                return;
-            }
-
-            final String failReason = mExported
-                    ? " requires " + missingPerm + ", or grantUriPermission()"
-                    : " requires the provider be exported, or grantUriPermission()";
-            throw new SecurityException("Permission Denial: reading "
-                    + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
-                    + ", uid=" + uid + failReason);
-        }
-
         private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException {
             enforceWritePermissionInner(uri);
             if (mWriteOp != AppOpsManager.OP_NONE) {
@@ -466,67 +405,130 @@
             }
             return AppOpsManager.MODE_ALLOWED;
         }
+    }
 
-        private void enforceWritePermissionInner(Uri uri) throws SecurityException {
-            final Context context = getContext();
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            String missingPerm = null;
+    /** {@hide} */
+    protected void enforceReadPermissionInner(Uri uri) throws SecurityException {
+        final Context context = getContext();
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        String missingPerm = null;
 
-            if (UserHandle.isSameApp(uid, mMyUid)) {
-                return;
+        if (UserHandle.isSameApp(uid, mMyUid)) {
+            return;
+        }
+
+        if (mExported) {
+            final String componentPerm = getReadPermission();
+            if (componentPerm != null) {
+                if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) {
+                    return;
+                } else {
+                    missingPerm = componentPerm;
+                }
             }
 
-            if (mExported) {
-                final String componentPerm = getWritePermission();
-                if (componentPerm != null) {
-                    if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) {
-                        return;
-                    } else {
-                        missingPerm = componentPerm;
-                    }
-                }
+            // track if unprotected read is allowed; any denied
+            // <path-permission> below removes this ability
+            boolean allowDefaultRead = (componentPerm == null);
 
-                // track if unprotected write is allowed; any denied
-                // <path-permission> below removes this ability
-                boolean allowDefaultWrite = (componentPerm == null);
-
-                final PathPermission[] pps = getPathPermissions();
-                if (pps != null) {
-                    final String path = uri.getPath();
-                    for (PathPermission pp : pps) {
-                        final String pathPerm = pp.getWritePermission();
-                        if (pathPerm != null && pp.match(path)) {
-                            if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) {
-                                return;
-                            } else {
-                                // any denied <path-permission> means we lose
-                                // default <provider> access.
-                                allowDefaultWrite = false;
-                                missingPerm = pathPerm;
-                            }
+            final PathPermission[] pps = getPathPermissions();
+            if (pps != null) {
+                final String path = uri.getPath();
+                for (PathPermission pp : pps) {
+                    final String pathPerm = pp.getReadPermission();
+                    if (pathPerm != null && pp.match(path)) {
+                        if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) {
+                            return;
+                        } else {
+                            // any denied <path-permission> means we lose
+                            // default <provider> access.
+                            allowDefaultRead = false;
+                            missingPerm = pathPerm;
                         }
                     }
                 }
-
-                // if we passed <path-permission> checks above, and no default
-                // <provider> permission, then allow access.
-                if (allowDefaultWrite) return;
             }
 
-            // last chance, check against any uri grants
-            if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
-                    == PERMISSION_GRANTED) {
-                return;
-            }
-
-            final String failReason = mExported
-                    ? " requires " + missingPerm + ", or grantUriPermission()"
-                    : " requires the provider be exported, or grantUriPermission()";
-            throw new SecurityException("Permission Denial: writing "
-                    + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
-                    + ", uid=" + uid + failReason);
+            // if we passed <path-permission> checks above, and no default
+            // <provider> permission, then allow access.
+            if (allowDefaultRead) return;
         }
+
+        // last chance, check against any uri grants
+        if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                == PERMISSION_GRANTED) {
+            return;
+        }
+
+        final String failReason = mExported
+                ? " requires " + missingPerm + ", or grantUriPermission()"
+                : " requires the provider be exported, or grantUriPermission()";
+        throw new SecurityException("Permission Denial: reading "
+                + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
+                + ", uid=" + uid + failReason);
+    }
+
+    /** {@hide} */
+    protected void enforceWritePermissionInner(Uri uri) throws SecurityException {
+        final Context context = getContext();
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        String missingPerm = null;
+
+        if (UserHandle.isSameApp(uid, mMyUid)) {
+            return;
+        }
+
+        if (mExported) {
+            final String componentPerm = getWritePermission();
+            if (componentPerm != null) {
+                if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) {
+                    return;
+                } else {
+                    missingPerm = componentPerm;
+                }
+            }
+
+            // track if unprotected write is allowed; any denied
+            // <path-permission> below removes this ability
+            boolean allowDefaultWrite = (componentPerm == null);
+
+            final PathPermission[] pps = getPathPermissions();
+            if (pps != null) {
+                final String path = uri.getPath();
+                for (PathPermission pp : pps) {
+                    final String pathPerm = pp.getWritePermission();
+                    if (pathPerm != null && pp.match(path)) {
+                        if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) {
+                            return;
+                        } else {
+                            // any denied <path-permission> means we lose
+                            // default <provider> access.
+                            allowDefaultWrite = false;
+                            missingPerm = pathPerm;
+                        }
+                    }
+                }
+            }
+
+            // if we passed <path-permission> checks above, and no default
+            // <provider> permission, then allow access.
+            if (allowDefaultWrite) return;
+        }
+
+        // last chance, check against any uri grants
+        if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+                == PERMISSION_GRANTED) {
+            return;
+        }
+
+        final String failReason = mExported
+                ? " requires " + missingPerm + ", or grantUriPermission()"
+                : " requires the provider be exported, or grantUriPermission()";
+        throw new SecurityException("Permission Denial: writing "
+                + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
+                + ", uid=" + uid + failReason);
     }
 
     /**
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index 0284882..cffc653 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -55,6 +55,14 @@
     }
 
     /** @hide */
+    public SyncInfo(SyncInfo other) {
+        this.authorityId = other.authorityId;
+        this.account = new Account(other.account.name, other.account.type);
+        this.authority = other.authority;
+        this.startTime = other.startTime;
+    }
+
+    /** @hide */
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 267fb2a..20002ad 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -53,6 +53,7 @@
  *  {@hide}
  */
 interface IPackageManager {
+    boolean isPackageAvailable(String packageName, int userId);
     PackageInfo getPackageInfo(String packageName, int flags, int userId);
     int getPackageUid(String packageName, int userId);
     int[] getPackageGids(String packageName);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 17d13e5..e6da288 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -282,6 +282,10 @@
                 || (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
     }
 
+    public static boolean isAvailable(PackageUserState state) {
+        return checkUseInstalledOrBlocked(0, state);
+    }
+
     public static PackageInfo generatePackageInfo(PackageParser.Package p,
             int gids[], int flags, long firstInstallTime, long lastUpdateTime,
             HashSet<String> grantedPermissions, PackageUserState state, int userId) {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index feb47aa..5178901 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -45,6 +45,7 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -2150,10 +2151,20 @@
         private static final String PIXEL_FORMAT_JPEG = "jpeg";
         private static final String PIXEL_FORMAT_BAYER_RGGB = "bayer-rggb";
 
-        private HashMap<String, String> mMap;
+        /**
+         * Order matters: Keys that are {@link #set(String, String) set} later
+         * will take precedence over keys that are set earlier (if the two keys
+         * conflict with each other).
+         *
+         * <p>One example is {@link #setPreviewFpsRange(int, int)} , since it
+         * conflicts with {@link #setPreviewFrameRate(int)} whichever key is set later
+         * is the one that will take precedence.
+         * </p>
+         */
+        private final LinkedHashMap<String, String> mMap;
 
         private Parameters() {
-            mMap = new HashMap<String, String>(64);
+            mMap = new LinkedHashMap<String, String>(/*initialCapacity*/64);
         }
 
         /**
@@ -2233,7 +2244,7 @@
                 return;
             }
 
-            mMap.put(key, value);
+            put(key, value);
         }
 
         /**
@@ -2243,7 +2254,18 @@
          * @param value the int value of the parameter
          */
         public void set(String key, int value) {
-            mMap.put(key, Integer.toString(value));
+            put(key, Integer.toString(value));
+        }
+
+        private void put(String key, String value) {
+            /*
+             * Remove the key if it already exists.
+             *
+             * This way setting a new value for an already existing key will always move
+             * that key to be ordered the latest in the map.
+             */
+            mMap.remove(key);
+            mMap.put(key, value);
         }
 
         private void set(String key, List<Area> areas) {
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 4fe2c4d..a38beec 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -334,6 +334,27 @@
 
     /**
      * <p>
+     * If set to 1, the HAL will always split result
+     * metadata for a single capture into multiple buffers,
+     * returned using multiple process_capture_result calls.
+     * </p>
+     * <p>
+     * Does not need to be listed in static
+     * metadata. Support for partial results will be reworked in
+     * future versions of camera service. This quirk will stop
+     * working at that point; DO NOT USE without careful
+     * consideration of future support.
+     * </p>
+     *
+     * <b>Optional</b> - This value may be null on some devices.
+     *
+     * @hide
+     */
+    public static final Key<Byte> QUIRKS_USE_PARTIAL_RESULT =
+            new Key<Byte>("android.quirks.usePartialResult", byte.class);
+
+    /**
+     * <p>
      * How many output streams can be allocated at
      * the same time for each type of stream
      * </p>
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 7095e4d..9e8d7d1 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -631,6 +631,36 @@
         }
 
         /**
+         * This method is called when some results from an image capture are
+         * available.
+         *
+         * <p>The result provided here will contain some subset of the fields of
+         * a full result. Multiple onCapturePartial calls may happen per
+         * capture; a given result field will only be present in one partial
+         * capture at most. The final onCaptureCompleted call will always
+         * contain all the fields, whether onCapturePartial was called or
+         * not.</p>
+         *
+         * <p>The default implementation of this method does nothing.</p>
+         *
+         * @param camera The CameraDevice sending the callback.
+         * @param request The request that was given to the CameraDevice
+         * @param result The partial output metadata from the capture, which
+         * includes a subset of the CaptureResult fields.
+         *
+         * @see #capture
+         * @see #captureBurst
+         * @see #setRepeatingRequest
+         * @see #setRepeatingBurst
+         *
+         * @hide
+         */
+        public void onCapturePartial(CameraDevice camera,
+                CaptureRequest request, CaptureResult result) {
+            // default empty implementation
+        }
+
+        /**
          * This method is called when an image capture has completed and the
          * result metadata is available.
          *
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index dbd0457..535b963 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -591,6 +591,32 @@
 
     /**
      * <p>
+     * Whether a result given to the framework is the
+     * final one for the capture, or only a partial that contains a
+     * subset of the full set of dynamic metadata
+     * values.
+     * </p>
+     * <p>
+     * The entries in the result metadata buffers for a
+     * single capture may not overlap, except for this entry. The
+     * FINAL buffers must retain FIFO ordering relative to the
+     * requests that generate them, so the FINAL buffer for frame 3 must
+     * always be sent to the framework after the FINAL buffer for frame 2, and
+     * before the FINAL buffer for frame 4. PARTIAL buffers may be returned
+     * in any order relative to other frames, but all PARTIAL buffers for a given
+     * capture must arrive before the FINAL buffer for that capture. This entry may
+     * only be used by the HAL if quirks.usePartialResult is set to 1.
+     * </p>
+     *
+     * <b>Optional</b> - This value may be null on some devices.
+     *
+     * @hide
+     */
+    public static final Key<Boolean> QUIRKS_PARTIAL_RESULT =
+            new Key<Boolean>("android.quirks.partialResult", boolean.class);
+
+    /**
+     * <p>
      * A frame counter set by the framework. This value monotonically
      * increases with every new result (that is, each new result has a unique
      * frameCount value).
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index c5d0999..40586f0 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -19,27 +19,24 @@
 import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
 
 import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.hardware.camera2.utils.CameraRuntimeException;
-import android.os.IBinder;
-import android.os.RemoteException;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Surface;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Stack;
 
 /**
  * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
@@ -49,6 +46,8 @@
     private final String TAG;
     private final boolean DEBUG;
 
+    private static final int REQUEST_ID_NONE = -1;
+
     // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
     private ICameraDeviceUser mRemoteDevice;
 
@@ -63,7 +62,8 @@
     private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
             new SparseArray<CaptureListenerHolder>();
 
-    private final Stack<Integer> mRepeatingRequestIdStack = new Stack<Integer>();
+    private int mRepeatingRequestId = REQUEST_ID_NONE;
+    private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>();
     // Map stream IDs to Surfaces
     private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>();
 
@@ -186,7 +186,7 @@
             stopRepeating();
 
             try {
-                mRemoteDevice.waitUntilIdle();
+                waitUntilIdle();
 
                 // TODO: mRemoteDevice.beginConfigure
                 // Delete all streams first (to free up HW resources)
@@ -279,6 +279,10 @@
             checkIfCameraClosed();
             int requestId;
 
+            if (repeating) {
+                stopRepeating();
+            }
+
             try {
                 requestId = mRemoteDevice.submitRequest(request, repeating);
             } catch (CameraRuntimeException e) {
@@ -293,7 +297,7 @@
             }
 
             if (repeating) {
-                mRepeatingRequestIdStack.add(requestId);
+                mRepeatingRequestId = requestId;
             }
 
             if (mIdle) {
@@ -327,8 +331,13 @@
 
         synchronized (mLock) {
             checkIfCameraClosed();
-            while (!mRepeatingRequestIdStack.isEmpty()) {
-                int requestId = mRepeatingRequestIdStack.pop();
+            if (mRepeatingRequestId != REQUEST_ID_NONE) {
+
+                int requestId = mRepeatingRequestId;
+                mRepeatingRequestId = REQUEST_ID_NONE;
+
+                // Queue for deletion after in-flight requests finish
+                mRepeatingRequestIdDeletedList.add(requestId);
 
                 try {
                     mRemoteDevice.cancelRequest(requestId);
@@ -347,7 +356,7 @@
 
         synchronized (mLock) {
             checkIfCameraClosed();
-            if (!mRepeatingRequestIdStack.isEmpty()) {
+            if (mRepeatingRequestId != REQUEST_ID_NONE) {
                 throw new IllegalStateException("Active repeating request ongoing");
             }
 
@@ -359,6 +368,10 @@
                 // impossible
                 return;
             }
+
+            mRepeatingRequestId = REQUEST_ID_NONE;
+            mRepeatingRequestIdDeletedList.clear();
+            mCaptureListenerMap.clear();
         }
     }
 
@@ -564,6 +577,9 @@
             }
             final CaptureListenerHolder holder;
 
+            Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
+            boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
+
             synchronized (mLock) {
                 // TODO: move this whole map into this class to make it more testable,
                 //        exposing the methods necessary like subscribeToRequest, unsubscribe..
@@ -572,13 +588,28 @@
                 holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
 
                 // Clean up listener once we no longer expect to see it.
-
-                // TODO: how to handle repeating listeners?
-                // we probably want cancelRequest to return # of times it already enqueued and
-                // keep a counter.
-                if (holder != null && !holder.isRepeating()) {
+                if (holder != null && !holder.isRepeating() && !quirkIsPartialResult) {
                     CameraDevice.this.mCaptureListenerMap.remove(requestId);
                 }
+
+                // TODO: add 'capture sequence completed' callback to the
+                // service, and clean up repeating requests there instead.
+
+                // If we received a result for a repeating request and have
+                // prior repeating requests queued for deletion, remove those
+                // requests from mCaptureListenerMap.
+                if (holder != null && holder.isRepeating() && !quirkIsPartialResult
+                        && mRepeatingRequestIdDeletedList.size() > 0) {
+                    Iterator<Integer> iter = mRepeatingRequestIdDeletedList.iterator();
+                    while (iter.hasNext()) {
+                        int deletedRequestId = iter.next();
+                        if (deletedRequestId < requestId) {
+                            CameraDevice.this.mCaptureListenerMap.remove(deletedRequestId);
+                            iter.remove();
+                        }
+                    }
+                }
+
             }
 
             // Check if we have a listener for this
@@ -591,8 +622,25 @@
             final CaptureRequest request = holder.getRequest();
             final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
 
-            holder.getHandler().post(
-                new Runnable() {
+            Runnable resultDispatch = null;
+
+            // Either send a partial result or the final capture completed result
+            if (quirkIsPartialResult) {
+                // Partial result
+                resultDispatch = new Runnable() {
+                    @Override
+                    public void run() {
+                        if (!CameraDevice.this.isClosed()){
+                            holder.getListener().onCapturePartial(
+                                CameraDevice.this,
+                                request,
+                                resultAsCapture);
+                        }
+                    }
+                };
+            } else {
+                // Final capture result
+                resultDispatch = new Runnable() {
                     @Override
                     public void run() {
                         if (!CameraDevice.this.isClosed()){
@@ -602,7 +650,10 @@
                                 resultAsCapture);
                         }
                     }
-                });
+                };
+            }
+
+            holder.getHandler().post(resultDispatch);
         }
 
     }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index f12be5f..093e0e9 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -297,12 +297,31 @@
     }
 
     /**
-     * Initiates a fresh scan of availble Wifi displays.
+     * Starts scanning for available Wifi displays.
      * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
+     * <p>
+     * Calls to this method nest and must be matched by an equal number of calls to
+     * {@link #stopWifiDisplayScan()}.
+     * </p><p>
+     * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}.
+     * </p>
+     *
      * @hide
      */
-    public void scanWifiDisplays() {
-        mGlobal.scanWifiDisplays();
+    public void startWifiDisplayScan() {
+        mGlobal.startWifiDisplayScan();
+    }
+
+    /**
+     * Stops scanning for available Wifi displays.
+     * <p>
+     * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}.
+     * </p>
+     *
+     * @hide
+     */
+    public void stopWifiDisplayScan() {
+        mGlobal.stopWifiDisplayScan();
     }
 
     /**
@@ -312,8 +331,7 @@
      * Automatically remembers the display after a successful connection, if not
      * already remembered.
      * </p><p>
-     * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY} to connect
-     * to unknown displays.  No permissions are required to connect to already known displays.
+     * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}.
      * </p>
      *
      * @param deviceAddress The MAC address of the device to which we should connect.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 936a086..3417430 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -72,6 +72,8 @@
     private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
     private int[] mDisplayIdCache;
 
+    private int mWifiDisplayScanNestCount;
+
     private DisplayManagerGlobal(IDisplayManager dm) {
         mDm = dm;
     }
@@ -267,11 +269,32 @@
         }
     }
 
-    public void scanWifiDisplays() {
-        try {
-            mDm.scanWifiDisplays();
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Failed to scan for Wifi displays.", ex);
+    public void startWifiDisplayScan() {
+        synchronized (mLock) {
+            if (mWifiDisplayScanNestCount++ == 0) {
+                registerCallbackIfNeededLocked();
+                try {
+                    mDm.startWifiDisplayScan();
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Failed to scan for Wifi displays.", ex);
+                }
+            }
+        }
+    }
+
+    public void stopWifiDisplayScan() {
+        synchronized (mLock) {
+            if (--mWifiDisplayScanNestCount == 0) {
+                try {
+                    mDm.stopWifiDisplayScan();
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Failed to scan for Wifi displays.", ex);
+                }
+            } else if (mWifiDisplayScanNestCount < 0) {
+                Log.wtf(TAG, "Wifi display scan nest count became negative: "
+                        + mWifiDisplayScanNestCount);
+                mWifiDisplayScanNestCount = 0;
+            }
         }
     }
 
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 6b2c887..68eb13f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -29,11 +29,14 @@
 
     void registerCallback(in IDisplayManagerCallback callback);
 
-    // No permissions required.
-    void scanWifiDisplays();
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
+    // The process must have previously registered a callback.
+    void startWifiDisplayScan();
 
-    // Requires CONFIGURE_WIFI_DISPLAY permission to connect to an unknown device.
-    // No permissions required to connect to a known device.
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
+    void stopWifiDisplayScan();
+
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
     void connectWifiDisplay(String address);
 
     // No permissions required.
@@ -45,6 +48,12 @@
     // Requires CONFIGURE_WIFI_DISPLAY permission.
     void forgetWifiDisplay(String address);
 
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
+    void pauseWifiDisplay();
+
+    // Requires CONFIGURE_WIFI_DISPLAY permission.
+    void resumeWifiDisplay();
+
     // No permissions required.
     WifiDisplayStatus getWifiDisplayStatus();
 
@@ -55,10 +64,4 @@
 
     // No permissions required but must be same Uid as the creator.
     void releaseVirtualDisplay(in IBinder token);
-
-    // Requires CONFIGURE_WIFI_DISPLAY permission.
-    void pauseWifiDisplay();
-
-    // Requires CONFIGURE_WIFI_DISPLAY permission.
-    void resumeWifiDisplay();
 }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c78a973..70c8750 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -395,6 +395,8 @@
 
     private final IConnectivityManager mService;
 
+    private final String mPackageName;
+
     /**
      * Tests if a given integer represents a valid network type.
      * @param networkType the type to be tested
@@ -802,7 +804,7 @@
     public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
         byte[] address = hostAddress.getAddress();
         try {
-            return mService.requestRouteToHostAddress(networkType, address);
+            return mService.requestRouteToHostAddress(networkType, address, mPackageName);
         } catch (RemoteException e) {
             return false;
         }
@@ -898,8 +900,9 @@
     /**
      * {@hide}
      */
-    public ConnectivityManager(IConnectivityManager service) {
+    public ConnectivityManager(IConnectivityManager service, String packageName) {
         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 c1da2e3..4bca7fe 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -71,9 +71,9 @@
 
     int stopUsingNetworkFeature(int networkType, in String feature);
 
-    boolean requestRouteToHost(int networkType, int hostAddress);
+    boolean requestRouteToHost(int networkType, int hostAddress, String packageName);
 
-    boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
+    boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, String packageName);
 
     boolean getMobileDataEnabled();
     void setMobileDataEnabled(boolean enabled);
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index b2ee50a..119e533 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -326,6 +326,7 @@
         }
 
         s.fd = accept(fd, s);
+        s.mFdCreatedInternally = true;
     }
 
     /**
@@ -536,4 +537,3 @@
         close();
     }
 }
-
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index c106514..21352bf 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -566,6 +566,17 @@
         return false;
     }
 
+
+    public void setInternalDataEnable(boolean enabled) {
+        if (DBG) log("setInternalDataEnable: E enabled=" + enabled);
+        final AsyncChannel channel = mDataConnectionTrackerAc;
+        if (channel != null) {
+            channel.sendMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE,
+                    enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
+        }
+        if (VDBG) log("setInternalDataEnable: X enabled=" + enabled);
+    }
+
     @Override
     public void setUserDataEnable(boolean enabled) {
         if (DBG) log("setUserDataEnable: E enabled=" + enabled);
diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java
index b674324..8a2c2b6 100644
--- a/core/java/android/net/PacProxySelector.java
+++ b/core/java/android/net/PacProxySelector.java
@@ -97,7 +97,7 @@
                 } catch (Exception e) {
                     port = 8080;
                 }
-                ret.add(new Proxy(Type.HTTP, new InetSocketAddress(host, port)));
+                ret.add(new Proxy(Type.HTTP, InetSocketAddress.createUnresolved(host, port)));
             }
         }
         if (ret.size() == 0) {
diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java
index 78ac75f..010e527 100644
--- a/core/java/android/net/ProxyProperties.java
+++ b/core/java/android/net/ProxyProperties.java
@@ -139,6 +139,17 @@
         return false;
     }
 
+    public boolean isValid() {
+        if (!TextUtils.isEmpty(mPacFileUrl)) return true;
+        try {
+            Proxy.validate(mHost == null ? "" : mHost, mPort == 0 ? "" : Integer.toString(mPort),
+                    mExclusionList == null ? "" : mExclusionList);
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+        return true;
+    }
+
     public java.net.Proxy makeProxy() {
         java.net.Proxy proxy = java.net.Proxy.NO_PROXY;
         if (mHost != null) {
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index d7dc7f5..7385dff 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -151,9 +151,10 @@
     }
 
     /**
-     * Protect a socket from VPN connections. The socket will be bound to the
-     * current default network interface, so its traffic will not be forwarded
-     * through VPN. This method is useful if some connections need to be kept
+     * Protect a socket from VPN connections. After protecting, data sent
+     * through this socket will go directly to the underlying network,
+     * so its traffic will not be forwarded through the VPN.
+     * This method is useful if some connections need to be kept
      * outside of VPN. For example, a VPN tunnel should protect itself if its
      * destination is covered by VPN routes. Otherwise its outgoing packets
      * will be sent back to the VPN interface and cause an infinite loop. This
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 9c3e405..7b2c623 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -301,27 +301,36 @@
 
         @Override
         public void handleMessage(Message message) {
-            Object listener = getListener(message.arg2);
-            boolean listenerRemove = true;
             switch (message.what) {
                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                     mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-                    break;
+                    return;
                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
                     mConnected.countDown();
-                    break;
+                    return;
                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
                     Log.e(TAG, "Channel lost");
+                    return;
+                default:
                     break;
+            }
+            Object listener = getListener(message.arg2);
+            if (listener == null) {
+                Log.d(TAG, "Stale key " + message.arg2);
+                return;
+            }
+            boolean listenerRemove = true;
+            NsdServiceInfo ns = getNsdService(message.arg2);
+            switch (message.what) {
                 case DISCOVER_SERVICES_STARTED:
-                    String s = ((NsdServiceInfo) message.obj).getServiceType();
+                    String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
                     ((DiscoveryListener) listener).onDiscoveryStarted(s);
                     // Keep listener until stop discovery
                     listenerRemove = false;
                     break;
                 case DISCOVER_SERVICES_FAILED:
-                    ((DiscoveryListener) listener).onStartDiscoveryFailed(
-                            getNsdService(message.arg2).getServiceType(), message.arg1);
+                    ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns),
+                            message.arg1);
                     break;
                 case SERVICE_FOUND:
                     ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj);
@@ -334,16 +343,14 @@
                     listenerRemove = false;
                     break;
                 case STOP_DISCOVERY_FAILED:
-                    ((DiscoveryListener) listener).onStopDiscoveryFailed(
-                            getNsdService(message.arg2).getServiceType(), message.arg1);
+                    ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
+                            message.arg1);
                     break;
                 case STOP_DISCOVERY_SUCCEEDED:
-                    ((DiscoveryListener) listener).onDiscoveryStopped(
-                            getNsdService(message.arg2).getServiceType());
+                    ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns));
                     break;
                 case REGISTER_SERVICE_FAILED:
-                    ((RegistrationListener) listener).onRegistrationFailed(
-                            getNsdService(message.arg2), message.arg1);
+                    ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1);
                     break;
                 case REGISTER_SERVICE_SUCCEEDED:
                     ((RegistrationListener) listener).onServiceRegistered(
@@ -352,16 +359,13 @@
                     listenerRemove = false;
                     break;
                 case UNREGISTER_SERVICE_FAILED:
-                    ((RegistrationListener) listener).onUnregistrationFailed(
-                            getNsdService(message.arg2), message.arg1);
+                    ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
                     break;
                 case UNREGISTER_SERVICE_SUCCEEDED:
-                    ((RegistrationListener) listener).onServiceUnregistered(
-                            getNsdService(message.arg2));
+                    ((RegistrationListener) listener).onServiceUnregistered(ns);
                     break;
                 case RESOLVE_SERVICE_FAILED:
-                    ((ResolveListener) listener).onResolveFailed(
-                            getNsdService(message.arg2), message.arg1);
+                    ((ResolveListener) listener).onResolveFailed(ns, message.arg1);
                     break;
                 case RESOLVE_SERVICE_SUCCEEDED:
                     ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
@@ -421,6 +425,11 @@
     }
 
 
+    private String getNsdServiceInfoType(NsdServiceInfo s) {
+        if (s == null) return "?";
+        return s.getServiceType();
+    }
+
     /**
      * Initialize AsyncChannel
      */
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index dbaa325..9ada6e6 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -106,6 +106,11 @@
     public static final int FOREGROUND_ACTIVITY = 10;
 
     /**
+     * A constant indicating a wifi batched scan is active
+     */
+    public static final int WIFI_BATCHED_SCAN = 11;
+
+    /**
      * Include all of the data in the stats, including previously saved data.
      */
     public static final int STATS_SINCE_CHARGED = 0;
@@ -270,6 +275,8 @@
         public abstract void noteFullWifiLockReleasedLocked();
         public abstract void noteWifiScanStartedLocked();
         public abstract void noteWifiScanStoppedLocked();
+        public abstract void noteWifiBatchedScanStartedLocked(int csph);
+        public abstract void noteWifiBatchedScanStoppedLocked();
         public abstract void noteWifiMulticastEnabledLocked();
         public abstract void noteWifiMulticastDisabledLocked();
         public abstract void noteAudioTurnedOnLocked();
@@ -281,6 +288,7 @@
         public abstract long getWifiRunningTime(long batteryRealtime, int which);
         public abstract long getFullWifiLockTime(long batteryRealtime, int which);
         public abstract long getWifiScanTime(long batteryRealtime, int which);
+        public abstract long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which);
         public abstract long getWifiMulticastTime(long batteryRealtime,
                                                   int which);
         public abstract long getAudioTurnedOnTime(long batteryRealtime, int which);
@@ -288,6 +296,8 @@
         public abstract Timer getForegroundActivityTimer();
         public abstract Timer getVibratorOnTimer();
 
+        public static final int NUM_WIFI_BATCHED_SCAN_BINS = 5;
+
         /**
          * Note that these must match the constants in android.os.PowerManager.
          * Also, if the user activity types change, the BatteryStatsImpl.VERSION must
@@ -844,12 +854,13 @@
     public static final int DATA_CONNECTION_EVDO_B = 12;
     public static final int DATA_CONNECTION_LTE = 13;
     public static final int DATA_CONNECTION_EHRPD = 14;
-    public static final int DATA_CONNECTION_OTHER = 15;
+    public static final int DATA_CONNECTION_HSPAP = 15;
+    public static final int DATA_CONNECTION_OTHER = 16;
 
     static final String[] DATA_CONNECTION_NAMES = {
         "none", "gprs", "edge", "umts", "cdma", "evdo_0", "evdo_A",
         "1xrtt", "hsdpa", "hsupa", "hspa", "iden", "evdo_b", "lte",
-        "ehrpd", "other"
+        "ehrpd", "hspap", "other"
     };
     
     public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER+1;
@@ -2080,9 +2091,11 @@
                                         TimeUtils.formatDuration(ew.usedTime, pw);
                                         pw.print(" over ");
                                         TimeUtils.formatDuration(ew.overTime, pw);
-                                        pw.print(" (");
-                                        pw.print((ew.usedTime*100)/ew.overTime);
-                                        pw.println("%)");
+                                        if (ew.overTime != 0) {
+                                            pw.print(" (");
+                                            pw.print((ew.usedTime*100)/ew.overTime);
+                                            pw.println("%)");
+                                        }
                             }
                         }
                         uidActivity = true;
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 21b8ae5..f65b6ba 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -420,7 +420,7 @@
     /**
     * Clear a user range from being associated with an interface.
     */
-    void clearDnsInterfaceForUidRange(int uid_start, int uid_end);
+    void clearDnsInterfaceForUidRange(String iface, int uid_start, int uid_end);
 
     /**
     * Clear the mappings from pid to Dns interface and from uid range to Dns interface.
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 37a8102..f7d1eb7 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -1069,11 +1069,11 @@
      * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>;
      *          greater than 0 if this Preference sorts after <var>another</var>.
      */
+    @Override
     public int compareTo(Preference another) {
-        if (mOrder != DEFAULT_ORDER
-                || (mOrder == DEFAULT_ORDER && another.mOrder != DEFAULT_ORDER)) {
+        if (mOrder != another.mOrder) {
             // Do order comparison
-            return mOrder - another.mOrder; 
+            return mOrder - another.mOrder;
         } else if (mTitle == another.mTitle) {
             // If titles are null or share same object comparison
             return 0;
diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl
index 9d384fb..2b95c12 100644
--- a/core/java/android/print/IPrintDocumentAdapter.aidl
+++ b/core/java/android/print/IPrintDocumentAdapter.aidl
@@ -37,4 +37,5 @@
     void write(in PageRange[] pages, in ParcelFileDescriptor fd,
             IWriteResultCallback callback, int sequence);
     void finish();
+    void cancel();
 }
diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java
index 9e811a6..1f59bef 100644
--- a/core/java/android/print/PrintDocumentAdapter.java
+++ b/core/java/android/print/PrintDocumentAdapter.java
@@ -141,15 +141,36 @@
      * or {@link LayoutResultCallback#onLayoutCancelled()} if layout was
      * cancelled in a response to a cancellation request via the passed in
      * {@link CancellationSignal}. Note that you <strong>must</strong> call one of
-     * the methods of the given callback for this method to be considered complete.
+     * the methods of the given callback for this method to be considered complete
+     * which is you will not receive any calls to this adapter until the current
+     * layout operation is complete by invoking a method on the callback instance.
+     * The callback methods can be invoked from an arbitrary thread.
      * </p>
      * <p>
+     * One of the arguments passed to this method is a {@link CancellationSignal}
+     * which is used to propagate requests from the system to your application for
+     * canceling the current layout operation. For example, a cancellation may be
+     * requested if the user changes a print option that may affect layout while
+     * you are performing a layout operation. In such a case the system will make
+     * an attempt to cancel the current layout as another one will have to be performed.
+     * Typically, you should register a cancellation callback in the cancellation
+     * signal. The cancellation callback <strong>will not</strong> be made on the
+     * main thread and can be registered as follows:
+     * </p>
+     * <pre>
+     * cancellationSignal.setOnCancelListener(new OnCancelListener() {
+     *     &#064;Override
+     *     public void onCancel() {
+     *         // Cancel layout
+     *     }
+     * });
+     * </pre>
+     * <p>
      * <strong>Note:</strong> If the content is large and a layout will be
      * performed, it is a good practice to schedule the work on a dedicated
      * thread and register an observer in the provided {@link
      * CancellationSignal} upon invocation of which you should stop the
-     * layout. The cancellation callback <strong>will not</strong> be made on
-     * the main thread.
+     * layout.
      * </p>
      *
      * @param oldAttributes The old print attributes.
@@ -177,14 +198,36 @@
      * CharSequence)}, if an error occurred; or {@link WriteResultCallback#onWriteCancelled()},
      * if writing was cancelled in a response to a cancellation request via the passed
      * in {@link CancellationSignal}. Note that you <strong>must</strong> call one of
-     * the methods of the given callback for this method to be considered complete.
+     * the methods of the given callback for this method to be considered complete which
+     * is you will not receive any calls to this adapter until the current write
+     * operation is complete by invoking a method on the callback instance. The callback
+     * methods can be invoked from an arbitrary thread.
      * </p>
      * <p>
+     * One of the arguments passed to this method is a {@link CancellationSignal}
+     * which is used to propagate requests from the system to your application for
+     * canceling the current write operation. For example, a cancellation may be
+     * requested if the user changes a print option that may affect layout while
+     * you are performing a write operation. In such a case the system will make
+     * an attempt to cancel the current write as a layout will have to be performed
+     * which then may be followed by a write. Typically, you should register a
+     * cancellation callback in the cancellation signal. The cancellation callback
+     * <strong>will not</strong> be made on the main thread and can be registered
+     * as follows:
+     * </p>
+     * <pre>
+     * cancellationSignal.setOnCancelListener(new OnCancelListener() {
+     *     &#064;Override
+     *     public void onCancel() {
+     *         // Cancel write
+     *     }
+     * });
+     * </pre>
+     * <p>
      * <strong>Note:</strong> If the printed content is large, it is a good
      * practice to schedule writing it on a dedicated thread and register an
      * observer in the provided {@link CancellationSignal} upon invocation of
-     * which you should stop writing. The cancellation callback will not be
-     * made on the main thread.
+     * which you should stop writing.
      * </p>
      *
      * @param pages The pages whose content to print - non-overlapping in ascending order.
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index bbfc307..d1bb8fd 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -616,6 +616,18 @@
         }
 
         @Override
+        public void cancel() {
+            // Start not called or finish called or destroyed - nothing to do.
+            if (!mStartReqeusted || mFinishRequested || mDestroyed) {
+                return;
+            }
+            // Request cancellation of pending work if needed.
+            synchronized (mLock) {
+                cancelPreviousCancellableOperationLocked();
+            }
+        }
+
+        @Override
         public void onActivityPaused(Activity activity) {
             /* do nothing */
         }
@@ -848,6 +860,11 @@
                 }
                 final ILayoutResultCallback callback;
                 synchronized (mLock) {
+                    if (mDestroyed) {
+                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                                + "finish the printing activity before print completion?");
+                        return;
+                    }
                     callback = mCallback;
                     clearLocked();
                 }
@@ -864,6 +881,11 @@
             public void onLayoutFailed(CharSequence error) {
                 final ILayoutResultCallback callback;
                 synchronized (mLock) {
+                    if (mDestroyed) {
+                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                                + "finish the printing activity before print completion?");
+                        return;
+                    }
                     callback = mCallback;
                     clearLocked();
                 }
@@ -879,6 +901,11 @@
             @Override
             public void onLayoutCancelled() {
                 synchronized (mLock) {
+                    if (mDestroyed) {
+                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                                + "finish the printing activity before print completion?");
+                        return;
+                    }
                     clearLocked();
                 }
             }
@@ -906,6 +933,11 @@
             public void onWriteFinished(PageRange[] pages) {
                 final IWriteResultCallback callback;
                 synchronized (mLock) {
+                    if (mDestroyed) {
+                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                                + "finish the printing activity before print completion?");
+                        return;
+                    }
                     callback = mCallback;
                     clearLocked();
                 }
@@ -928,6 +960,11 @@
             public void onWriteFailed(CharSequence error) {
                 final IWriteResultCallback callback;
                 synchronized (mLock) {
+                    if (mDestroyed) {
+                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                                + "finish the printing activity before print completion?");
+                        return;
+                    }
                     callback = mCallback;
                     clearLocked();
                 }
@@ -943,6 +980,11 @@
             @Override
             public void onWriteCancelled() {
                 synchronized (mLock) {
+                    if (mDestroyed) {
+                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                                + "finish the printing activity before print completion?");
+                        return;
+                    }
                     clearLocked();
                 }
             }
diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java
index b615600..806a89d 100644
--- a/core/java/android/print/PrinterCapabilitiesInfo.java
+++ b/core/java/android/print/PrinterCapabilitiesInfo.java
@@ -475,6 +475,12 @@
          * @param colorModes The color mode bit mask.
          * @param defaultColorMode The default color mode.
          * @return This builder.
+         * <p>
+         * <strong>Note:</strong> On platform version 19 (Kitkat) specifying
+         * only PrintAttributes#COLOR_MODE_MONOCHROME leads to a print spooler
+         * crash. Hence, you should declare either both color modes or
+         * PrintAttributes#COLOR_MODE_COLOR.
+         * </p>
          *
          * @throws IllegalArgumentException If color modes contains an invalid
          *         mode bit or if the default color mode is invalid.
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index b16df28..0863368 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -18,6 +18,7 @@
 
 import android.accounts.Account;
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
 import android.content.ContentProviderClient;
 import android.content.ContentProviderOperation;
 import android.content.ContentResolver;
@@ -40,6 +41,7 @@
 import android.util.DisplayMetrics;
 import android.util.Pair;
 import android.view.View;
+import android.widget.Toast;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -7981,7 +7983,7 @@
             // Trigger with obtained rectangle
             Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
                     excludeMimes);
-            context.startActivity(intent);
+            startActivityWithErrorToast(context, intent);
         }
 
         /**
@@ -8014,7 +8016,16 @@
                 String[] excludeMimes) {
             Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
                     excludeMimes);
-            context.startActivity(intent);
+            startActivityWithErrorToast(context, intent);
+        }
+
+        private static void startActivityWithErrorToast(Context context, Intent intent) {
+            try {
+              context.startActivity(intent);
+            } catch (ActivityNotFoundException e) {
+                Toast.makeText(context, com.android.internal.R.string.quick_contacts_not_available,
+                                Toast.LENGTH_SHORT).show();
+            }
         }
     }
 
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index e35b8eb..49816f8 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -62,7 +62,8 @@
  *            android:authorities="com.example.mycloudprovider"
  *            android:exported="true"
  *            android:grantUriPermissions="true"
- *            android:permission="android.permission.MANAGE_DOCUMENTS"&gt;
+ *            android:permission="android.permission.MANAGE_DOCUMENTS"
+ *            android:enabled="@bool/isAtLeastKitKat"&gt;
  *            &lt;intent-filter&gt;
  *                &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
  *            &lt;/intent-filter&gt;
@@ -216,6 +217,8 @@
      * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
      * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
      * limited to only return the 64 most recently modified documents.
+     * <p>
+     * Recent documents do not support change notifications.
      *
      * @param projection list of {@link Document} columns to put into the
      *            cursor. If {@code null} all supported columns should be
@@ -250,7 +253,8 @@
      * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
      * you are still fetching additional data. Then, when the network data is
      * available, you can send a change notification to trigger a requery and
-     * return the complete contents.
+     * return the complete contents. To return a Cursor with extras, you need to
+     * extend and override {@link Cursor#getExtras()}.
      * <p>
      * To support change notifications, you must
      * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
@@ -360,7 +364,7 @@
      * @param documentId the document to return.
      * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
      * @param signal used by the caller to signal if the request should be
-     *            cancelled.
+     *            cancelled. May be null.
      * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
      *      OnCloseListener)
      * @see ParcelFileDescriptor#createReliablePipe()
@@ -384,7 +388,7 @@
      * @param documentId the document to return.
      * @param sizeHint hint of the optimal thumbnail dimensions.
      * @param signal used by the caller to signal if the request should be
-     *            cancelled.
+     *            cancelled. May be null.
      * @see Document#FLAG_SUPPORTS_THUMBNAIL
      */
     @SuppressWarnings("unused")
@@ -512,10 +516,7 @@
         final boolean callerHasManage =
                 context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS)
                 == PackageManager.PERMISSION_GRANTED;
-        if (!callerHasManage) {
-            getContext().enforceCallingOrSelfUriPermission(
-                    documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method);
-        }
+        enforceWritePermissionInner(documentUri);
 
         final Bundle out = new Bundle();
         try {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 04f3f0a..3810eb3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5140,6 +5140,12 @@
         public static final String SMS_SHORT_CODE_RULE = "sms_short_code_rule";
 
        /**
+        * Used to select TCP's default initial receiver window size in segments - defaults to a build config value
+        * @hide
+        */
+       public static final String TCP_DEFAULT_INIT_RWND = "tcp_default_init_rwnd";
+
+       /**
         * Used to disable Tethering on a device - defaults to true
         * @hide
         */
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index b808363..2752085 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -993,8 +993,16 @@
         return runAction(new Action<Set<String>>() {
             @Override
             public Set<String> run(ITextToSpeechService service) throws RemoteException {
-                String[] features = service.getFeaturesForLanguage(
+                String[] features = null;
+                try {
+                    features = service.getFeaturesForLanguage(
                         locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
+                } catch(MissingResourceException e) {
+                    Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 " +
+                            "country code for locale: " + locale, e);
+                    return null;
+                }
+
                 if (features != null) {
                     final Set<String> featureSet = new HashSet<String>();
                     Collections.addAll(featureSet, features);
diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java
index 5fbd22e..4f996cd 100644
--- a/core/java/android/speech/tts/TtsEngines.java
+++ b/core/java/android/speech/tts/TtsEngines.java
@@ -44,6 +44,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Locale;
+import java.util.MissingResourceException;
 
 /**
  * Support class for querying the list of available engines
@@ -369,28 +370,34 @@
     public String getDefaultLocale() {
         final Locale locale = Locale.getDefault();
 
-        // Note that the default locale might have an empty variant
-        // or language, and we take care that the construction is
-        // the same as {@link #getV1Locale} i.e no trailing delimiters
-        // or spaces.
-        String defaultLocale = locale.getISO3Language();
-        if (TextUtils.isEmpty(defaultLocale)) {
-            Log.w(TAG, "Default locale is empty.");
-            return "";
-        }
+        try {
+            // Note that the default locale might have an empty variant
+            // or language, and we take care that the construction is
+            // the same as {@link #getV1Locale} i.e no trailing delimiters
+            // or spaces.
+            String defaultLocale = locale.getISO3Language();
+            if (TextUtils.isEmpty(defaultLocale)) {
+                Log.w(TAG, "Default locale is empty.");
+                return "";
+            }
 
-        if (!TextUtils.isEmpty(locale.getISO3Country())) {
-            defaultLocale += LOCALE_DELIMITER + locale.getISO3Country();
-        } else {
-            // Do not allow locales of the form lang--variant with
-            // an empty country.
+            if (!TextUtils.isEmpty(locale.getISO3Country())) {
+                defaultLocale += LOCALE_DELIMITER + locale.getISO3Country();
+            } else {
+                // Do not allow locales of the form lang--variant with
+                // an empty country.
+                return defaultLocale;
+            }
+            if (!TextUtils.isEmpty(locale.getVariant())) {
+                defaultLocale += LOCALE_DELIMITER + locale.getVariant();
+            }
+
             return defaultLocale;
+        } catch (MissingResourceException e) {
+            // Default locale does not have a ISO 3166 and/or ISO 639-2/T codes. Return the
+            // default "eng-usa" (that would be the result of Locale.getDefault() == Locale.US).
+            return "eng-usa";
         }
-        if (!TextUtils.isEmpty(locale.getVariant())) {
-            defaultLocale += LOCALE_DELIMITER + locale.getVariant();
-        }
-
-        return defaultLocale;
     }
 
     /**
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 160c630..f839d52 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -391,6 +391,15 @@
                 out.append("&gt;");
             } else if (c == '&') {
                 out.append("&amp;");
+            } else if (c >= 0xD800 && c <= 0xDFFF) {
+                if (c < 0xDC00 && i + 1 < end) {
+                    char d = text.charAt(i + 1);
+                    if (d >= 0xDC00 && d <= 0xDFFF) {
+                        i++;
+                        int codepoint = 0x010000 | (int) c - 0xD800 << 10 | (int) d - 0xDC00;
+                        out.append("&#").append(codepoint).append(";");
+                    }
+                }
             } else if (c > 0x7E || c < ' ') {
                 out.append("&#").append((int) c).append(";");
             } else if (c == ' ') {
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index f76e190..da9ba5a 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -1255,7 +1255,8 @@
             Animator anim = runningAnimators.keyAt(i);
             if (anim != null) {
                 AnimationInfo oldInfo = runningAnimators.get(anim);
-                if (oldInfo != null) {
+                if (oldInfo != null && oldInfo.view != null &&
+                        oldInfo.view.getContext() == sceneRoot.getContext()) {
                     boolean cancel = false;
                     TransitionValues oldValues = oldInfo.values;
                     View oldView = oldInfo.view;
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 4af0f51..9f77d5e 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -20,9 +20,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.util.ArrayMap;
 import android.util.AttributeSet;
-import android.util.SparseArray;
 import android.util.Xml;
 import android.view.InflateException;
 import android.view.ViewGroup;
@@ -43,15 +41,7 @@
  */
 public class TransitionInflater {
 
-    // We only need one inflater for any given context. Also, this allows us to associate
-    // ids with unique instances per-Context, used to avoid re-inflating
-    // already-inflated resources into new/different instances
-    private static final ArrayMap<Context, TransitionInflater> sInflaterMap =
-            new ArrayMap<Context, TransitionInflater>();
-
     private Context mContext;
-    // TODO: do we need id maps for transitions and transitionMgrs as well?
-    SparseArray<Scene> mScenes = new SparseArray<Scene>();
 
     private TransitionInflater(Context context) {
         mContext = context;
@@ -61,13 +51,7 @@
      * Obtains the TransitionInflater from the given context.
      */
     public static TransitionInflater from(Context context) {
-        TransitionInflater inflater = sInflaterMap.get(context);
-        if (inflater != null) {
-            return inflater;
-        }
-        inflater = new TransitionInflater(context);
-        sInflaterMap.put(context, inflater);
-        return inflater;
+        return new TransitionInflater(context);
     }
 
     /**
diff --git a/core/java/android/util/MapCollections.java b/core/java/android/util/MapCollections.java
index f4a9b0b..28b788b 100644
--- a/core/java/android/util/MapCollections.java
+++ b/core/java/android/util/MapCollections.java
@@ -97,10 +97,10 @@
             if (!mEntryValid) {
                 throw new IllegalStateException();
             }
+            colRemoveAt(mIndex);
             mIndex--;
             mEnd--;
             mEntryValid = false;
-            colRemoveAt(mIndex);
         }
 
         @Override
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 354ea66..7d310a2 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -643,6 +643,15 @@
                 || uid == 0;
     }
 
+    /**
+     * Returns true if the display is a public presentation display.
+     * @hide
+     */
+    public boolean isPublicPresentation() {
+        return (mFlags & (Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION)) ==
+                Display.FLAG_PRESENTATION;
+    }
+
     private void updateDisplayInfoLocked() {
         // Note: The display manager caches display info objects on our behalf.
         DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 1b76cb1..c92a104 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -78,7 +78,8 @@
     void addWindowToken(IBinder token, int type);
     void removeWindowToken(IBinder token);
     void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId,
-            int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId);
+            int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
+            int configChanges);
     void setAppGroupId(IBinder token, int groupId);
     void setAppOrientation(IApplicationToken token, int requestedOrientation);
     int getAppOrientation(IApplicationToken token);
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index f36c78f..42a58a8 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -323,6 +323,10 @@
                 mInProgress = false;
                 mInitialSpan = 0;
                 mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
+            } else if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS && streamComplete) {
+                mInProgress = false;
+                mInitialSpan = 0;
+                mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
             }
 
             if (streamComplete) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6c04c0b..5015b7e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -18,7 +18,6 @@
 
 import android.content.ClipData;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -3103,6 +3102,16 @@
     private static final int UNDEFINED_PADDING = Integer.MIN_VALUE;
 
     /**
+     * Cache if a left padding has been defined
+     */
+    private boolean mLeftPaddingDefined = false;
+
+    /**
+     * Cache if a right padding has been defined
+     */
+    private boolean mRightPaddingDefined = false;
+
+    /**
      * @hide
      */
     int mOldWidthMeasureSpec = Integer.MIN_VALUE;
@@ -3530,10 +3539,10 @@
         int overScrollMode = mOverScrollMode;
         boolean initializeScrollbars = false;
 
-        boolean leftPaddingDefined = false;
-        boolean rightPaddingDefined = false;
         boolean startPaddingDefined = false;
         boolean endPaddingDefined = false;
+        boolean leftPaddingDefined = false;
+        boolean rightPaddingDefined = false;
 
         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
 
@@ -3865,6 +3874,11 @@
             setBackground(background);
         }
 
+        // setBackground above will record that padding is currently provided by the background.
+        // If we have padding specified via xml, record that here instead and use it.
+        mLeftPaddingDefined = leftPaddingDefined;
+        mRightPaddingDefined = rightPaddingDefined;
+
         if (padding >= 0) {
             leftPadding = padding;
             topPadding = padding;
@@ -3882,11 +3896,11 @@
             // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial
             // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if
             // defined.
-            if (!leftPaddingDefined && startPaddingDefined) {
+            if (!mLeftPaddingDefined && startPaddingDefined) {
                 leftPadding = startPadding;
             }
             mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial;
-            if (!rightPaddingDefined && endPaddingDefined) {
+            if (!mRightPaddingDefined && endPaddingDefined) {
                 rightPadding = endPadding;
             }
             mUserPaddingRightInitial = (rightPadding >= 0) ? rightPadding : mUserPaddingRightInitial;
@@ -3898,10 +3912,10 @@
             // defined.
             final boolean hasRelativePadding = startPaddingDefined || endPaddingDefined;
 
-            if (leftPaddingDefined && !hasRelativePadding) {
+            if (mLeftPaddingDefined && !hasRelativePadding) {
                 mUserPaddingLeftInitial = leftPadding;
             }
-            if (rightPaddingDefined && !hasRelativePadding) {
+            if (mRightPaddingDefined && !hasRelativePadding) {
                 mUserPaddingRightInitial = rightPadding;
             }
         }
@@ -5900,6 +5914,8 @@
                 sThreadLocal.set(localInsets);
             }
             boolean res = computeFitSystemWindows(insets, localInsets);
+            mUserPaddingLeftInitial = localInsets.left;
+            mUserPaddingRightInitial = localInsets.right;
             internalSetPadding(localInsets.left, localInsets.top,
                     localInsets.right, localInsets.bottom);
             return res;
@@ -6527,7 +6543,7 @@
      * @attr ref android.R.styleable#View_filterTouchesWhenObscured
      */
     public void setFilterTouchesWhenObscured(boolean enabled) {
-        setFlags(enabled ? 0 : FILTER_TOUCHES_WHEN_OBSCURED,
+        setFlags(enabled ? FILTER_TOUCHES_WHEN_OBSCURED : 0,
                 FILTER_TOUCHES_WHEN_OBSCURED);
     }
 
@@ -12133,12 +12149,14 @@
         if (!isTextAlignmentResolved()) {
             resolveTextAlignment();
         }
-        if (!isPaddingResolved()) {
-            resolvePadding();
-        }
+        // Should resolve Drawables before Padding because we need the layout direction of the
+        // Drawable to correctly resolve Padding.
         if (!isDrawablesResolved()) {
             resolveDrawables();
         }
+        if (!isPaddingResolved()) {
+            resolvePadding();
+        }
         onRtlPropertiesChanged(getLayoutDirection());
         return true;
     }
@@ -12341,6 +12359,20 @@
             // If start / end padding are defined, they will be resolved (hence overriding) to
             // left / right or right / left depending on the resolved layout direction.
             // If start / end padding are not defined, use the left / right ones.
+            if (mBackground != null && (!mLeftPaddingDefined || !mRightPaddingDefined)) {
+                Rect padding = sThreadLocal.get();
+                if (padding == null) {
+                    padding = new Rect();
+                    sThreadLocal.set(padding);
+                }
+                mBackground.getPadding(padding);
+                if (!mLeftPaddingDefined) {
+                    mUserPaddingLeftInitial = padding.left;
+                }
+                if (!mRightPaddingDefined) {
+                    mUserPaddingRightInitial = padding.right;
+                }
+            }
             switch (resolvedLayoutDirection) {
                 case LAYOUT_DIRECTION_RTL:
                     if (mUserPaddingStart != UNDEFINED_PADDING) {
@@ -15336,6 +15368,8 @@
                         mUserPaddingRightInitial = padding.right;
                         internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
                 }
+                mLeftPaddingDefined = false;
+                mRightPaddingDefined = false;
             }
 
             // Compare the minimum sizes of the old Drawable and the new.  If there isn't an old or
@@ -15432,6 +15466,9 @@
         mUserPaddingLeftInitial = left;
         mUserPaddingRightInitial = right;
 
+        mLeftPaddingDefined = true;
+        mRightPaddingDefined = true;
+
         internalSetPadding(left, top, right, bottom);
     }
 
@@ -15517,6 +15554,8 @@
 
         mUserPaddingStart = start;
         mUserPaddingEnd = end;
+        mLeftPaddingDefined = true;
+        mRightPaddingDefined = true;
 
         switch(getLayoutDirection()) {
             case LAYOUT_DIRECTION_RTL:
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index c3f064f..e67659c 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -212,6 +212,14 @@
      */
     private static final int OVERFLING_DISTANCE = 6;
 
+    /**
+     * Configuration values for overriding {@link #hasPermanentMenuKey()} behavior.
+     * These constants must match the definition in res/values/config.xml.
+     */
+    private static final int HAS_PERMANENT_MENU_KEY_AUTODETECT = 0;
+    private static final int HAS_PERMANENT_MENU_KEY_TRUE = 1;
+    private static final int HAS_PERMANENT_MENU_KEY_FALSE = 2;
+
     private final int mEdgeSlop;
     private final int mFadingEdgeLength;
     private final int mMinimumFlingVelocity;
@@ -296,12 +304,31 @@
         mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
 
         if (!sHasPermanentMenuKeySet) {
-            IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-            try {
-                sHasPermanentMenuKey = !wm.hasNavigationBar();
-                sHasPermanentMenuKeySet = true;
-            } catch (RemoteException ex) {
-                sHasPermanentMenuKey = false;
+            final int configVal = res.getInteger(
+                    com.android.internal.R.integer.config_overrideHasPermanentMenuKey);
+
+            switch (configVal) {
+                default:
+                case HAS_PERMANENT_MENU_KEY_AUTODETECT: {
+                    IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+                    try {
+                        sHasPermanentMenuKey = !wm.hasNavigationBar();
+                        sHasPermanentMenuKeySet = true;
+                    } catch (RemoteException ex) {
+                        sHasPermanentMenuKey = false;
+                    }
+                }
+                break;
+
+                case HAS_PERMANENT_MENU_KEY_TRUE:
+                    sHasPermanentMenuKey = true;
+                    sHasPermanentMenuKeySet = true;
+                    break;
+
+                case HAS_PERMANENT_MENU_KEY_FALSE:
+                    sHasPermanentMenuKey = false;
+                    sHasPermanentMenuKeySet = true;
+                    break;
             }
         }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 637af6f..d779628 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -232,8 +232,6 @@
     InputStage mFirstInputStage;
     InputStage mFirstPostImeInputStage;
 
-    boolean mFlipControllerFallbackKeys;
-
     boolean mWindowAttributesChanged = false;
     int mWindowAttributesChangesFlag = 0;
 
@@ -370,8 +368,6 @@
         mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
         mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
         mChoreographer = Choreographer.getInstance();
-        mFlipControllerFallbackKeys =
-            context.getResources().getBoolean(R.bool.flip_controller_fallback_keys);
 
         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mAttachInfo.mScreenOn = powerManager.isScreenOn();
@@ -2912,11 +2908,8 @@
                 mView.dispatchConfigurationChanged(config);
             }
         }
-
-        mFlipControllerFallbackKeys =
-            mContext.getResources().getBoolean(R.bool.flip_controller_fallback_keys);
     }
-
+    
     /**
      * Return true if child is an ancestor of parent, (or equal to the parent).
      */
@@ -3393,16 +3386,7 @@
         public final void deliver(QueuedInputEvent q) {
             if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                 forward(q);
-            } else if (mView == null || !mAdded) {
-                Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent);
-                finish(q, false);
-            } else if (!mAttachInfo.mHasWindowFocus &&
-                  !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) &&
-                  !isTerminalInputEvent(q.mEvent)) {
-                // If this is a focused event and the window doesn't currently have input focus,
-                // then drop this event.  This could be an event that came back from the previous
-                // stage but the window has lost focus in the meantime.
-                Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent);
+            } else if (shouldDropInputEvent(q)) {
                 finish(q, false);
             } else {
                 apply(q, onProcess(q));
@@ -3461,6 +3445,22 @@
             }
         }
 
+        protected boolean shouldDropInputEvent(QueuedInputEvent q) {
+            if (mView == null || !mAdded) {
+                Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent);
+                return true;
+            } else if (!mAttachInfo.mHasWindowFocus &&
+                  !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) &&
+                  !isTerminalInputEvent(q.mEvent)) {
+                // If this is a focused event and the window doesn't currently have input focus,
+                // then drop this event.  This could be an event that came back from the previous
+                // stage but the window has lost focus in the meantime.
+                Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent);
+                return true;
+            }
+            return false;
+        }
+
         void dump(String prefix, PrintWriter writer) {
             if (mNext != null) {
                 mNext.dump(prefix, writer);
@@ -3846,6 +3846,10 @@
                 return FINISH_HANDLED;
             }
 
+            if (shouldDropInputEvent(q)) {
+                return FINISH_NOT_HANDLED;
+            }
+
             // If the Control modifier is held, try to interpret the key as a shortcut.
             if (event.getAction() == KeyEvent.ACTION_DOWN
                     && event.isCtrlPressed()
@@ -3854,12 +3858,18 @@
                 if (mView.dispatchKeyShortcutEvent(event)) {
                     return FINISH_HANDLED;
                 }
+                if (shouldDropInputEvent(q)) {
+                    return FINISH_NOT_HANDLED;
+                }
             }
 
             // Apply the fallback event policy.
             if (mFallbackEventHandler.dispatchKeyEvent(event)) {
                 return FINISH_HANDLED;
             }
+            if (shouldDropInputEvent(q)) {
+                return FINISH_NOT_HANDLED;
+            }
 
             // Handle automatic focus changes.
             if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -3968,7 +3978,6 @@
         private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler();
         private final SyntheticTouchNavigationHandler mTouchNavigation =
                 new SyntheticTouchNavigationHandler();
-        private final SyntheticKeyHandler mKeys = new SyntheticKeyHandler();
 
         public SyntheticInputStage() {
             super(null);
@@ -3991,12 +4000,7 @@
                     mTouchNavigation.process(event);
                     return FINISH_HANDLED;
                 }
-            } else if (q.mEvent instanceof KeyEvent) {
-                if (mKeys.process((KeyEvent) q.mEvent)) {
-                    return FINISH_HANDLED;
-                }
             }
-
             return FORWARD;
         }
 
@@ -4826,63 +4830,6 @@
         };
     }
 
-    final class SyntheticKeyHandler {
-
-        public boolean process(KeyEvent event) {
-            // In some locales (like Japan) controllers use B for confirm and A for back, rather
-            // than vice versa, so we need to special case this here since the input system itself
-            // is not locale-aware.
-            int keyCode;
-            switch(event.getKeyCode()) {
-                case KeyEvent.KEYCODE_BUTTON_A:
-                case KeyEvent.KEYCODE_BUTTON_C:
-                case KeyEvent.KEYCODE_BUTTON_X:
-                case KeyEvent.KEYCODE_BUTTON_Z:
-                    keyCode = mFlipControllerFallbackKeys ?
-                        KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_DPAD_CENTER;
-                    break;
-                case KeyEvent.KEYCODE_BUTTON_B:
-                case KeyEvent.KEYCODE_BUTTON_Y:
-                    keyCode = mFlipControllerFallbackKeys ?
-                        KeyEvent.KEYCODE_DPAD_CENTER : KeyEvent.KEYCODE_BACK;
-                    break;
-                case KeyEvent.KEYCODE_BUTTON_THUMBL:
-                case KeyEvent.KEYCODE_BUTTON_THUMBR:
-                case KeyEvent.KEYCODE_BUTTON_START:
-                case KeyEvent.KEYCODE_BUTTON_1:
-                case KeyEvent.KEYCODE_BUTTON_2:
-                case KeyEvent.KEYCODE_BUTTON_3:
-                case KeyEvent.KEYCODE_BUTTON_4:
-                case KeyEvent.KEYCODE_BUTTON_5:
-                case KeyEvent.KEYCODE_BUTTON_6:
-                case KeyEvent.KEYCODE_BUTTON_7:
-                case KeyEvent.KEYCODE_BUTTON_8:
-                case KeyEvent.KEYCODE_BUTTON_9:
-                case KeyEvent.KEYCODE_BUTTON_10:
-                case KeyEvent.KEYCODE_BUTTON_11:
-                case KeyEvent.KEYCODE_BUTTON_12:
-                case KeyEvent.KEYCODE_BUTTON_13:
-                case KeyEvent.KEYCODE_BUTTON_14:
-                case KeyEvent.KEYCODE_BUTTON_15:
-                case KeyEvent.KEYCODE_BUTTON_16:
-                    keyCode = KeyEvent.KEYCODE_DPAD_CENTER;
-                    break;
-                case KeyEvent.KEYCODE_BUTTON_SELECT:
-                case KeyEvent.KEYCODE_BUTTON_MODE:
-                    keyCode = KeyEvent.KEYCODE_MENU;
-                default:
-                    return false;
-            }
-
-            enqueueInputEvent(new KeyEvent(event.getDownTime(), event.getEventTime(),
-                        event.getAction(), keyCode, event.getRepeatCount(), event.getMetaState(),
-                        event.getDeviceId(), event.getScanCode(),
-                        event.getFlags() | KeyEvent.FLAG_FALLBACK, event.getSource()));
-            return true;
-        }
-
-    }
-
     /**
      * Returns true if the key is used for keyboard navigation.
      * @param keyEvent The key event.
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index f0e6677..52f9c0b 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -311,7 +311,6 @@
         lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
         lp.width = LayoutParams.WRAP_CONTENT;
         lp.height = LayoutParams.WRAP_CONTENT;
-        lp.privateFlags |= LayoutParams.PRIVATE_FLAG_FORCE_SHOW_NAV_BAR;
         window.setAttributes(lp);
         window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL
                 | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 0ce4da5..53a4c0d0 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1063,13 +1063,6 @@
         public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
 
         /**
-         * Special flag for the volume overlay: force the window manager out of "hide nav bar"
-         * mode while the window is on screen.
-         *
-         * {@hide} */
-        public static final int PRIVATE_FLAG_FORCE_SHOW_NAV_BAR = 0x00000020;
-
-        /**
          * Never animate position changes of the window.
          *
          * {@hide} */
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 40f95ce..2ab3024 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -534,6 +534,13 @@
     private static int hashCodeInternal(String locale, String mode, String extraValue,
             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
             boolean isAsciiCapable) {
+        // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new
+        // attribute is added in order to avoid enabled subtypes being unexpectedly disabled.
+        final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable;
+        if (needsToCalculateCompatibleHashCode) {
+            return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
+                    overridesImplicitlyEnabledSubtype});
+        }
         return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
                 overridesImplicitlyEnabledSubtype, isAsciiCapable});
     }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5bc39f1..d53bb74 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -686,6 +686,15 @@
     }
 
     /**
+     * Used only by internal tests to free up memory.
+     *
+     * @hide
+     */
+    public static void freeMemoryForTests() {
+        getFactory().getStatics().freeMemoryForTests();
+    }
+
+    /**
      * Informs WebView of the network state. This is used to set
      * the JavaScript property window.navigator.isOnline and
      * generates the online/offline event as specified in HTML5, sec. 5.7.7
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 9d9d882..e391aaf 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -50,6 +50,11 @@
         String getDefaultUserAgent(Context context);
 
         /**
+         * Used for tests only.
+         */
+         void freeMemoryForTests();
+
+        /**
          * Implements the API method:
          * {@link android.webkit.WebView#setWebContentsDebuggingEnabled(boolean) }
          */
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6a7a34f..25a43a6 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2076,22 +2076,23 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
         mInLayout = true;
+        final int childCount = getChildCount();
         if (changed) {
-            int childCount = getChildCount();
             for (int i = 0; i < childCount; i++) {
                 getChildAt(i).forceLayout();
             }
             mRecycler.markChildrenDirty();
         }
 
-        if (mFastScroller != null && (mItemCount != mOldItemCount || mDataChanged)) {
-            mFastScroller.onItemCountChanged(mItemCount);
-        }
-
         layoutChildren();
         mInLayout = false;
 
         mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
+
+        // TODO: Move somewhere sane. This doesn't belong in onLayout().
+        if (mFastScroller != null) {
+            mFastScroller.onItemCountChanged(getChildCount(), mItemCount);
+        }
     }
 
     /**
@@ -2228,26 +2229,34 @@
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
 
         isScrap[0] = false;
-        View scrapView;
 
-        scrapView = mRecycler.getTransientStateView(position);
-        if (scrapView == null) {
-            scrapView = mRecycler.getScrapView(position);
-        }
+        // Check whether we have a transient state view. Attempt to re-bind the
+        // data and discard the view if we fail.
+        final View transientView = mRecycler.getTransientStateView(position);
+        if (transientView != null) {
+            final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
 
-        View child;
-        if (scrapView != null) {
-            child = mAdapter.getView(position, scrapView, this);
+            // If the view type hasn't changed, attempt to re-bind the data.
+            if (params.viewType == mAdapter.getItemViewType(position)) {
+                final View updatedView = mAdapter.getView(position, transientView, this);
 
-            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
-                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+                // If we failed to re-bind the data, scrap the obtained view.
+                if (updatedView != transientView) {
+                    mRecycler.addScrapView(updatedView, position);
+                }
             }
 
+            // Scrap view implies temporary detachment.
+            isScrap[0] = true;
+            return transientView;
+        }
+
+        final View scrapView = mRecycler.getScrapView(position);
+        final View child = mAdapter.getView(position, scrapView, this);
+        if (scrapView != null) {
             if (child != scrapView) {
+                // Failed to re-bind the data, return scrap to the heap.
                 mRecycler.addScrapView(scrapView, position);
-                if (mCacheColorHint != 0) {
-                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
-                }
             } else {
                 isScrap[0] = true;
 
@@ -2259,16 +2268,14 @@
 
                 child.dispatchFinishTemporaryDetach();
             }
-        } else {
-            child = mAdapter.getView(position, null, this);
+        }
 
-            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
-                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-            }
+        if (mCacheColorHint != 0) {
+            child.setDrawingCacheBackgroundColor(mCacheColorHint);
+        }
 
-            if (mCacheColorHint != 0) {
-                child.setDrawingCacheBackgroundColor(mCacheColorHint);
-            }
+        if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
 
         if (mAdapterHasStableIds) {
@@ -6562,12 +6569,8 @@
                     }
                 }
             }
-            if (mTransientStateViews != null) {
-                mTransientStateViews.clear();
-            }
-            if (mTransientStateViewsById != null) {
-                mTransientStateViewsById.clear();
-            }
+
+            clearTransientStateViews();
         }
 
         /**
@@ -6634,14 +6637,26 @@
         }
 
         /**
-         * Dump any currently saved views with transient state.
+         * Dumps and fully detaches any currently saved views with transient
+         * state.
          */
         void clearTransientStateViews() {
-            if (mTransientStateViews != null) {
-                mTransientStateViews.clear();
+            final SparseArray<View> viewsByPos = mTransientStateViews;
+            if (viewsByPos != null) {
+                final int N = viewsByPos.size();
+                for (int i = 0; i < N; i++) {
+                    removeDetachedView(viewsByPos.valueAt(i), false);
+                }
+                viewsByPos.clear();
             }
-            if (mTransientStateViewsById != null) {
-                mTransientStateViewsById.clear();
+
+            final LongSparseArray<View> viewsById = mTransientStateViewsById;
+            if (viewsById != null) {
+                final int N = viewsById.size();
+                for (int i = 0; i < N; i++) {
+                    removeDetachedView(viewsById.valueAt(i), false);
+                }
+                viewsById.clear();
             }
         }
 
@@ -6686,6 +6701,13 @@
 
             scrap.dispatchStartTemporaryDetach();
 
+            // The the accessibility state of the view may change while temporary
+            // detached and we do not allow detached views to fire accessibility
+            // events. So we are announcing that the subtree changed giving a chance
+            // to clients holding on to a view in this subtree to refresh it.
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+
             // Don't scrap views that have transient state.
             final boolean scrapHasTransientState = scrap.hasTransientState();
             if (scrapHasTransientState) {
@@ -6759,44 +6781,48 @@
                 if (victim != null) {
                     final AbsListView.LayoutParams lp
                             = (AbsListView.LayoutParams) victim.getLayoutParams();
-                    int whichScrap = lp.viewType;
+                    final int whichScrap = lp.viewType;
 
                     activeViews[i] = null;
 
-                    final boolean scrapHasTransientState = victim.hasTransientState();
-                    if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
-                        // Do not move views that should be ignored
-                        if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER &&
-                                scrapHasTransientState) {
+                    if (victim.hasTransientState()) {
+                        // Store views with transient state for later use.
+                        victim.dispatchStartTemporaryDetach();
+
+                        if (mAdapter != null && mAdapterHasStableIds) {
+                            if (mTransientStateViewsById == null) {
+                                mTransientStateViewsById = new LongSparseArray<View>();
+                            }
+                            long id = mAdapter.getItemId(mFirstActivePosition + i);
+                            mTransientStateViewsById.put(id, victim);
+                        } else if (!mDataChanged) {
+                            if (mTransientStateViews == null) {
+                                mTransientStateViews = new SparseArray<View>();
+                            }
+                            mTransientStateViews.put(mFirstActivePosition + i, victim);
+                        } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
+                            // The data has changed, we can't keep this view.
                             removeDetachedView(victim, false);
                         }
-                        if (scrapHasTransientState) {
-                            if (mAdapter != null && mAdapterHasStableIds) {
-                                if (mTransientStateViewsById == null) {
-                                    mTransientStateViewsById = new LongSparseArray<View>();
-                                }
-                                long id = mAdapter.getItemId(mFirstActivePosition + i);
-                                mTransientStateViewsById.put(id, victim);
-                            } else {
-                                if (mTransientStateViews == null) {
-                                    mTransientStateViews = new SparseArray<View>();
-                                }
-                                mTransientStateViews.put(mFirstActivePosition + i, victim);
-                            }
+                    } else if (!shouldRecycleViewType(whichScrap)) {
+                        // Discard non-recyclable views except headers/footers.
+                        if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
+                            removeDetachedView(victim, false);
                         }
-                        continue;
-                    }
+                    } else {
+                        // Store everything else on the appropriate scrap heap.
+                        if (multipleScraps) {
+                            scrapViews = mScrapViews[whichScrap];
+                        }
 
-                    if (multipleScraps) {
-                        scrapViews = mScrapViews[whichScrap];
-                    }
-                    victim.dispatchStartTemporaryDetach();
-                    lp.scrappedFromPosition = mFirstActivePosition + i;
-                    scrapViews.add(victim);
+                        victim.dispatchStartTemporaryDetach();
+                        lp.scrappedFromPosition = mFirstActivePosition + i;
+                        scrapViews.add(victim);
 
-                    victim.setAccessibilityDelegate(null);
-                    if (hasListener) {
-                        mRecyclerListener.onMovedToScrapHeap(victim);
+                        victim.setAccessibilityDelegate(null);
+                        if (hasListener) {
+                            mRecyclerListener.onMovedToScrapHeap(victim);
+                        }
                     }
                 }
             }
@@ -6805,8 +6831,10 @@
         }
 
         /**
-         * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
-         * (This can happen if an adapter does not recycle its views).
+         * Makes sure that the size of mScrapViews does not exceed the size of
+         * mActiveViews, which can happen if an adapter does not recycle its
+         * views. Removes cached transient state views that no longer have
+         * transient state.
          */
         private void pruneScrapViews() {
             final int maxViews = mActiveViews.length;
@@ -6822,20 +6850,25 @@
                 }
             }
 
-            if (mTransientStateViews != null) {
-                for (int i = 0; i < mTransientStateViews.size(); i++) {
-                    final View v = mTransientStateViews.valueAt(i);
+            final SparseArray<View> transViewsByPos = mTransientStateViews;
+            if (transViewsByPos != null) {
+                for (int i = 0; i < transViewsByPos.size(); i++) {
+                    final View v = transViewsByPos.valueAt(i);
                     if (!v.hasTransientState()) {
-                        mTransientStateViews.removeAt(i);
+                        removeDetachedView(v, false);
+                        transViewsByPos.removeAt(i);
                         i--;
                     }
                 }
             }
-            if (mTransientStateViewsById != null) {
-                for (int i = 0; i < mTransientStateViewsById.size(); i++) {
-                    final View v = mTransientStateViewsById.valueAt(i);
+
+            final LongSparseArray<View> transViewsById = mTransientStateViewsById;
+            if (transViewsById != null) {
+                for (int i = 0; i < transViewsById.size(); i++) {
+                    final View v = transViewsById.valueAt(i);
                     if (!v.hasTransientState()) {
-                        mTransientStateViewsById.removeAt(i);
+                        removeDetachedView(v, false);
+                        transViewsById.removeAt(i);
                         i--;
                     }
                 }
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index 0957ab4..e90b460 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -1210,35 +1210,38 @@
             child = (WeekView) view.getChildAt(offset);
         }
 
-        // Find out which month we're moving into
-        int month;
-        if (mIsScrollingUp) {
-            month = child.getMonthOfFirstWeekDay();
-        } else {
-            month = child.getMonthOfLastWeekDay();
-        }
-
-        // And how it relates to our current highlighted month
-        int monthDiff;
-        if (mCurrentMonthDisplayed == 11 && month == 0) {
-            monthDiff = 1;
-        } else if (mCurrentMonthDisplayed == 0 && month == 11) {
-            monthDiff = -1;
-        } else {
-            monthDiff = month - mCurrentMonthDisplayed;
-        }
-
-        // Only switch months if we're scrolling away from the currently
-        // selected month
-        if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
-            Calendar firstDay = child.getFirstDay();
+        if (child != null) {
+            // Find out which month we're moving into
+            int month;
             if (mIsScrollingUp) {
-                firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
+                month = child.getMonthOfFirstWeekDay();
             } else {
-                firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
+                month = child.getMonthOfLastWeekDay();
             }
-            setMonthDisplayed(firstDay);
+
+            // And how it relates to our current highlighted month
+            int monthDiff;
+            if (mCurrentMonthDisplayed == 11 && month == 0) {
+                monthDiff = 1;
+            } else if (mCurrentMonthDisplayed == 0 && month == 11) {
+                monthDiff = -1;
+            } else {
+                monthDiff = month - mCurrentMonthDisplayed;
+            }
+
+            // Only switch months if we're scrolling away from the currently
+            // selected month
+            if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
+                Calendar firstDay = child.getFirstDay();
+                if (mIsScrollingUp) {
+                    firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
+                } else {
+                    firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
+                }
+                setMonthDisplayed(firstDay);
+            }
         }
+
         mPreviousScrollPosition = currScroll;
         mPreviousScrollState = mCurrentScrollState;
     }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 9dab7b4..748af7b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -317,7 +317,7 @@
 
                 mErrorPopup = null;
             }
-
+            mShowErrorAfterAttach = false;
         } else {
             setErrorIcon(icon);
             if (mTextView.isFocused()) {
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 01ac8fd..4379bf6 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -224,6 +224,8 @@
             mHasPendingDrag = false;
         }
     };
+    private int mOldItemCount;
+    private int mOldChildCount;
 
     /**
      * Used to delay hiding fast scroll decorations.
@@ -248,6 +250,8 @@
     public FastScroller(AbsListView listView) {
         mList = listView;
         mOverlay = listView.getOverlay();
+        mOldItemCount = listView.getCount();
+        mOldChildCount = listView.getChildCount();
 
         final Context context = listView.getContext();
         mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
@@ -258,6 +262,7 @@
         final ImageView trackImage = new ImageView(context);
         mTrackImage = trackImage;
 
+        updateLongList(mOldChildCount, mOldItemCount);
         int width = 0;
 
         // Add track to overlay if it has an image.
@@ -445,20 +450,23 @@
         updateLayout();
     }
 
-    public void onItemCountChanged(int totalItemCount) {
-        final int visibleItemCount = mList.getChildCount();
-        final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
-        if (hasMoreItems && mState != STATE_DRAGGING) {
-            final int firstVisibleItem = mList.getFirstVisiblePosition();
-            setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
-        }
+    public void onItemCountChanged(int childCount, int itemCount) {
+        if (mOldItemCount != itemCount || mOldChildCount != childCount) {
+            mOldItemCount = itemCount;
+            mOldChildCount = childCount;
 
-        updateLongList(visibleItemCount, totalItemCount);
+            final boolean hasMoreItems = itemCount - childCount > 0;
+            if (hasMoreItems && mState != STATE_DRAGGING) {
+                final int firstVisibleItem = mList.getFirstVisiblePosition();
+                setThumbPos(getPosFromItemCount(firstVisibleItem, childCount, itemCount));
+            }
+
+            updateLongList(childCount, itemCount);
+        }
     }
 
-    private void updateLongList(int visibleItemCount, int totalItemCount) {
-        final boolean longList = visibleItemCount > 0
-                && totalItemCount / visibleItemCount >= MIN_PAGES;
+    private void updateLongList(int childCount, int itemCount) {
+        final boolean longList = childCount > 0 && itemCount / childCount >= MIN_PAGES;
         if (mLongList != longList) {
             mLongList = longList;
 
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 313dd54..f05179b 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -710,6 +710,7 @@
             }
             d.setLevel(mLevel);
             d.setLayoutDirection(getLayoutDirection());
+            d.setVisible(getVisibility() == VISIBLE, true);
             mDrawableWidth = d.getIntrinsicWidth();
             mDrawableHeight = d.getIntrinsicHeight();
             applyColorMod();
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 65a2d4d..6a369a6 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -308,6 +308,11 @@
         mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl);
 
         a.recycle();
+
+        // If not explicitly specified this view is important for accessibility.
+        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
     }
 
     /**
@@ -959,9 +964,11 @@
         if (!mInDrawing) {
             if (verifyDrawable(dr)) {
                 final Rect dirty = dr.getBounds();
+                final int scrollX = mScrollX + mPaddingLeft;
+                final int scrollY = mScrollY + mPaddingTop;
 
-                invalidate(dirty.left + mScrollX, dirty.top + mScrollY,
-                        dirty.right + mScrollX, dirty.bottom + mScrollY);
+                invalidate(dirty.left + scrollX, dirty.top + scrollY,
+                        dirty.right + scrollX, dirty.bottom + scrollY);
             } else {
                 super.invalidateDrawable(dr);
             }
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index b204dfd..1cda631 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -731,10 +731,14 @@
                 }
             }
 
-            if (scheduleOtherSpellCheck) {
+            if (scheduleOtherSpellCheck && wordStart <= end) {
                 // Update range span: start new spell check from last wordStart
                 setRangeSpan(editable, wordStart, end);
             } else {
+                if (DBG && scheduleOtherSpellCheck) {
+                    Log.w(TAG, "Trying to schedule spellcheck for invalid region, from "
+                            + wordStart + " to " + end);
+                }
                 removeRangeSpan(editable);
             }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index cb930d6..37121e2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -56,6 +56,7 @@
 import android.text.SpanWatcher;
 import android.text.Spannable;
 import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.text.StaticLayout;
@@ -3494,19 +3495,7 @@
             ss.selEnd = end;
 
             if (mText instanceof Spanned) {
-                /*
-                 * Calling setText() strips off any ChangeWatchers;
-                 * strip them now to avoid leaking references.
-                 * But do it to a copy so that if there are any
-                 * further changes to the text of this view, it
-                 * won't get into an inconsistent state.
-                 */
-
-                Spannable sp = new SpannableString(mText);
-
-                for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
-                    sp.removeSpan(cw);
-                }
+                Spannable sp = new SpannableStringBuilder(mText);
 
                 if (mEditor != null) {
                     removeMisspelledSpans(sp);
@@ -8678,6 +8667,10 @@
         super.onRtlPropertiesChanged(layoutDirection);
 
         mTextDir = getTextDirectionHeuristic();
+
+        if (mLayout != null) {
+            checkForRelayout();
+        }
     }
 
     TextDirectionHeuristic getTextDirectionHeuristic() {
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index fbdf318..d57b739 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -56,7 +56,17 @@
  * can load images from various sources (such as resources or content
  * providers), takes care of computing its measurement from the video so that
  * it can be used in any layout manager, and provides various display options
- * such as scaling and tinting.
+ * such as scaling and tinting.<p>
+ *
+ * <em>Note: VideoView does not retain its full state when going into the
+ * background.</em>  In particular, it does not restore the current play state,
+ * play position, selected tracks, or any subtitle tracks added via
+ * {@link #addSubtitleSource addSubtitleSource()}.  Applications should
+ * save and restore these on their own in
+ * {@link android.app.Activity#onSaveInstanceState} and
+ * {@link android.app.Activity#onRestoreInstanceState}.<p>
+ * Also note that the audio session id (from {@link #getAudioSessionId}) may
+ * change from its previously returned value when the VideoView is restored.
  */
 public class VideoView extends SurfaceView
         implements MediaPlayerControl, SubtitleController.Anchor {
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 525517c..43c4b49 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -68,6 +68,8 @@
     void noteFullWifiLockReleasedFromSource(in WorkSource ws);
     void noteWifiScanStartedFromSource(in WorkSource ws);
     void noteWifiScanStoppedFromSource(in WorkSource ws);
+    void noteWifiBatchedScanStartedFromSource(in WorkSource ws, int csph);
+    void noteWifiBatchedScanStoppedFromSource(in WorkSource ws);
     void noteWifiMulticastEnabledFromSource(in WorkSource ws);
     void noteWifiMulticastDisabledFromSource(in WorkSource ws);
     void noteNetworkInterfaceType(String iface, int type);
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
new file mode 100644
index 0000000..47d2a9c
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteInfo;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.Comparator;
+
+/**
+ * This class implements the route chooser dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to choose a route that matches a given selector.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ *
+ * TODO: Move this back into the API, as in the support library media router.
+ */
+public class MediaRouteChooserDialog extends Dialog {
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+
+    private int mRouteTypes;
+    private View.OnClickListener mExtendedSettingsClickListener;
+    private RouteAdapter mAdapter;
+    private ListView mListView;
+    private Button mExtendedSettingsButton;
+    private boolean mAttachedToWindow;
+
+    public MediaRouteChooserDialog(Context context, int theme) {
+        super(context, theme);
+
+        mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mCallback = new MediaRouterCallback();
+    }
+
+    /**
+     * Gets the media route types for filtering the routes that the user can
+     * select using the media route chooser dialog.
+     *
+     * @return The route types.
+     */
+    public int getRouteTypes() {
+        return mRouteTypes;
+    }
+
+    /**
+     * Sets the types of routes that will be shown in the media route chooser dialog
+     * launched by this button.
+     *
+     * @param types The route types to match.
+     */
+    public void setRouteTypes(int types) {
+        if (mRouteTypes != types) {
+            mRouteTypes = types;
+
+            if (mAttachedToWindow) {
+                mRouter.removeCallback(mCallback);
+                mRouter.addCallback(types, mCallback,
+                        MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+            }
+
+            refreshRoutes();
+        }
+    }
+
+    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+        if (listener != mExtendedSettingsClickListener) {
+            mExtendedSettingsClickListener = listener;
+            updateExtendedSettingsButton();
+        }
+    }
+
+    /**
+     * Returns true if the route should be included in the list.
+     * <p>
+     * The default implementation returns true for enabled non-default routes that
+     * match the route types.  Subclasses can override this method to filter routes
+     * differently.
+     * </p>
+     *
+     * @param route The route to consider, never null.
+     * @return True if the route should be included in the chooser dialog.
+     */
+    public boolean onFilterRoute(MediaRouter.RouteInfo route) {
+        return !route.isDefault() && route.isEnabled() && route.matchesTypes(mRouteTypes);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
+
+        setContentView(R.layout.media_route_chooser_dialog);
+        setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY
+                ? R.string.media_route_chooser_title_for_remote_display
+                : R.string.media_route_chooser_title);
+
+        // Must be called after setContentView.
+        getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
+                R.drawable.ic_media_route_off_holo_dark);
+
+        mAdapter = new RouteAdapter(getContext());
+        mListView = (ListView)findViewById(R.id.media_route_list);
+        mListView.setAdapter(mAdapter);
+        mListView.setOnItemClickListener(mAdapter);
+        mListView.setEmptyView(findViewById(android.R.id.empty));
+
+        mExtendedSettingsButton = (Button)findViewById(R.id.media_route_extended_settings_button);
+        updateExtendedSettingsButton();
+    }
+
+    private void updateExtendedSettingsButton() {
+        if (mExtendedSettingsButton != null) {
+            mExtendedSettingsButton.setOnClickListener(mExtendedSettingsClickListener);
+            mExtendedSettingsButton.setVisibility(
+                    mExtendedSettingsClickListener != null ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mAttachedToWindow = true;
+        mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+        refreshRoutes();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mAttachedToWindow = false;
+        mRouter.removeCallback(mCallback);
+
+        super.onDetachedFromWindow();
+    }
+
+    /**
+     * Refreshes the list of routes that are shown in the chooser dialog.
+     */
+    public void refreshRoutes() {
+        if (mAttachedToWindow) {
+            mAdapter.update();
+        }
+    }
+
+    private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
+            implements ListView.OnItemClickListener {
+        private final LayoutInflater mInflater;
+
+        public RouteAdapter(Context context) {
+            super(context, 0);
+            mInflater = LayoutInflater.from(context);
+        }
+
+        public void update() {
+            clear();
+            final int count = mRouter.getRouteCount();
+            for (int i = 0; i < count; i++) {
+                MediaRouter.RouteInfo route = mRouter.getRouteAt(i);
+                if (onFilterRoute(route)) {
+                    add(route);
+                }
+            }
+            sort(RouteComparator.sInstance);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return getItem(position).isEnabled();
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view = convertView;
+            if (view == null) {
+                view = mInflater.inflate(R.layout.media_route_list_item, parent, false);
+            }
+            MediaRouter.RouteInfo route = getItem(position);
+            TextView text1 = (TextView)view.findViewById(android.R.id.text1);
+            TextView text2 = (TextView)view.findViewById(android.R.id.text2);
+            text1.setText(route.getName());
+            CharSequence description = route.getDescription();
+            if (TextUtils.isEmpty(description)) {
+                text2.setVisibility(View.GONE);
+                text2.setText("");
+            } else {
+                text2.setVisibility(View.VISIBLE);
+                text2.setText(description);
+            }
+            view.setEnabled(route.isEnabled());
+            return view;
+        }
+
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            MediaRouter.RouteInfo route = getItem(position);
+            if (route.isEnabled()) {
+                route.select();
+                dismiss();
+            }
+        }
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
+        @Override
+        public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoutes();
+        }
+
+        @Override
+        public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoutes();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoutes();
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+            dismiss();
+        }
+    }
+
+    private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
+        public static final RouteComparator sInstance = new RouteComparator();
+
+        @Override
+        public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
+            return lhs.getName().toString().compareTo(rhs.getName().toString());
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index e300021..ae362af 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,675 +16,86 @@
 
 package com.android.internal.app;
 
-import com.android.internal.R;
-
-import android.app.Activity;
 import android.app.Dialog;
 import android.app.DialogFragment;
-import android.app.MediaRouteActionProvider;
-import android.app.MediaRouteButton;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.hardware.display.DisplayManager;
-import android.media.MediaRouter;
-import android.media.MediaRouter.RouteCategory;
-import android.media.MediaRouter.RouteGroup;
-import android.media.MediaRouter.RouteInfo;
 import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.CheckBox;
-import android.widget.Checkable;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
+import android.view.View.OnClickListener;
 
 /**
- * This class implements the route chooser dialog for {@link MediaRouter}.
+ * Media route chooser dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteChooserDialog}.  The application may subclass
+ * this dialog fragment to customize the media route chooser dialog.
+ * </p>
  *
- * @see MediaRouteButton
- * @see MediaRouteActionProvider
+ * TODO: Move this back into the API, as in the support library media router.
  */
 public class MediaRouteChooserDialogFragment extends DialogFragment {
-    private static final String TAG = "MediaRouteChooserDialogFragment";
-    public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment";
+    private final String ARGUMENT_ROUTE_TYPES = "routeTypes";
 
-    private static final int[] ITEM_LAYOUTS = new int[] {
-        R.layout.media_route_list_item_top_header,
-        R.layout.media_route_list_item_section_header,
-        R.layout.media_route_list_item,
-        R.layout.media_route_list_item_checkable,
-        R.layout.media_route_list_item_collapse_group
-    };
+    private View.OnClickListener mExtendedSettingsClickListener;
 
-    MediaRouter mRouter;
-    private int mRouteTypes;
-
-    private LayoutInflater mInflater;
-    private LauncherListener mLauncherListener;
-    private View.OnClickListener mExtendedSettingsListener;
-    private RouteAdapter mAdapter;
-    private ListView mListView;
-    private SeekBar mVolumeSlider;
-    private ImageView mVolumeIcon;
-
-    final RouteComparator mComparator = new RouteComparator();
-    final MediaRouterCallback mCallback = new MediaRouterCallback();
-    private boolean mIgnoreSliderVolumeChanges;
-    private boolean mIgnoreCallbackVolumeChanges;
-
+    /**
+     * Creates a media route chooser dialog fragment.
+     * <p>
+     * All subclasses of this class must also possess a default constructor.
+     * </p>
+     */
     public MediaRouteChooserDialogFragment() {
-        setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog);
+        setCancelable(true);
+        setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog);
     }
 
-    public void setLauncherListener(LauncherListener listener) {
-        mLauncherListener = listener;
-    }
-
-    @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE);
-        mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
-    }
-
-    @Override
-    public void onDetach() {
-        super.onDetach();
-        if (mLauncherListener != null) {
-            mLauncherListener.onDetached(this);
-        }
-        if (mAdapter != null) {
-            mAdapter = null;
-        }
-        mInflater = null;
-        mRouter.removeCallback(mCallback);
-        mRouter = null;
-    }
-
-    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
-        mExtendedSettingsListener = listener;
+    public int getRouteTypes() {
+        Bundle args = getArguments();
+        return args != null ? args.getInt(ARGUMENT_ROUTE_TYPES) : 0;
     }
 
     public void setRouteTypes(int types) {
-        mRouteTypes = types;
-    }
-
-    void updateVolume() {
-        if (mRouter == null) return;
-
-        final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-        mVolumeIcon.setImageResource(selectedRoute == null ||
-                selectedRoute.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_LOCAL ?
-                R.drawable.ic_audio_vol : R.drawable.ic_media_route_on_holo_dark);
-
-        mIgnoreSliderVolumeChanges = true;
-
-        if (selectedRoute == null ||
-                selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_FIXED) {
-            // Disable the slider and show it at max volume.
-            mVolumeSlider.setMax(1);
-            mVolumeSlider.setProgress(1);
-            mVolumeSlider.setEnabled(false);
-        } else {
-            mVolumeSlider.setEnabled(true);
-            mVolumeSlider.setMax(selectedRoute.getVolumeMax());
-            mVolumeSlider.setProgress(selectedRoute.getVolume());
-        }
-
-        mIgnoreSliderVolumeChanges = false;
-    }
-
-    void changeVolume(int newValue) {
-        if (mIgnoreSliderVolumeChanges) return;
-
-        final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-        if (selectedRoute != null &&
-                selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
-            final int maxVolume = selectedRoute.getVolumeMax();
-            newValue = Math.max(0, Math.min(newValue, maxVolume));
-            selectedRoute.requestSetVolume(newValue);
-        }
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        mInflater = inflater;
-        final View layout = inflater.inflate(R.layout.media_route_chooser_layout, container, false);
-
-        mVolumeIcon = (ImageView) layout.findViewById(R.id.volume_icon);
-        mVolumeSlider = (SeekBar) layout.findViewById(R.id.volume_slider);
-        updateVolume();
-        mVolumeSlider.setOnSeekBarChangeListener(new VolumeSliderChangeListener());
-
-        if (mExtendedSettingsListener != null) {
-            final View extendedSettingsButton = layout.findViewById(R.id.extended_settings);
-            extendedSettingsButton.setVisibility(View.VISIBLE);
-            extendedSettingsButton.setOnClickListener(mExtendedSettingsListener);
-        }
-
-        final ListView list = (ListView) layout.findViewById(R.id.list);
-        list.setItemsCanFocus(true);
-        list.setAdapter(mAdapter = new RouteAdapter());
-        list.setOnItemClickListener(mAdapter);
-
-        mListView = list;
-
-        mAdapter.scrollToSelectedItem();
-
-        return layout;
-    }
-
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        return new RouteChooserDialog(getActivity(), getTheme());
-    }
-
-    private static class ViewHolder {
-        public TextView text1;
-        public TextView text2;
-        public ImageView icon;
-        public ImageButton expandGroupButton;
-        public RouteAdapter.ExpandGroupListener expandGroupListener;
-        public int position;
-        public CheckBox check;
-    }
-
-    private class RouteAdapter extends BaseAdapter implements ListView.OnItemClickListener {
-        private static final int VIEW_TOP_HEADER = 0;
-        private static final int VIEW_SECTION_HEADER = 1;
-        private static final int VIEW_ROUTE = 2;
-        private static final int VIEW_GROUPING_ROUTE = 3;
-        private static final int VIEW_GROUPING_DONE = 4;
-
-        private int mSelectedItemPosition = -1;
-        private final ArrayList<Object> mItems = new ArrayList<Object>();
-
-        private RouteCategory mCategoryEditingGroups;
-        private RouteGroup mEditingGroup;
-
-        // Temporary lists for manipulation
-        private final ArrayList<RouteInfo> mCatRouteList = new ArrayList<RouteInfo>();
-        private final ArrayList<RouteInfo> mSortRouteList = new ArrayList<RouteInfo>();
-
-        private boolean mIgnoreUpdates;
-
-        RouteAdapter() {
-            update();
-        }
-
-        void update() {
-            /*
-             * This is kind of wacky, but our data sets are going to be
-             * fairly small on average. Ideally we should be able to do some of this stuff
-             * in-place instead.
-             *
-             * Basic idea: each entry in mItems represents an item in the list for quick access.
-             * Entries can be a RouteCategory (section header), a RouteInfo with a category of
-             * mCategoryEditingGroups (a flattened RouteInfo pulled out of its group, allowing
-             * the user to change the group),
-             */
-            if (mIgnoreUpdates) return;
-
-            mItems.clear();
-
-            final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-            mSelectedItemPosition = -1;
-
-            List<RouteInfo> routes;
-            final int catCount = mRouter.getCategoryCount();
-            for (int i = 0; i < catCount; i++) {
-                final RouteCategory cat = mRouter.getCategoryAt(i);
-                routes = cat.getRoutes(mCatRouteList);
-
-                if (!cat.isSystem()) {
-                    mItems.add(cat);
-                }
-
-                if (cat == mCategoryEditingGroups) {
-                    addGroupEditingCategoryRoutes(routes);
-                } else {
-                    addSelectableRoutes(selectedRoute, routes);
-                }
-
-                routes.clear();
+        if (types != getRouteTypes()) {
+            Bundle args = getArguments();
+            if (args == null) {
+                args = new Bundle();
             }
+            args.putInt(ARGUMENT_ROUTE_TYPES, types);
+            setArguments(args);
 
-            notifyDataSetChanged();
-            if (mListView != null && mSelectedItemPosition >= 0) {
-                mListView.setItemChecked(mSelectedItemPosition, true);
-            }
-        }
-
-        void scrollToEditingGroup() {
-            if (mCategoryEditingGroups == null || mListView == null) return;
-
-            int pos = 0;
-            int bound = 0;
-            final int itemCount = mItems.size();
-            for (int i = 0; i < itemCount; i++) {
-                final Object item = mItems.get(i);
-                if (item != null && item == mCategoryEditingGroups) {
-                    bound = i;
-                }
-                if (item == null) {
-                    pos = i;
-                    break; // this is always below the category header; we can stop here.
-                }
-            }
-
-            mListView.smoothScrollToPosition(pos, bound);
-        }
-
-        void scrollToSelectedItem() {
-            if (mListView == null || mSelectedItemPosition < 0) return;
-
-            mListView.smoothScrollToPosition(mSelectedItemPosition);
-        }
-
-        void addSelectableRoutes(RouteInfo selectedRoute, List<RouteInfo> from) {
-            final int routeCount = from.size();
-            for (int j = 0; j < routeCount; j++) {
-                final RouteInfo info = from.get(j);
-                if (info == selectedRoute) {
-                    mSelectedItemPosition = mItems.size();
-                }
-                mItems.add(info);
-            }
-        }
-
-        void addGroupEditingCategoryRoutes(List<RouteInfo> from) {
-            // Unpack groups and flatten for presentation
-            // mSortRouteList will always be empty here.
-            final int topCount = from.size();
-            for (int i = 0; i < topCount; i++) {
-                final RouteInfo route = from.get(i);
-                final RouteGroup group = route.getGroup();
-                if (group == route) {
-                    // This is a group, unpack it.
-                    final int groupCount = group.getRouteCount();
-                    for (int j = 0; j < groupCount; j++) {
-                        final RouteInfo innerRoute = group.getRouteAt(j);
-                        mSortRouteList.add(innerRoute);
-                    }
-                } else {
-                    mSortRouteList.add(route);
-                }
-            }
-            // Sort by name. This will keep the route positions relatively stable even though they
-            // will be repeatedly added and removed.
-            Collections.sort(mSortRouteList, mComparator);
-
-            mItems.addAll(mSortRouteList);
-            mSortRouteList.clear();
-
-            mItems.add(null); // Sentinel reserving space for the "done" button.
-        }
-
-        @Override
-        public int getCount() {
-            return mItems.size();
-        }
-
-        @Override
-        public int getViewTypeCount() {
-            return 5;
-        }
-
-        @Override
-        public int getItemViewType(int position) {
-            final Object item = getItem(position);
-            if (item instanceof RouteCategory) {
-                return position == 0 ? VIEW_TOP_HEADER : VIEW_SECTION_HEADER;
-            } else if (item == null) {
-                return VIEW_GROUPING_DONE;
-            } else {
-                final RouteInfo info = (RouteInfo) item;
-                if (info.getCategory() == mCategoryEditingGroups) {
-                    return VIEW_GROUPING_ROUTE;
-                }
-                return VIEW_ROUTE;
-            }
-        }
-
-        @Override
-        public boolean areAllItemsEnabled() {
-            return false;
-        }
-
-        @Override
-        public boolean isEnabled(int position) {
-            switch (getItemViewType(position)) {
-                case VIEW_ROUTE:
-                    return ((RouteInfo) mItems.get(position)).isEnabled();
-                case VIEW_GROUPING_ROUTE:
-                case VIEW_GROUPING_DONE:
-                    return true;
-                default:
-                    return false;
-            }
-        }
-
-        @Override
-        public Object getItem(int position) {
-            return mItems.get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final int viewType = getItemViewType(position);
-
-            ViewHolder holder;
-            if (convertView == null) {
-                convertView = mInflater.inflate(ITEM_LAYOUTS[viewType], parent, false);
-                holder = new ViewHolder();
-                holder.position = position;
-                holder.text1 = (TextView) convertView.findViewById(R.id.text1);
-                holder.text2 = (TextView) convertView.findViewById(R.id.text2);
-                holder.icon = (ImageView) convertView.findViewById(R.id.icon);
-                holder.check = (CheckBox) convertView.findViewById(R.id.check);
-                holder.expandGroupButton = (ImageButton) convertView.findViewById(
-                        R.id.expand_button);
-                if (holder.expandGroupButton != null) {
-                    holder.expandGroupListener = new ExpandGroupListener();
-                    holder.expandGroupButton.setOnClickListener(holder.expandGroupListener);
-                }
-
-                final View fview = convertView;
-                final ListView list = (ListView) parent;
-                final ViewHolder fholder = holder;
-                convertView.setOnClickListener(new View.OnClickListener() {
-                    @Override public void onClick(View v) {
-                        list.performItemClick(fview, fholder.position, 0);
-                    }
-                });
-                convertView.setTag(holder);
-            } else {
-                holder = (ViewHolder) convertView.getTag();
-                holder.position = position;
-            }
-
-            switch (viewType) {
-                case VIEW_ROUTE:
-                case VIEW_GROUPING_ROUTE:
-                    bindItemView(position, holder);
-                    break;
-                case VIEW_SECTION_HEADER:
-                case VIEW_TOP_HEADER:
-                    bindHeaderView(position, holder);
-                    break;
-            }
-
-            convertView.setActivated(position == mSelectedItemPosition);
-            convertView.setEnabled(isEnabled(position));
-
-            return convertView;
-        }
-
-        void bindItemView(int position, ViewHolder holder) {
-            RouteInfo info = (RouteInfo) mItems.get(position);
-            holder.text1.setText(info.getName(getActivity()));
-            final CharSequence status = info.getStatus();
-            if (TextUtils.isEmpty(status)) {
-                holder.text2.setVisibility(View.GONE);
-            } else {
-                holder.text2.setVisibility(View.VISIBLE);
-                holder.text2.setText(status);
-            }
-            Drawable icon = info.getIconDrawable();
-            if (icon != null) {
-                // Make sure we have a fresh drawable where it doesn't matter if we mutate it
-                icon = icon.getConstantState().newDrawable(getResources());
-            }
-            holder.icon.setImageDrawable(icon);
-            holder.icon.setVisibility(icon != null ? View.VISIBLE : View.GONE);
-
-            RouteCategory cat = info.getCategory();
-            boolean canGroup = false;
-            if (cat == mCategoryEditingGroups) {
-                RouteGroup group = info.getGroup();
-                holder.check.setEnabled(group.getRouteCount() > 1);
-                holder.check.setChecked(group == mEditingGroup);
-            } else {
-                if (cat.isGroupable()) {
-                    final RouteGroup group = (RouteGroup) info;
-                    canGroup = group.getRouteCount() > 1 ||
-                            getItemViewType(position - 1) == VIEW_ROUTE ||
-                            (position < getCount() - 1 &&
-                                    getItemViewType(position + 1) == VIEW_ROUTE);
-                }
-            }
-
-            if (holder.expandGroupButton != null) {
-                holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE);
-                holder.expandGroupListener.position = position;
-            }
-        }
-
-        void bindHeaderView(int position, ViewHolder holder) {
-            RouteCategory cat = (RouteCategory) mItems.get(position);
-            holder.text1.setText(cat.getName(getActivity()));
-        }
-
-        @Override
-        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            final int type = getItemViewType(position);
-            if (type == VIEW_SECTION_HEADER || type == VIEW_TOP_HEADER) {
-                return;
-            } else if (type == VIEW_GROUPING_DONE) {
-                finishGrouping();
-                return;
-            } else {
-                final Object item = getItem(position);
-                if (!(item instanceof RouteInfo)) {
-                    // Oops. Stale event running around? Skip it.
-                    return;
-                }
-
-                final RouteInfo route = (RouteInfo) item;
-                if (type == VIEW_ROUTE) {
-                    mRouter.selectRouteInt(mRouteTypes, route);
-                    dismiss();
-                } else if (type == VIEW_GROUPING_ROUTE) {
-                    final Checkable c = (Checkable) view;
-                    final boolean wasChecked = c.isChecked();
-
-                    mIgnoreUpdates = true;
-                    RouteGroup oldGroup = route.getGroup();
-                    if (!wasChecked && oldGroup != mEditingGroup) {
-                        // Assumption: in a groupable category oldGroup will never be null.
-                        if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) {
-                            // Old group was selected but is now empty. Select the group
-                            // we're manipulating since that's where the last route went.
-                            mRouter.selectRouteInt(mRouteTypes, mEditingGroup);
-                        }
-                        oldGroup.removeRoute(route);
-                        mEditingGroup.addRoute(route);
-                        c.setChecked(true);
-                    } else if (wasChecked && mEditingGroup.getRouteCount() > 1) {
-                        mEditingGroup.removeRoute(route);
-
-                        // In a groupable category this will add
-                        // the route into its own new group.
-                        mRouter.addRouteInt(route);
-                    }
-                    mIgnoreUpdates = false;
-                    update();
-                }
-            }
-        }
-
-        boolean isGrouping() {
-            return mCategoryEditingGroups != null;
-        }
-
-        void finishGrouping() {
-            mCategoryEditingGroups = null;
-            mEditingGroup = null;
-            getDialog().setCanceledOnTouchOutside(true);
-            update();
-            scrollToSelectedItem();
-        }
-
-        class ExpandGroupListener implements View.OnClickListener {
-            int position;
-
-            @Override
-            public void onClick(View v) {
-                // Assumption: this is only available for the user to click if we're presenting
-                // a groupable category, where every top-level route in the category is a group.
-                final RouteGroup group = (RouteGroup) getItem(position);
-                mEditingGroup = group;
-                mCategoryEditingGroups = group.getCategory();
-                getDialog().setCanceledOnTouchOutside(false);
-                mRouter.selectRouteInt(mRouteTypes, mEditingGroup);
-                update();
-                scrollToEditingGroup();
+            MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog();
+            if (dialog != null) {
+                dialog.setRouteTypes(types);
             }
         }
     }
 
-    class MediaRouterCallback extends MediaRouter.Callback {
-        @Override
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
-            mAdapter.update();
-            updateVolume();
-        }
+    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+        if (listener != mExtendedSettingsClickListener) {
+            mExtendedSettingsClickListener = listener;
 
-        @Override
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            if (info == mAdapter.mEditingGroup) {
-                mAdapter.finishGrouping();
-            }
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo info) {
-            mAdapter.notifyDataSetChanged();
-        }
-
-        @Override
-        public void onRouteGrouped(MediaRouter router, RouteInfo info,
-                RouteGroup group, int index) {
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
-            if (!mIgnoreCallbackVolumeChanges) {
-                updateVolume();
-            }
-        }
-    }
-
-    class RouteComparator implements Comparator<RouteInfo> {
-        @Override
-        public int compare(RouteInfo lhs, RouteInfo rhs) {
-            return lhs.getName(getActivity()).toString()
-                    .compareTo(rhs.getName(getActivity()).toString());
-        }
-    }
-
-    class RouteChooserDialog extends Dialog {
-        public RouteChooserDialog(Context context, int theme) {
-            super(context, theme);
-        }
-
-        @Override
-        public void onBackPressed() {
-            if (mAdapter != null && mAdapter.isGrouping()) {
-                mAdapter.finishGrouping();
-            } else {
-                super.onBackPressed();
-            }
-        }
-        
-        public boolean onKeyDown(int keyCode, KeyEvent event) {
-            if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) {
-                final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-                if (selectedRoute != null) {
-                    selectedRoute.requestUpdateVolume(-1);
-                    return true;
-                }
-            } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) {
-                final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-                if (selectedRoute != null) {
-                    mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(1);
-                    return true;
-                }
-            }
-            return super.onKeyDown(keyCode, event);
-        }
-
-        public boolean onKeyUp(int keyCode, KeyEvent event) {
-            if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) {
-                return true;
-            } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) {
-                return true;
-            } else {
-                return super.onKeyUp(keyCode, event);
+            MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog();
+            if (dialog != null) {
+                dialog.setExtendedSettingsClickListener(listener);
             }
         }
     }
 
     /**
-     * Implemented by the MediaRouteButton that launched this dialog
+     * Called when the chooser dialog is being created.
+     * <p>
+     * Subclasses may override this method to customize the dialog.
+     * </p>
      */
-    public interface LauncherListener {
-        public void onDetached(MediaRouteChooserDialogFragment detachedFragment);
+    public MediaRouteChooserDialog onCreateChooserDialog(
+            Context context, Bundle savedInstanceState) {
+        return new MediaRouteChooserDialog(context, getTheme());
     }
 
-    class VolumeSliderChangeListener implements SeekBar.OnSeekBarChangeListener {
-
-        @Override
-        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-            changeVolume(progress);
-        }
-
-        @Override
-        public void onStartTrackingTouch(SeekBar seekBar) {
-            mIgnoreCallbackVolumeChanges = true;
-        }
-
-        @Override
-        public void onStopTrackingTouch(SeekBar seekBar) {
-            mIgnoreCallbackVolumeChanges = false;
-            updateVolume();
-        }
-
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        MediaRouteChooserDialog dialog = onCreateChooserDialog(getActivity(), savedInstanceState);
+        dialog.setRouteTypes(getRouteTypes());
+        dialog.setExtendedSettingsClickListener(mExtendedSettingsClickListener);
+        return dialog;
     }
 }
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
new file mode 100644
index 0000000..8fc99c7
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+import android.app.MediaRouteActionProvider;
+import android.app.MediaRouteButton;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteGroup;
+import android.media.MediaRouter.RouteInfo;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+/**
+ * This class implements the route controller dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to control or disconnect from the currently selected route.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ *
+ * TODO: Move this back into the API, as in the support library media router.
+ */
+public class MediaRouteControllerDialog extends Dialog {
+    // Time to wait before updating the volume when the user lets go of the seek bar
+    // to allow the route provider time to propagate the change and publish a new
+    // route descriptor.
+    private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
+
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+    private final MediaRouter.RouteInfo mRoute;
+
+    private boolean mCreated;
+    private Drawable mMediaRouteConnectingDrawable;
+    private Drawable mMediaRouteOnDrawable;
+    private Drawable mCurrentIconDrawable;
+
+    private boolean mVolumeControlEnabled = true;
+    private LinearLayout mVolumeLayout;
+    private SeekBar mVolumeSlider;
+    private boolean mVolumeSliderTouched;
+
+    private View mControlView;
+
+    private Button mDisconnectButton;
+
+    public MediaRouteControllerDialog(Context context, int theme) {
+        super(context, theme);
+
+        mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mCallback = new MediaRouterCallback();
+        mRoute = mRouter.getSelectedRoute();
+    }
+
+    /**
+     * Gets the route that this dialog is controlling.
+     */
+    public MediaRouter.RouteInfo getRoute() {
+        return mRoute;
+    }
+
+    /**
+     * Provides the subclass an opportunity to create a view that will
+     * be included within the body of the dialog to offer additional media controls
+     * for the currently playing content.
+     *
+     * @param savedInstanceState The dialog's saved instance state.
+     * @return The media control view, or null if none.
+     */
+    public View onCreateMediaControlView(Bundle savedInstanceState) {
+        return null;
+    }
+
+    /**
+     * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}.
+     *
+     * @return The media control view, or null if none.
+     */
+    public View getMediaControlView() {
+        return mControlView;
+    }
+
+    /**
+     * Sets whether to enable the volume slider and volume control using the volume keys
+     * when the route supports it.
+     * <p>
+     * The default value is true.
+     * </p>
+     */
+    public void setVolumeControlEnabled(boolean enable) {
+        if (mVolumeControlEnabled != enable) {
+            mVolumeControlEnabled = enable;
+            if (mCreated) {
+                updateVolume();
+            }
+        }
+    }
+
+    /**
+     * Returns whether to enable the volume slider and volume control using the volume keys
+     * when the route supports it.
+     */
+    public boolean isVolumeControlEnabled() {
+        return mVolumeControlEnabled;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
+
+        setContentView(R.layout.media_route_controller_dialog);
+
+        mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout);
+        mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider);
+        mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            private final Runnable mStopTrackingTouch = new Runnable() {
+                @Override
+                public void run() {
+                    if (mVolumeSliderTouched) {
+                        mVolumeSliderTouched = false;
+                        updateVolume();
+                    }
+                }
+            };
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+                if (mVolumeSliderTouched) {
+                    mVolumeSlider.removeCallbacks(mStopTrackingTouch);
+                } else {
+                    mVolumeSliderTouched = true;
+                }
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                // Defer resetting mVolumeSliderTouched to allow the media route provider
+                // a little time to settle into its new state and publish the final
+                // volume update.
+                mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
+            }
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (fromUser) {
+                    mRoute.requestSetVolume(progress);
+                }
+            }
+        });
+
+        mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button);
+        mDisconnectButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mRoute.isSelected()) {
+                    mRouter.getDefaultRoute().select();
+                }
+                dismiss();
+            }
+        });
+
+        mCreated = true;
+        if (update()) {
+            mControlView = onCreateMediaControlView(savedInstanceState);
+            FrameLayout controlFrame =
+                    (FrameLayout)findViewById(R.id.media_route_control_frame);
+            if (mControlView != null) {
+                controlFrame.addView(mControlView);
+                controlFrame.setVisibility(View.VISIBLE);
+            } else {
+                controlFrame.setVisibility(View.GONE);
+            }
+        }
+    }
+
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
+        update();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mRouter.removeCallback(mCallback);
+
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+            mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    private boolean update() {
+        if (!mRoute.isSelected() || mRoute.isDefault()) {
+            dismiss();
+            return false;
+        }
+
+        setTitle(mRoute.getName());
+        updateVolume();
+
+        Drawable icon = getIconDrawable();
+        if (icon != mCurrentIconDrawable) {
+            mCurrentIconDrawable = icon;
+            getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon);
+        }
+        return true;
+    }
+
+    private Drawable getIconDrawable() {
+        if (mRoute.isConnecting()) {
+            if (mMediaRouteConnectingDrawable == null) {
+                mMediaRouteConnectingDrawable = getContext().getResources().getDrawable(
+                        R.drawable.ic_media_route_connecting_holo_dark);
+            }
+            return mMediaRouteConnectingDrawable;
+        } else {
+            if (mMediaRouteOnDrawable == null) {
+                mMediaRouteOnDrawable = getContext().getResources().getDrawable(
+                        R.drawable.ic_media_route_on_holo_dark);
+            }
+            return mMediaRouteOnDrawable;
+        }
+    }
+
+    private void updateVolume() {
+        if (!mVolumeSliderTouched) {
+            if (isVolumeControlAvailable()) {
+                mVolumeLayout.setVisibility(View.VISIBLE);
+                mVolumeSlider.setMax(mRoute.getVolumeMax());
+                mVolumeSlider.setProgress(mRoute.getVolume());
+            } else {
+                mVolumeLayout.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    private boolean isVolumeControlAvailable() {
+        return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
+                MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
+        @Override
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+            update();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+            update();
+        }
+
+        @Override
+        public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+            if (route == mRoute) {
+                updateVolume();
+            }
+        }
+
+        @Override
+        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
+                int index) {
+            update();
+        }
+
+        @Override
+        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
+            update();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java
new file mode 100644
index 0000000..108e81f
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * Media route controller dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteControllerDialog}.  The application may subclass
+ * this dialog fragment to customize the media route controller dialog.
+ * </p>
+ *
+ * TODO: Move this back into the API, as in the support library media router.
+ */
+public class MediaRouteControllerDialogFragment extends DialogFragment {
+    /**
+     * Creates a media route controller dialog fragment.
+     * <p>
+     * All subclasses of this class must also possess a default constructor.
+     * </p>
+     */
+    public MediaRouteControllerDialogFragment() {
+        setCancelable(true);
+        setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog);
+    }
+
+    /**
+     * Called when the controller dialog is being created.
+     * <p>
+     * Subclasses may override this method to customize the dialog.
+     * </p>
+     */
+    public MediaRouteControllerDialog onCreateControllerDialog(
+            Context context, Bundle savedInstanceState) {
+        return new MediaRouteControllerDialog(context, getTheme());
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return onCreateControllerDialog(getActivity(), savedInstanceState);
+    }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
new file mode 100644
index 0000000..fad7fd4
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.media.MediaRouter;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * Shows media route dialog as appropriate.
+ * @hide
+ */
+public abstract class MediaRouteDialogPresenter {
+    private static final String TAG = "MediaRouter";
+
+    private static final String CHOOSER_FRAGMENT_TAG =
+            "android.app.MediaRouteButton:MediaRouteChooserDialogFragment";
+    private static final String CONTROLLER_FRAGMENT_TAG =
+            "android.app.MediaRouteButton:MediaRouteControllerDialogFragment";
+
+    public static DialogFragment showDialogFragment(Activity activity,
+            int routeTypes, View.OnClickListener extendedSettingsClickListener) {
+        final MediaRouter router = (MediaRouter)activity.getSystemService(
+                Context.MEDIA_ROUTER_SERVICE);
+        final FragmentManager fm = activity.getFragmentManager();
+
+        MediaRouter.RouteInfo route = router.getSelectedRoute();
+        if (route.isDefault() || !route.matchesTypes(routeTypes)) {
+            if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) {
+                Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
+                return null;
+            }
+            MediaRouteChooserDialogFragment f = new MediaRouteChooserDialogFragment();
+            f.setRouteTypes(routeTypes);
+            f.setExtendedSettingsClickListener(extendedSettingsClickListener);
+            f.show(fm, CHOOSER_FRAGMENT_TAG);
+            return f;
+        } else {
+            if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) {
+                Log.w(TAG, "showDialog(): Route controller dialog already showing!");
+                return null;
+            }
+            MediaRouteControllerDialogFragment f = new MediaRouteControllerDialogFragment();
+            f.show(fm, CONTROLLER_FRAGMENT_TAG);
+            return f;
+        }
+    }
+
+    public static Dialog createDialog(Context context,
+            int routeTypes, View.OnClickListener extendedSettingsClickListener) {
+        final MediaRouter router = (MediaRouter)context.getSystemService(
+                Context.MEDIA_ROUTER_SERVICE);
+
+        MediaRouter.RouteInfo route = router.getSelectedRoute();
+        if (route.isDefault() || !route.matchesTypes(routeTypes)) {
+            final MediaRouteChooserDialog d = new MediaRouteChooserDialog(
+                    context, android.R.style.Theme_DeviceDefault_Dialog);
+            d.setRouteTypes(routeTypes);
+            d.setExtendedSettingsClickListener(extendedSettingsClickListener);
+            return d;
+        } else {
+            MediaRouteControllerDialog d = new MediaRouteControllerDialog(
+                    context, android.R.style.Theme_DeviceDefault_Dialog);
+            return d;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 20b8c95..0aad0b5 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -1004,7 +1004,7 @@
                 for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) {
                     ProcessState ps = pkgState.mProcesses.valueAt(iproc);
                     if (ps.isInUse() || ps.mCommonProcess.isInUse()) {
-                        pkgState.mProcesses.valueAt(iproc).resetSafely(now);
+                        ps.resetSafely(now);
                     } else {
                         pkgState.mProcesses.valueAt(iproc).makeDead();
                         pkgState.mProcesses.removeAt(iproc);
@@ -1013,7 +1013,7 @@
                 for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) {
                     ServiceState ss = pkgState.mServices.valueAt(isvc);
                     if (ss.isInUse()) {
-                        pkgState.mServices.valueAt(isvc).resetSafely(now);
+                        ss.resetSafely(now);
                     } else {
                         pkgState.mServices.removeAt(isvc);
                     }
@@ -1758,21 +1758,34 @@
                 mStartTime, now);
         ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
         boolean printedHeader = false;
+        boolean sepNeeded = false;
         for (int ip=0; ip<pkgMap.size(); ip++) {
-            String pkgName = pkgMap.keyAt(ip);
-            if (reqPackage != null && !reqPackage.equals(pkgName)) {
-                continue;
-            }
-            SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+            final String pkgName = pkgMap.keyAt(ip);
+            final SparseArray<PackageState> uids = pkgMap.valueAt(ip);
             for (int iu=0; iu<uids.size(); iu++) {
-                int uid = uids.keyAt(iu);
-                PackageState pkgState = uids.valueAt(iu);
+                final int uid = uids.keyAt(iu);
+                final PackageState pkgState = uids.valueAt(iu);
                 final int NPROCS = pkgState.mProcesses.size();
                 final int NSRVS = pkgState.mServices.size();
+                final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+                if (!pkgMatch) {
+                    boolean procMatch = false;
+                    for (int iproc=0; iproc<NPROCS; iproc++) {
+                        ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+                        if (reqPackage.equals(proc.mName)) {
+                            procMatch = true;
+                            break;
+                        }
+                    }
+                    if (!procMatch) {
+                        continue;
+                    }
+                }
                 if (NPROCS > 0 || NSRVS > 0) {
                     if (!printedHeader) {
                         pw.println("Per-Package Stats:");
                         printedHeader = true;
+                        sepNeeded = true;
                     }
                     pw.print("  * "); pw.print(pkgName); pw.print(" / ");
                             UserHandle.formatUid(pw, uid); pw.println(":");
@@ -1780,6 +1793,9 @@
                 if (!dumpSummary || dumpAll) {
                     for (int iproc=0; iproc<NPROCS; iproc++) {
                         ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+                        if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+                            continue;
+                        }
                         if (activeOnly && !proc.isInUse()) {
                             pw.print("      (Not active: ");
                                     pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")");
@@ -1787,7 +1803,11 @@
                         }
                         pw.print("      Process ");
                         pw.print(pkgState.mProcesses.keyAt(iproc));
-                        pw.print(" (");
+                        if (proc.mCommonProcess.mMultiPackage) {
+                            pw.print(" (multi, ");
+                        } else {
+                            pw.print(" (unique, ");
+                        }
                         pw.print(proc.mDurationsTableSize);
                         pw.print(" entries)");
                         pw.println(":");
@@ -1801,6 +1821,9 @@
                     ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
                     for (int iproc=0; iproc<NPROCS; iproc++) {
                         ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+                        if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+                            continue;
+                        }
                         if (activeOnly && !proc.isInUse()) {
                             continue;
                         }
@@ -1811,6 +1834,9 @@
                 }
                 for (int isvc=0; isvc<NSRVS; isvc++) {
                     ServiceState svc = pkgState.mServices.valueAt(isvc);
+                    if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) {
+                        continue;
+                    }
                     if (activeOnly && !svc.isInUse()) {
                         pw.print("      (Not active: ");
                                 pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")");
@@ -1840,64 +1866,73 @@
                         if (svc.mOwner != null) {
                             pw.print("        mOwner="); pw.println(svc.mOwner);
                         }
+                        if (svc.mStarted || svc.mRestarting) {
+                            pw.print("        mStarted="); pw.print(svc.mStarted);
+                            pw.print(" mRestarting="); pw.println(svc.mRestarting);
+                        }
                     }
                 }
             }
         }
 
-        if (reqPackage == null) {
-            ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
-            printedHeader = false;
-            int numShownProcs = 0, numTotalProcs = 0;
-            for (int ip=0; ip<procMap.size(); ip++) {
-                String procName = procMap.keyAt(ip);
-                SparseArray<ProcessState> uids = procMap.valueAt(ip);
-                for (int iu=0; iu<uids.size(); iu++) {
-                    int uid = uids.keyAt(iu);
-                    numTotalProcs++;
-                    ProcessState proc = uids.valueAt(iu);
-                    if (proc.mDurationsTableSize == 0 && proc.mCurState == STATE_NOTHING
-                            && proc.mPssTableSize == 0) {
-                        continue;
-                    }
-                    numShownProcs++;
-                    if (!printedHeader) {
-                        pw.println();
-                        pw.println("Per-Process Stats:");
-                        printedHeader = true;
-                    }
-                    if (activeOnly && !proc.isInUse()) {
-                        pw.print("      (Not active: "); pw.print(procName); pw.println(")");
-                        continue;
-                    }
-                    pw.print("  * "); pw.print(procName); pw.print(" / ");
-                            UserHandle.formatUid(pw, uid);
-                            pw.print(" ("); pw.print(proc.mDurationsTableSize);
-                            pw.print(" entries)"); pw.println(":");
-                    dumpProcessState(pw, "        ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
-                            ALL_PROC_STATES, now);
-                    dumpProcessPss(pw, "        ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
-                            ALL_PROC_STATES);
-                    if (dumpAll) {
-                        dumpProcessInternalLocked(pw, "        ", proc, dumpAll);
-                    }
+        ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+        printedHeader = false;
+        int numShownProcs = 0, numTotalProcs = 0;
+        for (int ip=0; ip<procMap.size(); ip++) {
+            String procName = procMap.keyAt(ip);
+            SparseArray<ProcessState> uids = procMap.valueAt(ip);
+            for (int iu=0; iu<uids.size(); iu++) {
+                int uid = uids.keyAt(iu);
+                numTotalProcs++;
+                ProcessState proc = uids.valueAt(iu);
+                if (proc.mDurationsTableSize == 0 && proc.mCurState == STATE_NOTHING
+                        && proc.mPssTableSize == 0) {
+                    continue;
                 }
+                if (!proc.mMultiPackage) {
+                    continue;
+                }
+                if (reqPackage != null && !reqPackage.equals(procName)
+                        && !reqPackage.equals(proc.mPackage)) {
+                    continue;
+                }
+                numShownProcs++;
+                if (sepNeeded) {
+                    pw.println();
+                }
+                sepNeeded = true;
+                if (!printedHeader) {
+                    pw.println("Multi-Package Common Processes:");
+                    printedHeader = true;
+                }
+                if (activeOnly && !proc.isInUse()) {
+                    pw.print("      (Not active: "); pw.print(procName); pw.println(")");
+                    continue;
+                }
+                pw.print("  * "); pw.print(procName); pw.print(" / ");
+                        UserHandle.formatUid(pw, uid);
+                        pw.print(" ("); pw.print(proc.mDurationsTableSize);
+                        pw.print(" entries)"); pw.println(":");
+                dumpProcessState(pw, "        ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+                        ALL_PROC_STATES, now);
+                dumpProcessPss(pw, "        ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+                        ALL_PROC_STATES);
+                dumpProcessInternalLocked(pw, "        ", proc, dumpAll);
             }
-            if (dumpAll) {
-                pw.println();
-                pw.print("  Total procs: "); pw.print(numShownProcs);
-                        pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total");
-            }
+        }
+        if (dumpAll) {
+            pw.println();
+            pw.print("  Total procs: "); pw.print(numShownProcs);
+                    pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total");
+        }
 
+        if (sepNeeded) {
             pw.println();
-            if (dumpSummary) {
-                pw.println("Summary:");
-                dumpSummaryLocked(pw, reqPackage, now, activeOnly);
-            } else {
-                dumpTotalsLocked(pw, now);
-            }
+        }
+        if (dumpSummary) {
+            pw.println("Summary:");
+            dumpSummaryLocked(pw, reqPackage, now, activeOnly);
         } else {
-            pw.println();
             dumpTotalsLocked(pw, now);
         }
 
@@ -2031,17 +2066,20 @@
     public ArrayList<ProcessState> collectProcessesLocked(int[] screenStates, int[] memStates,
             int[] procStates, int sortProcStates[], long now, String reqPackage,
             boolean activeOnly) {
-        ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>();
-        ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+        final ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>();
+        final ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
         for (int ip=0; ip<pkgMap.size(); ip++) {
-            if (reqPackage != null && !reqPackage.equals(pkgMap.keyAt(ip))) {
-                continue;
-            }
-            SparseArray<PackageState> procs = pkgMap.valueAt(ip);
+            final String pkgName = pkgMap.keyAt(ip);
+            final SparseArray<PackageState> procs = pkgMap.valueAt(ip);
             for (int iu=0; iu<procs.size(); iu++) {
-                PackageState state = procs.valueAt(iu);
-                for (int iproc=0; iproc<state.mProcesses.size(); iproc++) {
-                    ProcessState proc = state.mProcesses.valueAt(iproc);
+                final PackageState state = procs.valueAt(iu);
+                final int NPROCS = state.mProcesses.size();
+                final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+                for (int iproc=0; iproc<NPROCS; iproc++) {
+                    final ProcessState proc = state.mProcesses.valueAt(iproc);
+                    if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+                        continue;
+                    }
                     if (activeOnly && !proc.isInUse()) {
                         continue;
                     }
@@ -2601,23 +2639,35 @@
             }
         }
 
-        void incStartedServices(int memFactor, long now) {
+        void incStartedServices(int memFactor, long now, String serviceName) {
+            if (false) {
+                RuntimeException here = new RuntimeException("here");
+                here.fillInStackTrace();
+                Slog.d(TAG, "incStartedServices: " + this + " service=" + serviceName
+                        + " to " + (mNumStartedServices+1), here);
+            }
             if (mCommonProcess != this) {
-                mCommonProcess.incStartedServices(memFactor, now);
+                mCommonProcess.incStartedServices(memFactor, now, serviceName);
             }
             mNumStartedServices++;
             if (mNumStartedServices == 1 && mCurState == STATE_NOTHING) {
-                setState(STATE_NOTHING, memFactor, now, null);
+                setState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
             }
         }
 
-        void decStartedServices(int memFactor, long now) {
+        void decStartedServices(int memFactor, long now, String serviceName) {
+            if (false) {
+                RuntimeException here = new RuntimeException("here");
+                here.fillInStackTrace();
+                Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName
+                        + " to " + (mNumStartedServices-1), here);
+            }
             if (mCommonProcess != this) {
-                mCommonProcess.decStartedServices(memFactor, now);
+                mCommonProcess.decStartedServices(memFactor, now, serviceName);
             }
             mNumStartedServices--;
-            if (mNumStartedServices == 0 && mCurState == STATE_SERVICE_RESTARTING) {
-                setState(STATE_NOTHING, memFactor, now, null);
+            if (mNumStartedServices == 0 && (mCurState%STATE_COUNT) == STATE_SERVICE_RESTARTING) {
+                setState(STATE_NOTHING, now);
             } else if (mNumStartedServices < 0) {
                 Slog.wtfStack(TAG, "Proc started services underrun: pkg="
                         + mPackage + " uid=" + mUid + " name=" + mName);
@@ -2873,6 +2923,8 @@
         public int mRunState = STATE_NOTHING;
         long mRunStartTime;
 
+        boolean mStarted;
+        boolean mRestarting;
         int mStartedCount;
         public int mStartedState = STATE_NOTHING;
         long mStartedStartTime;
@@ -2902,10 +2954,9 @@
                     // There was already an old owner, reset this object for its
                     // new owner.
                     mOwner = newOwner;
-                    if (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING
-                            || mExecState != STATE_NOTHING) {
+                    if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING) {
                         long now = SystemClock.uptimeMillis();
-                        if (mStartedState != STATE_NOTHING) {
+                        if (mStarted) {
                             if (DEBUG) Slog.d(TAG, "Service has new owner " + newOwner
                                     + " from " + mOwner + " while started: pkg="
                                     + mPackage + " service=" + mName + " proc=" + mProc);
@@ -2931,10 +2982,9 @@
         public void clearCurrentOwner(Object owner, boolean silently) {
             if (mOwner == owner) {
                 mProc.decActiveServices(mName);
-                if (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING
-                        || mExecState != STATE_NOTHING) {
+                if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING) {
                     long now = SystemClock.uptimeMillis();
-                    if (mStartedState != STATE_NOTHING) {
+                    if (mStarted) {
                         if (!silently) {
                             Slog.wtfStack(TAG, "Service owner " + owner
                                     + " cleared while started: pkg=" + mPackage + " service="
@@ -2964,7 +3014,7 @@
         }
 
         public boolean isInUse() {
-            return mOwner != null;
+            return mOwner != null || mRestarting;
         }
 
         void add(ServiceState other) {
@@ -3042,7 +3092,18 @@
             if (mOwner == null) {
                 Slog.wtf(TAG, "Starting service " + this + " without owner");
             }
+            mStarted = started;
+            updateStartedState(memFactor, now);
+        }
+
+        public void setRestarting(boolean restarting, int memFactor, long now) {
+            mRestarting = restarting;
+            updateStartedState(memFactor, now);
+        }
+
+        void updateStartedState(int memFactor, long now) {
             final boolean wasStarted = mStartedState != STATE_NOTHING;
+            final boolean started = mStarted || mRestarting;
             final int state = started ? memFactor : STATE_NOTHING;
             if (mStartedState != state) {
                 if (mStartedState != STATE_NOTHING) {
@@ -3056,9 +3117,9 @@
                 mProc = mProc.pullFixedProc(mPackage);
                 if (wasStarted != started) {
                     if (started) {
-                        mProc.incStartedServices(memFactor, now);
+                        mProc.incStartedServices(memFactor, now, mName);
                     } else {
-                        mProc.decStartedServices(memFactor, now);
+                        mProc.decStartedServices(memFactor, now, mName);
                     }
                 }
                 updateRunning(memFactor, now);
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 5bfa1b2..1e37fd9 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -23,6 +23,12 @@
 
 /** {@hide} */
 interface IBackupTransport {
+    /**
+     * Ask the transport for the name under which it should be registered.  This will
+     * typically be its host service's component name, but need not be.
+     */
+    String name();
+
 	/**
 	 * Ask the transport for an Intent that can be used to launch any internal
 	 * configuration Activity that it wishes to present.  For example, the transport
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index eb2d1fe..494bc78 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -19,6 +19,7 @@
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.RestoreSet;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
@@ -71,6 +72,10 @@
         }
     }
 
+    public String name() {
+        return new ComponentName(mContext, this.getClass()).flattenToShortString();
+    }
+
     public Intent configurationIntent() {
         // The local transport is not user-configurable
         return null;
diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java
new file mode 100644
index 0000000..d05699a
--- /dev/null
+++ b/core/java/com/android/internal/backup/LocalTransportService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.backup;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class LocalTransportService extends Service {
+    private static LocalTransport sTransport = null;
+
+    @Override
+    public void onCreate() {
+        if (sTransport == null) {
+            sTransport = new LocalTransport(this);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return sTransport;
+    }
+}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index ab871fb..942995b 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -372,23 +372,25 @@
         } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
             String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
             mAppearingPackages = pkgList;
-            mChangeType = PACKAGE_TEMPORARY_CHANGE;
+            mChangeType = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
+                    ? PACKAGE_UPDATING : PACKAGE_TEMPORARY_CHANGE;
             mSomePackagesChanged = true;
             if (pkgList != null) {
                 onPackagesAvailable(pkgList);
                 for (int i=0; i<pkgList.length; i++) {
-                    onPackageAppeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE);
+                    onPackageAppeared(pkgList[i], mChangeType);
                 }
             }
         } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
             String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
             mDisappearingPackages = pkgList;
-            mChangeType = PACKAGE_TEMPORARY_CHANGE;
+            mChangeType = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
+                    ? PACKAGE_UPDATING : PACKAGE_TEMPORARY_CHANGE;
             mSomePackagesChanged = true;
             if (pkgList != null) {
                 onPackagesUnavailable(pkgList);
                 for (int i=0; i<pkgList.length; i++) {
-                    onPackageDisappeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE);
+                    onPackageDisappeared(pkgList[i], mChangeType);
                 }
             }
         }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index e0a154c..0a702ff 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -84,7 +84,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 66 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 67 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -154,6 +154,8 @@
     final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<StopwatchTimer>();
     final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<StopwatchTimer>();
     final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<StopwatchTimer>();
+    final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers =
+            new SparseArray<ArrayList<StopwatchTimer>>();
 
     // Last partial timers we use for distributing CPU usage.
     final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>();
@@ -2172,6 +2174,9 @@
                 case TelephonyManager.NETWORK_TYPE_EHRPD:
                     bin = DATA_CONNECTION_EHRPD;
                     break;
+                case TelephonyManager.NETWORK_TYPE_HSPAP:
+                    bin = DATA_CONNECTION_HSPAP;
+                    break;
                 default:
                     bin = DATA_CONNECTION_OTHER;
                     break;
@@ -2401,6 +2406,14 @@
         getUidStatsLocked(uid).noteWifiScanStoppedLocked();
     }
 
+    public void noteWifiBatchedScanStartedLocked(int uid, int csph) {
+        getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph);
+    }
+
+    public void noteWifiBatchedScanStoppedLocked(int uid) {
+        getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked();
+    }
+
     int mWifiMulticastNesting = 0;
 
     public void noteWifiMulticastEnabledLocked(int uid) {
@@ -2453,6 +2466,20 @@
         }
     }
 
+    public void noteWifiBatchedScanStartedFromSourceLocked(WorkSource ws, int csph) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiBatchedScanStartedLocked(ws.get(i), csph);
+        }
+    }
+
+    public void noteWifiBatchedScanStoppedFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiBatchedScanStoppedLocked(ws.get(i));
+        }
+    }
+
     public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) {
         int N = ws.size();
         for (int i=0; i<N; i++) {
@@ -2576,6 +2603,10 @@
         boolean mWifiScanStarted;
         StopwatchTimer mWifiScanTimer;
 
+        private static final int NO_BATCHED_SCAN_STARTED = -1;
+        int mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
+        StopwatchTimer[] mWifiBatchedScanTimer;
+
         boolean mWifiMulticastEnabled;
         StopwatchTimer mWifiMulticastTimer;
 
@@ -2626,6 +2657,7 @@
                     mFullWifiLockTimers, mUnpluggables);
             mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
                     mWifiScanTimers, mUnpluggables);
+            mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS];
             mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
                     mWifiMulticastTimers, mUnpluggables);
         }
@@ -2716,6 +2748,36 @@
         }
 
         @Override
+        public void noteWifiBatchedScanStartedLocked(int csph) {
+            int bin = 0;
+            while (csph > 8 && bin < NUM_WIFI_BATCHED_SCAN_BINS) {
+                csph = csph >> 3;
+                bin++;
+            }
+
+            if (mWifiBatchedScanBinStarted == bin) return;
+
+            if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) {
+                mWifiBatchedScanTimer[mWifiBatchedScanBinStarted].
+                        stopRunningLocked(BatteryStatsImpl.this);
+            }
+            mWifiBatchedScanBinStarted = bin;
+            if (mWifiBatchedScanTimer[bin] == null) {
+                makeWifiBatchedScanBin(bin, null);
+            }
+            mWifiBatchedScanTimer[bin].startRunningLocked(BatteryStatsImpl.this);
+        }
+
+        @Override
+        public void noteWifiBatchedScanStoppedLocked() {
+            if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) {
+                mWifiBatchedScanTimer[mWifiBatchedScanBinStarted].
+                        stopRunningLocked(BatteryStatsImpl.this);
+                mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
+            }
+        }
+
+        @Override
         public void noteWifiMulticastEnabledLocked() {
             if (!mWifiMulticastEnabled) {
                 mWifiMulticastEnabled = true;
@@ -2851,6 +2913,15 @@
         }
 
         @Override
+        public long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which) {
+            if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
+            if (mWifiBatchedScanTimer[csphBin] == null) {
+                return 0;
+            }
+            return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(batteryRealtime, which);
+        }
+
+        @Override
         public long getWifiMulticastTime(long batteryRealtime, int which) {
             if (mWifiMulticastTimer == null) {
                 return 0;
@@ -2911,6 +2982,24 @@
             return mUserActivityCounters[type].getCountLocked(which);
         }
 
+        void makeWifiBatchedScanBin(int i, Parcel in) {
+            if (i < 0 || i >= NUM_WIFI_BATCHED_SCAN_BINS) return;
+
+            ArrayList<StopwatchTimer> collected = mWifiBatchedScanTimers.get(i);
+            if (collected == null) {
+                collected = new ArrayList<StopwatchTimer>();
+                mWifiBatchedScanTimers.put(i, collected);
+            }
+            if (in == null) {
+                mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected,
+                        mUnpluggables);
+            } else {
+                mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected,
+                        mUnpluggables, in);
+            }
+        }
+
+
         void initUserActivityLocked() {
             mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
             for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
@@ -2971,6 +3060,14 @@
                 active |= !mWifiScanTimer.reset(BatteryStatsImpl.this, false);
                 active |= mWifiScanStarted;
             }
+            if (mWifiBatchedScanTimer != null) {
+                for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                    if (mWifiBatchedScanTimer[i] != null) {
+                        active |= !mWifiBatchedScanTimer[i].reset(BatteryStatsImpl.this, false);
+                    }
+                }
+                active |= (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED);
+            }
             if (mWifiMulticastTimer != null) {
                 active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false);
                 active |= mWifiMulticastEnabled;
@@ -3077,6 +3174,11 @@
                 if (mWifiScanTimer != null) {
                     mWifiScanTimer.detach();
                 }
+                for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                    if (mWifiBatchedScanTimer[i] != null) {
+                        mWifiBatchedScanTimer[i].detach();
+                    }
+                }
                 if (mWifiMulticastTimer != null) {
                     mWifiMulticastTimer.detach();
                 }
@@ -3154,6 +3256,14 @@
             } else {
                 out.writeInt(0);
             }
+            for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                if (mWifiBatchedScanTimer[i] != null) {
+                    out.writeInt(1);
+                    mWifiBatchedScanTimer[i].writeToParcel(out, batteryRealtime);
+                } else {
+                    out.writeInt(0);
+                }
+            }
             if (mWifiMulticastTimer != null) {
                 out.writeInt(1);
                 mWifiMulticastTimer.writeToParcel(out, batteryRealtime);
@@ -3263,6 +3373,14 @@
             } else {
                 mWifiScanTimer = null;
             }
+            mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
+            for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                if (in.readInt() != 0) {
+                    makeWifiBatchedScanBin(i, in);
+                } else {
+                    mWifiBatchedScanTimer[i] = null;
+                }
+            }
             mWifiMulticastEnabled = false;
             if (in.readInt() != 0) {
                 mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
@@ -5460,6 +5578,13 @@
             if (in.readInt() != 0) {
                 u.mWifiScanTimer.readSummaryFromParcelLocked(in);
             }
+            u.mWifiBatchedScanBinStarted = Uid.NO_BATCHED_SCAN_STARTED;
+            for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                if (in.readInt() != 0) {
+                    u.makeWifiBatchedScanBin(i, null);
+                    u.mWifiBatchedScanTimer[i].readSummaryFromParcelLocked(in);
+                }
+            }
             u.mWifiMulticastEnabled = false;
             if (in.readInt() != 0) {
                 u.mWifiMulticastTimer.readSummaryFromParcelLocked(in);
@@ -5671,6 +5796,14 @@
             } else {
                 out.writeInt(0);
             }
+            for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                if (u.mWifiBatchedScanTimer[i] != null) {
+                    out.writeInt(1);
+                    u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+                } else {
+                    out.writeInt(0);
+                }
+            }
             if (u.mWifiMulticastTimer != null) {
                 out.writeInt(1);
                 u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL);
@@ -5906,6 +6039,7 @@
         mWifiRunningTimers.clear();
         mFullWifiLockTimers.clear();
         mWifiScanTimers.clear();
+        mWifiBatchedScanTimers.clear();
         mWifiMulticastTimers.clear();
 
         sNumSpeedSteps = in.readInt();
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 99a6843..94750d3 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -136,6 +136,13 @@
     public static final String POWER_CPU_SPEEDS = "cpu.speeds";
 
     /**
+     * Power consumed by wif batched scaning.  Broken down into bins by
+     * Channels Scanned per Hour.  May do 1-720 scans per hour of 1-100 channels
+     * for a range of 1-72,000.  Going logrithmic (1-8, 9-64, 65-512, 513-4096, 4097-)!
+     */
+    public static final String POWER_WIFI_BATCHED_SCAN = "wifi.batchedscan";
+
+    /**
      * Battery capacity in milliAmpHour (mAh).
      */
     public static final String POWER_BATTERY_CAPACITY = "battery.capacity";
@@ -171,7 +178,7 @@
 
                 String element = parser.getName();
                 if (element == null) break;
-                
+
                 if (parsingArray && !element.equals(TAG_ARRAYITEM)) {
                     // Finish array
                     sPowerMap.put(arrayName, array.toArray(new Double[array.size()]));
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 73d34c3..1548f1b 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -192,10 +192,16 @@
     static void closeServerSocket() {
         try {
             if (sServerSocket != null) {
+                FileDescriptor fd = sServerSocket.getFileDescriptor();
                 sServerSocket.close();
+                if (fd != null) {
+                    Libcore.os.close(fd);
+                }
             }
         } catch (IOException ex) {
             Log.e(TAG, "Zygote:  error closing sockets", ex);
+        } catch (libcore.io.ErrnoException ex) {
+            Log.e(TAG, "Zygote:  error closing descriptor", ex);
         }
 
         sServerSocket = null;
@@ -301,6 +307,8 @@
                         count++;
                     } catch (ClassNotFoundException e) {
                         Log.w(TAG, "Class not found for preloading: " + line);
+                    } catch (UnsatisfiedLinkError e) {
+                        Log.w(TAG, "Problem preloading " + line + ": " + e);
                     } catch (Throwable t) {
                         Log.e(TAG, "Error preloading " + line + ".", t);
                         if (t instanceof Error) {
diff --git a/core/java/com/android/internal/view/CheckableLinearLayout.java b/core/java/com/android/internal/view/CheckableLinearLayout.java
deleted file mode 100644
index 3fb7cec..0000000
--- a/core/java/com/android/internal/view/CheckableLinearLayout.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.view;
-
-import com.android.internal.R;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.Checkable;
-import android.widget.CheckBox;
-import android.widget.LinearLayout;
-
-public class CheckableLinearLayout extends LinearLayout implements Checkable {
-    private CheckBox mCheckBox;
-
-    public CheckableLinearLayout(Context context) {
-        super(context);
-        // TODO Auto-generated constructor stub
-    }
-
-    public CheckableLinearLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        // TODO Auto-generated constructor stub
-    }
-
-    public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        // TODO Auto-generated constructor stub
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mCheckBox = (CheckBox) findViewById(R.id.check);
-    }
-
-    @Override
-    public void setChecked(boolean checked) {
-        mCheckBox.setChecked(checked);
-    }
-
-    @Override
-    public boolean isChecked() {
-        return mCheckBox.isChecked();
-    }
-
-    @Override
-    public void toggle() {
-        mCheckBox.toggle();
-    }
-}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 12ced68..3724a08 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -73,5 +73,5 @@
     boolean switchToNextInputMethod(in IBinder token, boolean onlyCurrentIme);
     boolean shouldOfferSwitchingToNextInputMethod(in IBinder token);
     boolean setInputMethodEnabled(String id, boolean enabled);
-    oneway void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
+    void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
 }
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
index 44e7ec1..fe1cf72 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -164,8 +164,24 @@
         }
         actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
 
+        final ActionMenuView menuParent = (ActionMenuView) parent;
+        final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
+        if (!menuParent.checkLayoutParams(lp)) {
+            actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
+        }
+        return actionView;
+    }
+
+    @Override
+    public void bindItemView(final MenuItemImpl item, MenuView.ItemView itemView) {
+        itemView.initialize(item, 0);
+
+        final ActionMenuView menuView = (ActionMenuView) mMenuView;
+        final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
+        actionItemView.setItemInvoker(menuView);
+
         if (item.hasSubMenu()) {
-            actionView.setOnTouchListener(new ForwardingListener(actionView) {
+            actionItemView.setOnTouchListener(new ForwardingListener(actionItemView) {
                 @Override
                 public ListPopupWindow getPopup() {
                     return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
@@ -182,24 +198,8 @@
                 }
             });
         } else {
-            actionView.setOnTouchListener(null);
+            actionItemView.setOnTouchListener(null);
         }
-
-        final ActionMenuView menuParent = (ActionMenuView) parent;
-        final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
-        if (!menuParent.checkLayoutParams(lp)) {
-            actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
-        }
-        return actionView;
-    }
-
-    @Override
-    public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
-        itemView.initialize(item, 0);
-
-        final ActionMenuView menuView = (ActionMenuView) mMenuView;
-        ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
-        actionItemView.setItemInvoker(menuView);
     }
 
     @Override
@@ -721,7 +721,8 @@
             if (subMenu == null) return false;
 
             mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
-            return false;
+            final MenuPresenter.Callback cb = getCallback();
+            return cb != null ? cb.onOpenSubMenu(subMenu) : false;
         }
 
         @Override
@@ -729,6 +730,10 @@
             if (menu instanceof SubMenuBuilder) {
                 ((SubMenuBuilder) menu).getRootMenu().close(false);
             }
+            final MenuPresenter.Callback cb = getCallback();
+            if (cb != null) {
+                cb.onCloseMenu(menu, allMenusAreClosing);
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
index db0d6dd..92e9ea6 100644
--- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
@@ -144,6 +144,10 @@
         mCallback = cb;
     }
 
+    public Callback getCallback() {
+        return mCallback;
+    }
+
     /**
      * Create a new item view that can be re-bound to other item data later.
      *
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
index 4882adc..e1bb3621 100644
--- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -163,7 +163,7 @@
 
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        mMenu.performItemAction(mAdapter.getItem(position), 0);
+        mMenu.performItemAction(mAdapter.getItem(position), this, 0);
     }
 
     @Override
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index aff697a..195a00d 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -247,11 +247,17 @@
         startDispatchingItemsChanged();
     }
     
-    private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) {
+    private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu,
+            MenuPresenter preferredPresenter) {
         if (mPresenters.isEmpty()) return false;
 
         boolean result = false;
 
+        // Try the preferred presenter first.
+        if (preferredPresenter != null) {
+            result = preferredPresenter.onSubMenuSelected(subMenu);
+        }
+
         for (WeakReference<MenuPresenter> ref : mPresenters) {
             final MenuPresenter presenter = ref.get();
             if (presenter == null) {
@@ -865,6 +871,10 @@
     }
 
     public boolean performItemAction(MenuItem item, int flags) {
+        return performItemAction(item, null, flags);
+    }
+
+    public boolean performItemAction(MenuItem item, MenuPresenter preferredPresenter, int flags) {
         MenuItemImpl itemImpl = (MenuItemImpl) item;
         
         if (itemImpl == null || !itemImpl.isEnabled()) {
@@ -889,7 +899,7 @@
             if (providerHasSubMenu) {
                 provider.onPrepareSubMenu(subMenu);
             }
-            invoked |= dispatchSubMenuSelected(subMenu);
+            invoked |= dispatchSubMenuSelected(subMenu, preferredPresenter);
             if (!invoked) close(true);
         } else {
             if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index b5d74e8..786f5cf 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -526,7 +526,7 @@
         if (mLogoNavItem != null) {
             mLogoNavItem.setTitle(title);
         }
-        mUpGoerFive.setContentDescription(buildHomeContentDescription());
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
     }
 
     public CharSequence getSubtitle() {
@@ -544,7 +544,7 @@
                     (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle));
             mTitleLayout.setVisibility(visible ? VISIBLE : GONE);
         }
-        mUpGoerFive.setContentDescription(buildHomeContentDescription());
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
     }
 
     public void setHomeButtonEnabled(boolean enable) {
@@ -566,7 +566,11 @@
         mUpGoerFive.setEnabled(enable);
         mUpGoerFive.setFocusable(enable);
         // Make sure the home button has an accurate content description for accessibility.
-        if (!enable) {
+        updateHomeAccessibility(enable);
+    }
+
+    private void updateHomeAccessibility(boolean homeEnabled) {
+        if (!homeEnabled) {
             mUpGoerFive.setContentDescription(null);
             mUpGoerFive.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
         } else {
@@ -677,19 +681,7 @@
         }
 
         // Make sure the home button has an accurate content description for accessibility.
-        if (!mHomeLayout.isEnabled()) {
-            mHomeLayout.setContentDescription(null);
-            mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-        } else {
-            mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-            if ((options & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
-                mHomeLayout.setContentDescription(mContext.getResources().getText(
-                        R.string.action_bar_up_description));
-            } else {
-                mHomeLayout.setContentDescription(mContext.getResources().getText(
-                        R.string.action_bar_home_description));
-            }
-        }
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
     }
 
     public void setIcon(Drawable icon) {
@@ -1340,11 +1332,13 @@
 
     public void setHomeActionContentDescription(CharSequence description) {
         mHomeDescription = description;
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
     }
 
     public void setHomeActionContentDescription(int resId) {
         mHomeDescriptionRes = resId;
         mHomeDescription = resId != 0 ? getResources().getText(resId) : null;
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
     }
 
     static class SavedState extends BaseSavedState {
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index eea9ee1..2125763 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -227,7 +227,7 @@
     do {

         *dst++ = SkUnPreMultiply::PMColorToColor(colors[*s++]);

     } while (--width != 0);

-    ctable->unlockColors(false);

+    ctable->unlockColors();

 }

 

 static void ToColor_SI8_Raw(SkColor dst[], const void* src, int width,

@@ -240,7 +240,7 @@
         *dst++ = SkColorSetARGB(SkGetPackedA32(c), SkGetPackedR32(c),

                                 SkGetPackedG32(c), SkGetPackedB32(c));

     } while (--width != 0);

-    ctable->unlockColors(false);

+    ctable->unlockColors();

 }

 

 static void ToColor_SI8_Opaque(SkColor dst[], const void* src, int width,

@@ -253,7 +253,7 @@
         *dst++ = SkColorSetRGB(SkGetPackedR32(c), SkGetPackedG32(c),

                                SkGetPackedB32(c));

     } while (--width != 0);

-    ctable->unlockColors(false);

+    ctable->unlockColors();

 }

 

 // can return NULL

@@ -442,9 +442,15 @@
     return !bitmap->isOpaque();

 }

 

-static void Bitmap_setHasAlpha(JNIEnv* env, jobject, SkBitmap* bitmap,

-                               jboolean hasAlpha) {

-    bitmap->setIsOpaque(!hasAlpha);

+static void Bitmap_setAlphaAndPremultiplied(JNIEnv* env, jobject, SkBitmap* bitmap,

+                                            jboolean hasAlpha, jboolean isPremul) {

+    if (!hasAlpha) {

+        bitmap->setAlphaType(kOpaque_SkAlphaType);

+    } else if (isPremul) {

+        bitmap->setAlphaType(kPremul_SkAlphaType);

+    } else {

+        bitmap->setAlphaType(kUnpremul_SkAlphaType);

+    }

 }

 

 static jboolean Bitmap_hasMipMap(JNIEnv* env, jobject, SkBitmap* bitmap) {

@@ -543,14 +549,14 @@
     p->writeInt32(bitmap->rowBytes());

     p->writeInt32(density);

 

-    if (bitmap->getConfig() == SkBitmap::kIndex8_Config) {

+    if (bitmap->config() == SkBitmap::kIndex8_Config) {

         SkColorTable* ctable = bitmap->getColorTable();

         if (ctable != NULL) {

             int count = ctable->count();

             p->writeInt32(count);

             memcpy(p->writeInplace(count * sizeof(SkPMColor)),

                    ctable->lockColors(), count * sizeof(SkPMColor));

-            ctable->unlockColors(false);

+            ctable->unlockColors();

         } else {

             p->writeInt32(0);   // indicate no ctable

         }

@@ -770,7 +776,7 @@
     {   "nativeRowBytes",           "(I)I", (void*)Bitmap_rowBytes },

     {   "nativeConfig",             "(I)I", (void*)Bitmap_config },

     {   "nativeHasAlpha",           "(I)Z", (void*)Bitmap_hasAlpha },

-    {   "nativeSetHasAlpha",        "(IZ)V", (void*)Bitmap_setHasAlpha },

+    {   "nativeSetAlphaAndPremultiplied", "(IZZ)V", (void*)Bitmap_setAlphaAndPremultiplied},

     {   "nativeHasMipMap",          "(I)Z", (void*)Bitmap_hasMipMap },

     {   "nativeSetHasMipMap",       "(IZ)V", (void*)Bitmap_setHasMipMap },

     {   "nativeCreateFromParcel",

diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index da6219f..709de5c 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -21,6 +21,7 @@
 #include <androidfw/Asset.h>
 #include <androidfw/ResourceTypes.h>
 #include <netinet/in.h>
+#include <stdio.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 
@@ -124,12 +125,18 @@
 static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStreamRewindable* stream,
         int sampleSize, bool ditherImage) {
 
+    SkImageInfo bitmapInfo;
+    if (!bitmap->asImageInfo(&bitmapInfo)) {
+        ALOGW("bitmap has unknown configuration so no memory has been allocated");
+        return NULL;
+    }
+
     SkImageRef* pr;
     // only use ashmem for large images, since mmaps come at a price
     if (bitmap->getSize() >= 32 * 1024) {
-        pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize);
+        pr = new SkImageRef_ashmem(bitmapInfo, stream, sampleSize);
     } else {
-        pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize);
+        pr = new SkImageRef_GlobalPool(bitmapInfo, stream, sampleSize);
     }
     pr->setDitherImage(ditherImage);
     bitmap->setPixelRef(pr)->unref();
@@ -157,7 +164,7 @@
     virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
         // accounts for scale in final allocation, using eventual size and config
         const int bytesPerPixel = SkBitmap::ComputeBytesPerPixel(
-                configForScaledOutput(bitmap->getConfig()));
+                configForScaledOutput(bitmap->config()));
         const int requestedSize = bytesPerPixel *
                 int(bitmap->width() * mScale + 0.5f) *
                 int(bitmap->height() * mScale + 0.5f);
@@ -191,8 +198,15 @@
             return false;
         }
 
+        SkImageInfo bitmapInfo;
+        if (!bitmap->asImageInfo(&bitmapInfo)) {
+            ALOGW("unable to reuse a bitmap as the target has an unknown bitmap configuration");
+            return false;
+        }
+
         // Create a new pixelref with the new ctable that wraps the previous pixelref
-        SkPixelRef* pr = new AndroidPixelRef(*static_cast<AndroidPixelRef*>(mPixelRef), ctable);
+        SkPixelRef* pr = new AndroidPixelRef(*static_cast<AndroidPixelRef*>(mPixelRef),
+                bitmapInfo, bitmap->rowBytes(), ctable);
 
         bitmap->setPixelRef(pr)->unref();
         // since we're already allocated, we lockPixels right away
@@ -401,8 +415,11 @@
 
         // TODO: avoid copying when scaled size equals decodingBitmap size
         SkBitmap::Config config = configForScaledOutput(decodingBitmap.config());
-        outputBitmap->setConfig(config, scaledWidth, scaledHeight);
-        outputBitmap->setIsOpaque(decodingBitmap.isOpaque());
+        // FIXME: If the alphaType is kUnpremul and the image has alpha, the
+        // colors may not be correct, since Skia does not yet support drawing
+        // to/from unpremultiplied bitmaps.
+        outputBitmap->setConfig(config, scaledWidth, scaledHeight, 0,
+                                decodingBitmap.alphaType());
         if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
             return nullObjectReturn("allocation failed for scaled bitmap");
         }
@@ -414,7 +431,7 @@
         }
 
         SkPaint paint;
-        paint.setFilterBitmap(true);
+        paint.setFilterLevel(SkPaint::kLow_FilterLevel);
 
         SkCanvas canvas(*outputBitmap);
         canvas.scale(sx, sy);
@@ -470,6 +487,12 @@
             bitmapCreateFlags, ninePatchChunk, layoutBounds, -1);
 }
 
+// Need to buffer enough input to be able to rewind as much as might be read by a decoder
+// trying to determine the stream's format. Currently the most is 64, read by
+// SkImageDecoder_libwebp.
+// FIXME: Get this number from SkImageDecoder
+#define BYTES_TO_BUFFER 64
+
 static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
         jobject padding, jobject options) {
 
@@ -477,11 +500,8 @@
     SkAutoTUnref<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));
 
     if (stream.get()) {
-        // Need to buffer enough input to be able to rewind as much as might be read by a decoder
-        // trying to determine the stream's format. Currently the most is 64, read by
-        // SkImageDecoder_libwebp.
-        // FIXME: Get this number from SkImageDecoder
-        SkAutoTUnref<SkStreamRewindable> bufferedStream(SkFrontBufferedStream::Create(stream, 64));
+        SkAutoTUnref<SkStreamRewindable> bufferedStream(
+                SkFrontBufferedStream::Create(stream, BYTES_TO_BUFFER));
         SkASSERT(bufferedStream.get() != NULL);
         // for now we don't allow purgeable with java inputstreams
         bitmap = doDecode(env, bufferedStream, padding, options, false, false);
@@ -502,27 +522,48 @@
         return nullObjectReturn("fstat return -1");
     }
 
-    bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions);
-    bool isShareable = optionsShareable(env, bitmapFactoryOptions);
-    bool weOwnTheFD = false;
-    if (isPurgeable && isShareable) {
-        int newFD = ::dup(descriptor);
-        if (-1 != newFD) {
-            weOwnTheFD = true;
-            descriptor = newFD;
+    // Restore the descriptor's offset on exiting this function.
+    AutoFDSeek autoRestore(descriptor);
+
+    FILE* file = fdopen(descriptor, "r");
+    if (file == NULL) {
+        return nullObjectReturn("Could not open file");
+    }
+
+    SkAutoTUnref<SkFILEStream> fileStream(new SkFILEStream(file,
+                         SkFILEStream::kCallerRetains_Ownership));
+
+    SkAutoTUnref<SkStreamRewindable> stream;
+
+    // Retain the old behavior of allowing purgeable if both purgeable and
+    // shareable are set to true.
+    bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions)
+                       && optionsShareable(env, bitmapFactoryOptions);
+    if (isPurgeable) {
+        // Copy the stream, so the image can be decoded multiple times without
+        // continuing to modify the original file descriptor.
+        // Copy beginning from the current position.
+        const size_t fileSize = fileStream->getLength() - fileStream->getPosition();
+        void* buffer = sk_malloc_flags(fileSize, 0);
+        if (buffer == NULL) {
+            return nullObjectReturn("Could not make a copy for ashmem");
         }
+
+        SkAutoTUnref<SkData> data(SkData::NewFromMalloc(buffer, fileSize));
+
+        if (fileStream->read(buffer, fileSize) != fileSize) {
+            return nullObjectReturn("Could not read the file.");
+        }
+
+        stream.reset(new SkMemoryStream(data));
+    } else {
+        // Use a buffered stream. Although an SkFILEStream can be rewound, this
+        // ensures that SkImageDecoder::Factory never rewinds beyond the
+        // current position of the file descriptor.
+        stream.reset(SkFrontBufferedStream::Create(fileStream, BYTES_TO_BUFFER));
     }
 
-    SkAutoTUnref<SkData> data(SkData::NewFromFD(descriptor));
-    if (data.get() == NULL) {
-        return nullObjectReturn("NewFromFD failed in nativeDecodeFileDescriptor");
-    }
-    SkAutoTUnref<SkMemoryStream> stream(new SkMemoryStream(data));
-
-    /* Allow purgeable iff we own the FD, i.e., in the puregeable and
-       shareable case.
-    */
-    return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD);
+    return doDecode(env, stream, padding, bitmapFactoryOptions, isPurgeable);
 }
 
 static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
@@ -541,7 +582,9 @@
     } else {
         // since we know we'll be done with the asset when we return, we can
         // just use a simple wrapper
-        stream = new AssetStreamAdaptor(asset);
+        stream = new AssetStreamAdaptor(asset,
+                                        AssetStreamAdaptor::kNo_OwnAsset,
+                                        AssetStreamAdaptor::kNo_HasMemoryBase);
     }
     SkAutoUnref aur(stream);
     return doDecode(env, stream, padding, options, forcePurgeable, forcePurgeable);
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index ee47ac4..1412a0e 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -29,7 +29,6 @@
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "Utils.h"
 #include "JNIHelp.h"
-#include "SkTScopedPtr.h"
 
 #include <android_runtime/AndroidRuntime.h>
 #include "android_util_Binder.h"
@@ -76,7 +75,7 @@
     int fHeight;
 };
 
-static jobject createBitmapRegionDecoder(JNIEnv* env, SkStream* stream) {
+static jobject createBitmapRegionDecoder(JNIEnv* env, SkStreamRewindable* stream) {
     SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
     int width, height;
     if (NULL == decoder) {
@@ -108,7 +107,7 @@
         For now we just always copy the array's data if isShareable.
      */
     AutoJavaByteArray ar(env, byteArray);
-    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
+    SkStreamRewindable* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
 
     jobject brd = createBitmapRegionDecoder(env, stream);
     SkSafeUnref(stream); // the decoder now holds a reference
@@ -215,7 +214,7 @@
     region.fRight = start_x + width;
     region.fBottom = start_y + height;
     SkBitmap* bitmap = NULL;
-    SkTScopedPtr<SkBitmap> adb;
+    SkAutoTDelete<SkBitmap> adb;
 
     if (tileBitmap != NULL) {
         // Re-use bitmap.
@@ -246,7 +245,7 @@
     }
 
     // detach bitmap from its autodeleter, since we want to own it now
-    adb.release();
+    adb.detach();
 
     JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator();
     jbyteArray buff = allocator->getStorageObjAndReset();
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 813dd5a..edf3b4a 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -476,7 +476,7 @@
                 if (paint) {
                     filteredPaint = *paint;
                 }
-                filteredPaint.setFilterBitmap(true);
+                filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
                 canvas->drawBitmap(*bitmap, left_, top_, &filteredPaint);
             } else {
                 canvas->drawBitmap(*bitmap, left_, top_, paint);
@@ -491,7 +491,7 @@
             if (paint) {
                 filteredPaint = *paint;
             }
-            filteredPaint.setFilterBitmap(true);
+            filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
 
             canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint);
 
@@ -514,7 +514,7 @@
             if (paint) {
                 filteredPaint = *paint;
             }
-            filteredPaint.setFilterBitmap(true);
+            filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
             canvas->drawBitmapRect(*bitmap, srcPtr, dst, &filteredPaint);
         } else {
             canvas->drawBitmapRect(*bitmap, srcPtr, dst, paint);
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 8cb152d..3090ad2 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -346,6 +346,18 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////
 
+// Assert that bitmap's SkAlphaType is consistent with isPremultiplied.
+static void assert_premultiplied(const SkBitmap& bitmap, bool isPremultiplied) {
+    // kOpaque_SkAlphaType and kIgnore_SkAlphaType mean that isPremultiplied is
+    // irrelevant. This just tests to ensure that the SkAlphaType is not
+    // opposite of isPremultiplied.
+    if (isPremultiplied) {
+        SkASSERT(bitmap.alphaType() != kUnpremul_SkAlphaType);
+    } else {
+        SkASSERT(bitmap.alphaType() != kPremul_SkAlphaType);
+    }
+}
+
 jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
         int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density)
 {
@@ -354,6 +366,10 @@
     bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
     bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
 
+    // The caller needs to have already set the alpha type properly, so the
+    // native SkBitmap stays in sync with the Java Bitmap.
+    assert_premultiplied(*bitmap, isPremultiplied);
+
     jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
             static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)), buffer,
             bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
@@ -371,6 +387,10 @@
 void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap,
         bool isPremultiplied)
 {
+    // The caller needs to have already set the alpha type properly, so the
+    // native SkBitmap stays in sync with the Java Bitmap.
+    assert_premultiplied(*bitmap, isPremultiplied);
+
     env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID,
             bitmap->width(), bitmap->height(), isPremultiplied);
 }
@@ -413,8 +433,9 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-AndroidPixelRef::AndroidPixelRef(JNIEnv* env, void* storage, size_t size, jbyteArray storageObj,
-        SkColorTable* ctable) : SkMallocPixelRef(storage, size, ctable, (storageObj == NULL)),
+AndroidPixelRef::AndroidPixelRef(JNIEnv* env, const SkImageInfo& info, void* storage,
+        size_t rowBytes, jbyteArray storageObj, SkColorTable* ctable) :
+        SkMallocPixelRef(info, storage, rowBytes, ctable, (storageObj == NULL)),
         fWrappedPixelRef(NULL) {
     SkASSERT(storage);
     SkASSERT(env);
@@ -429,13 +450,13 @@
 
     // If storageObj is NULL, the memory was NOT allocated on the Java heap
     fOnJavaHeap = (storageObj != NULL);
-
 }
 
-AndroidPixelRef::AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, SkColorTable* ctable) :
-        SkMallocPixelRef(wrappedPixelRef.getAddr(), wrappedPixelRef.getSize(), ctable, false),
+AndroidPixelRef::AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, const SkImageInfo& info,
+        size_t rowBytes, SkColorTable* ctable) :
+        SkMallocPixelRef(info, wrappedPixelRef.getAddr(), rowBytes, ctable, false),
         fWrappedPixelRef(wrappedPixelRef.fWrappedPixelRef ?
-            wrappedPixelRef.fWrappedPixelRef : &wrappedPixelRef)
+                wrappedPixelRef.fWrappedPixelRef : &wrappedPixelRef)
 {
     SkASSERT(fWrappedPixelRef);
     SkSafeRef(fWrappedPixelRef);
@@ -540,13 +561,21 @@
         return NULL;
     }
 
+    SkImageInfo bitmapInfo;
+    if (!bitmap->asImageInfo(&bitmapInfo)) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                "unknown bitmap configuration");
+        return NULL;
+    }
+
     size_t size = size64.get32();
     jbyteArray arrayObj = env->NewByteArray(size);
     if (arrayObj) {
         // TODO: make this work without jniGetNonMovableArrayElements
         jbyte* addr = jniGetNonMovableArrayElements(&env->functions, arrayObj);
         if (addr) {
-            SkPixelRef* pr = new AndroidPixelRef(env, (void*) addr, size, arrayObj, ctable);
+            SkPixelRef* pr = new AndroidPixelRef(env, bitmapInfo, (void*) addr,
+                    bitmap->rowBytes(), arrayObj, ctable);
             bitmap->setPixelRef(pr)->unref();
             // since we're already allocated, we lockPixels right away
             // HeapAllocator behaves this way too
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index f4590b9..cb154aa 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -57,6 +57,7 @@
 
     /** Create a java Bitmap object given the native bitmap (required) and optional
         storage array (may be null).
+        bitmap's SkAlphaType must already be in sync with bitmapCreateFlags.
     */
     static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
             int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density = -1);
@@ -64,6 +65,9 @@
     static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
             jbyteArray ninepatch, int density = -1);
 
+    /** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in
+        sync with isPremultiplied
+    */
     static void reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap,
             bool isPremultiplied);
 
@@ -88,15 +92,16 @@
 
 class AndroidPixelRef : public SkMallocPixelRef {
 public:
-    AndroidPixelRef(JNIEnv* env, void* storage, size_t size, jbyteArray storageObj,
-                    SkColorTable* ctable);
+    AndroidPixelRef(JNIEnv* env, const SkImageInfo& info, void* storage, size_t rowBytes,
+            jbyteArray storageObj, SkColorTable* ctable);
 
     /**
      * Creates an AndroidPixelRef that wraps (and refs) another to reuse/share
      * the same storage and java byte array refcounting, yet have a different
      * color table.
      */
-    AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, SkColorTable* ctable);
+    AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, const SkImageInfo& info,
+            size_t rowBytes, SkColorTable* ctable);
 
     virtual ~AndroidPixelRef();
 
diff --git a/core/jni/android/graphics/Movie.cpp b/core/jni/android/graphics/Movie.cpp
index feb2dec..55be7c1 100644
--- a/core/jni/android/graphics/Movie.cpp
+++ b/core/jni/android/graphics/Movie.cpp
@@ -85,7 +85,9 @@
 static jobject movie_decodeAsset(JNIEnv* env, jobject clazz, jint native_asset) {
     android::Asset* asset = reinterpret_cast<android::Asset*>(native_asset);
     if (asset == NULL) return NULL;
-    SkAutoTUnref<SkStreamRewindable> stream (new android::AssetStreamAdaptor(asset));
+    SkAutoTUnref<SkStreamRewindable> stream (new android::AssetStreamAdaptor(asset,
+            android::AssetStreamAdaptor::kNo_OwnAsset,
+            android::AssetStreamAdaptor::kNo_HasMemoryBase));
     SkMovie* moov = SkMovie::DecodeStream(stream.get());
     return create_jmovie(env, moov);
 }
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index df996af..d3482da 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -40,15 +40,14 @@
         // now update our host to force index or 32bit config
         // 'cause we don't want 565 predithered, since as a 9patch, we know
         // we will be stretched, and therefore we want to dither afterwards.
-        static const SkBitmap::Config gNo565Pref[] = {
-            SkBitmap::kIndex8_Config,
-            SkBitmap::kIndex8_Config,
-            SkBitmap::kARGB_8888_Config,
-            SkBitmap::kARGB_8888_Config,
-            SkBitmap::kARGB_8888_Config,
-            SkBitmap::kARGB_8888_Config,
-        };
-        fHost->setPrefConfigTable(gNo565Pref);
+        SkImageDecoder::PrefConfigTable table;
+        table.fPrefFor_8Index_NoAlpha_src   = SkBitmap::kIndex8_Config;
+        table.fPrefFor_8Index_YesAlpha_src  = SkBitmap::kIndex8_Config;
+        table.fPrefFor_8Gray_src            = SkBitmap::kARGB_8888_Config;
+        table.fPrefFor_8bpc_NoAlpha_src     = SkBitmap::kARGB_8888_Config;
+        table.fPrefFor_8bpc_YesAlpha_src    = SkBitmap::kARGB_8888_Config;
+
+        fHost->setPrefConfigTable(table);
     } else if (strcmp("npLb", tag) == 0 && length == sizeof(int) * 4) {
         fLayoutBounds = new int[4];
         memcpy(fLayoutBounds, data, sizeof(int) * 4);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 40e0731..fd3f327 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -148,7 +148,8 @@
 
     static void setFilterBitmap(JNIEnv* env, jobject paint, jboolean filterBitmap) {
         NPE_CHECK_RETURN_VOID(env, paint);
-        GraphicsJNI::getNativePaint(env, paint)->setFilterBitmap(filterBitmap);
+        GraphicsJNI::getNativePaint(env, paint)->setFilterLevel(
+                filterBitmap ? SkPaint::kLow_FilterLevel : SkPaint::kNone_FilterLevel);
     }
 
     static void setDither(JNIEnv* env, jobject paint, jboolean dither) {
@@ -565,7 +566,7 @@
                 return 0;
             }
         }
-        jfloat advancesArray[count];
+        jfloat* advancesArray = new jfloat[count];
         jfloat totalAdvance = 0;
 
         TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags,
@@ -574,6 +575,7 @@
         if (advances != NULL) {
             env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
         }
+        delete [] advancesArray;
         return totalAdvance;
     }
 
diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp
index ded2186..f0a7baf 100644
--- a/core/jni/android/graphics/Region.cpp
+++ b/core/jni/android/graphics/Region.cpp
@@ -177,7 +177,7 @@
 
     SkRegion* region = new SkRegion;
     size_t size = p->readInt32();
-    region->readFromMemory(p->readInplace(size));
+    region->readFromMemory(p->readInplace(size), size);
 
     return region;
 }
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index 92d253f..144ac39 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -20,6 +20,7 @@
 
 #include "TextLayoutCache.h"
 #include "TextLayout.h"
+#include "SkGlyphCache.h"
 #include "SkTypeface_android.h"
 #include "HarfBuzzNGFaceSkia.h"
 #include <unicode/unistr.h>
@@ -757,8 +758,8 @@
             outPos->add(ypos);
             totalAdvance += xAdvance;
 
-            // TODO: consider using glyph cache
-            const SkGlyph& metrics = mShapingPaint.getGlyphMetrics(glyphId, NULL);
+            SkAutoGlyphCache autoCache(mShapingPaint, NULL, NULL);
+            const SkGlyph& metrics = autoCache.getCache()->getGlyphIDMetrics(glyphId);
             outBounds->join(xpos + metrics.fLeft, ypos + metrics.fTop,
                     xpos + metrics.fLeft + metrics.fWidth, ypos + metrics.fTop + metrics.fHeight);
 
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index a7a0bb2..d10a960 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -4,6 +4,7 @@
 #include "GraphicsJNI.h"
 #include "SkStream.h"
 #include "SkTypeface.h"
+#include "Utils.h"
 #include <android_runtime/android_util_AssetManager.h>
 #include <androidfw/AssetManager.h>
 
@@ -34,6 +35,13 @@
     if (NULL != name) {
         AutoJavaStringToUTF8    str(env, name);
         face = SkTypeface::CreateFromName(str.c_str(), style);
+        // Try to find the closest matching font, using the standard heuristic
+        if (NULL == face) {
+            face = SkTypeface::CreateFromName(str.c_str(), (SkTypeface::Style)(style ^ SkTypeface::kItalic));
+        }
+        for (int i = 0; NULL == face && i < 4; i++) {
+            face = SkTypeface::CreateFromName(str.c_str(), (SkTypeface::Style)i);
+        }
     }
 
     // return the default font at the best style if no exact match exists
@@ -45,8 +53,13 @@
 
 static SkTypeface* Typeface_createFromTypeface(JNIEnv* env, jobject, SkTypeface* family, int style) {
     SkTypeface* face = SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)style);
-    // return the default font at the best style if the requested style does not
-    // exist in the provided family
+    // Try to find the closest matching font, using the standard heuristic
+    if (NULL == face) {
+        face = SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));
+    }
+    for (int i = 0; NULL == face && i < 4; i++) {
+        face = SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)i);
+    }
     if (NULL == face) {
         face = SkTypeface::CreateFromName(NULL, (SkTypeface::Style)style);
     }
@@ -61,65 +74,6 @@
     return face->style();
 }
 
-class AssetStream : public SkStream {
-public:
-    AssetStream(Asset* asset, bool hasMemoryBase) : fAsset(asset)
-    {
-        fMemoryBase = hasMemoryBase ? fAsset->getBuffer(false) : NULL;
-    }
-
-    virtual ~AssetStream()
-    {
-        delete fAsset;
-    }
-
-    virtual const void* getMemoryBase()
-    {
-        return fMemoryBase;
-    }
-
-	virtual bool rewind()
-    {
-        off64_t pos = fAsset->seek(0, SEEK_SET);
-        return pos != (off64_t)-1;
-    }
-
-	virtual size_t read(void* buffer, size_t size)
-    {
-        ssize_t amount;
-
-        if (NULL == buffer)
-        {
-            if (0 == size)  // caller is asking us for our total length
-                return fAsset->getLength();
-
-            // asset->seek returns new total offset
-            // we want to return amount that was skipped
-
-            off64_t oldOffset = fAsset->seek(0, SEEK_CUR);
-            if (-1 == oldOffset)
-                return 0;
-            off64_t newOffset = fAsset->seek(size, SEEK_CUR);
-            if (-1 == newOffset)
-                return 0;
-
-            amount = newOffset - oldOffset;
-        }
-        else
-        {
-            amount = fAsset->read(buffer, size);
-        }
-
-        if (amount < 0)
-            amount = 0;
-        return amount;
-    }
-
-private:
-    Asset*      fAsset;
-    const void* fMemoryBase;
-};
-
 static SkTypeface* Typeface_createFromAsset(JNIEnv* env, jobject,
                                             jobject jassetMgr,
                                             jstring jpath) {
@@ -138,7 +92,9 @@
         return NULL;
     }
 
-    SkStream* stream = new AssetStream(asset, true);
+    SkStream* stream = new AssetStreamAdaptor(asset,
+                                              AssetStreamAdaptor::kYes_OwnAsset,
+                                              AssetStreamAdaptor::kYes_HasMemoryBase);
     SkTypeface* face = SkTypeface::CreateFromStream(stream);
     // SkTypeFace::CreateFromStream calls ref() on the stream, so we
     // need to unref it here or it won't be freed later on
diff --git a/core/jni/android/graphics/Utils.cpp b/core/jni/android/graphics/Utils.cpp
index b7d1f3a..eb416cb 100644
--- a/core/jni/android/graphics/Utils.cpp
+++ b/core/jni/android/graphics/Utils.cpp
@@ -19,6 +19,21 @@
 
 using namespace android;
 
+AssetStreamAdaptor::AssetStreamAdaptor(Asset* asset, OwnAsset ownAsset,
+                                       HasMemoryBase hasMemoryBase)
+    : fAsset(asset)
+    , fMemoryBase(kYes_HasMemoryBase == hasMemoryBase ?
+                  asset->getBuffer(false) : NULL)
+    , fOwnAsset(ownAsset)
+{
+}
+
+AssetStreamAdaptor::~AssetStreamAdaptor() {
+    if (kYes_OwnAsset == fOwnAsset) {
+        delete fAsset;
+    }
+}
+
 bool AssetStreamAdaptor::rewind() {
     off64_t pos = fAsset->seek(0, SEEK_SET);
     if (pos == (off64_t)-1) {
diff --git a/core/jni/android/graphics/Utils.h b/core/jni/android/graphics/Utils.h
index a1ac72a..b90593c 100644
--- a/core/jni/android/graphics/Utils.h
+++ b/core/jni/android/graphics/Utils.h
@@ -28,16 +28,36 @@
 
 class AssetStreamAdaptor : public SkStreamRewindable {
 public:
-    AssetStreamAdaptor(Asset* a) : fAsset(a) {}
+    // Enum passed to constructor. If set to kYes_OwnAsset,
+    // the passed in Asset will be deleted upon destruction.
+    enum OwnAsset {
+        kYes_OwnAsset,
+        kNo_OwnAsset,
+    };
+
+    // Enum passed to constructor. If set to kYes_HasMemoryBase,
+    // getMemoryBase will return the Asset's buffer.
+    enum HasMemoryBase {
+        kYes_HasMemoryBase,
+        kNo_HasMemoryBase,
+    };
+
+    AssetStreamAdaptor(Asset*, OwnAsset, HasMemoryBase);
+    ~AssetStreamAdaptor();
+
     virtual bool rewind();
     virtual size_t read(void* buffer, size_t size);
     virtual bool hasLength() const { return true; }
     virtual size_t getLength() const;
     virtual bool isAtEnd() const;
 
+    virtual const void* getMemoryBase() { return fMemoryBase; }
+
     virtual SkStreamRewindable* duplicate() const;
 private:
-    Asset*  fAsset;
+    Asset*              fAsset;
+    const void* const   fMemoryBase;
+    const OwnAsset      fOwnAsset;
 };
 
 /**
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index b57a0fe..6175a8f 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -17,62 +17,138 @@
 #include "jni.h"
 #include "GraphicsJNI.h"
 #include <android_runtime/AndroidRuntime.h>
+#include <vector>
+
+#include "CreateJavaOutputStreamAdaptor.h"
 
 #include "SkCanvas.h"
-#include "SkPDFDevice.h"
-#include "SkPDFDocument.h"
+#include "SkDocument.h"
+#include "SkPicture.h"
+#include "SkStream.h"
 #include "SkRect.h"
-#include "SkSize.h"
-#include "CreateJavaOutputStreamAdaptor.h"
-#include "JNIHelp.h"
 
 namespace android {
 
-#define LOGD(x...) do { Log::Instance()->printf(Log::ELogD, x); } while(0)
+struct PageRecord {
 
-static jint nativeCreateDocument(JNIEnv* env, jobject clazz) {
-    return reinterpret_cast<jint>(new SkPDFDocument());
+    PageRecord(int width, int height, const SkRect& contentRect)
+            : mPicture(new SkPicture()), mWidth(width), mHeight(height) {
+        mContentRect = contentRect;
+    }
+
+    ~PageRecord() {
+        mPicture->unref();
+    }
+
+    SkPicture* const mPicture;
+    const int mWidth;
+    const int mHeight;
+    SkRect mContentRect;
+};
+
+class PdfDocument {
+public:
+    PdfDocument() {
+        mCurrentPage = NULL;
+    }
+
+    SkCanvas* startPage(int width, int height,
+            int contentLeft, int contentTop, int contentRight, int contentBottom) {
+        assert(mCurrentPage == NULL);
+
+        SkRect contentRect = SkRect::MakeLTRB(
+                contentLeft, contentTop, contentRight, contentBottom);
+        PageRecord* page = new PageRecord(width, height, contentRect);
+        mPages.push_back(page);
+        mCurrentPage = page;
+
+        SkCanvas* canvas = page->mPicture->beginRecording(
+                contentRect.width(), contentRect.height(), 0);
+
+        // We pass this canvas to Java where it is used to construct
+        // a Java Canvas object which dereferences the pointer when it
+        // is destroyed, so we have to bump up the reference count.
+        canvas->ref();
+
+        return canvas;
+    }
+
+    void finishPage() {
+        assert(mCurrentPage != NULL);
+        mCurrentPage->mPicture->endRecording();
+        mCurrentPage = NULL;
+    }
+
+    void write(SkWStream* stream) {
+        SkDocument* document = SkDocument::CreatePDF(stream);
+        for (unsigned i = 0; i < mPages.size(); i++) {
+            PageRecord* page =  mPages[i];
+
+            SkCanvas* canvas = document->beginPage(page->mWidth, page->mHeight,
+                    &(page->mContentRect));
+
+            canvas->clipRect(page->mContentRect);
+            canvas->translate(page->mContentRect.left(), page->mContentRect.top());
+            canvas->drawPicture(*page->mPicture);
+
+            document->endPage();
+        }
+        document->close();
+    }
+
+    void close() {
+        for (unsigned i = 0; i < mPages.size(); i++) {
+            delete mPages[i];
+        }
+        delete mCurrentPage;
+        mCurrentPage = NULL;
+    }
+
+private:
+    ~PdfDocument() {
+        close();
+    }
+
+    std::vector<PageRecord*> mPages;
+    PageRecord* mCurrentPage;
+};
+
+static jint nativeCreateDocument(JNIEnv* env, jobject thiz) {
+    return reinterpret_cast<jint>(new PdfDocument());
 }
 
-static void nativeFinalize(JNIEnv* env, jobject thiz, jint documentPtr) {
-    delete reinterpret_cast<SkPDFDocument*>(documentPtr);
-}
-
-static jint nativeCreatePage(JNIEnv* env, jobject thiz, jint pageWidth, jint pageHeight,
+static jint nativeStartPage(JNIEnv* env, jobject thiz, jint documentPtr,
+        jint pageWidth, jint pageHeight,
         jint contentLeft, jint contentTop, jint contentRight, jint contentBottom) {
-
-    SkMatrix transformation;
-    transformation.setTranslate(contentLeft, contentTop);
-
-    SkISize skPageSize = SkISize::Make(pageWidth, pageHeight);
-    SkISize skContentSize = SkISize::Make(contentRight - contentLeft, contentBottom - contentTop);
-
-    SkPDFDevice* skPdfDevice = new SkPDFDevice(skPageSize, skContentSize, transformation);
-    return reinterpret_cast<jint>(new SkCanvas(skPdfDevice));
+    PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr);
+    return reinterpret_cast<jint>(document->startPage(pageWidth, pageHeight,
+            contentLeft, contentTop, contentRight, contentBottom));
 }
 
-static void nativeAppendPage(JNIEnv* env, jobject thiz, jint documentPtr, jint pagePtr) {
-    SkCanvas* page = reinterpret_cast<SkCanvas*>(pagePtr);
-    SkPDFDocument* document = reinterpret_cast<SkPDFDocument*>(documentPtr);
-    SkPDFDevice* device = static_cast<SkPDFDevice*>(page->getDevice());
-    document->appendPage(device);
+static void nativeFinishPage(JNIEnv* env, jobject thiz, jint documentPtr) {
+    PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr);
+    document->finishPage();
 }
 
-static void nativeWriteTo(JNIEnv* env, jobject clazz, jint documentPtr,
-        jobject out, jbyteArray chunk) {
+static void nativeWriteTo(JNIEnv* env, jobject thiz, jint documentPtr, jobject out,
+        jbyteArray chunk) {
+    PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr);
     SkWStream* skWStream = CreateJavaOutputStreamAdaptor(env, out, chunk);
-    SkPDFDocument* document = reinterpret_cast<SkPDFDocument*>(documentPtr);
-    document->emitPDF(skWStream);
+    document->write(skWStream);
     delete skWStream;
 }
 
+static void nativeClose(JNIEnv* env, jobject thiz, jint documentPtr) {
+    PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr);
+    document->close();
+}
+
 static JNINativeMethod gPdfDocument_Methods[] = {
     {"nativeCreateDocument", "()I", (void*) nativeCreateDocument},
-    {"nativeFinalize", "(I)V", (void*) nativeFinalize},
-    {"nativeCreatePage", "(IIIIII)I",
-            (void*) nativeCreatePage},
-    {"nativeAppendPage", "(II)V", (void*) nativeAppendPage},
-    {"nativeWriteTo", "(ILjava/io/OutputStream;[B)V", (void*) nativeWriteTo}
+    {"nativeStartPage", "(IIIIIII)I", (void*) nativeStartPage},
+    {"nativeFinishPage", "(I)V", (void*) nativeFinishPage},
+    {"nativeWriteTo", "(ILjava/io/OutputStream;[B)V", (void*) nativeWriteTo},
+    {"nativeClose", "(I)V", (void*) nativeClose}
 };
 
 int register_android_graphics_pdf_PdfDocument(JNIEnv* env) {
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 44af199..4bb091d 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -632,7 +632,7 @@
     SkBitmap const * nativeBitmap =
             (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
     const SkBitmap& bitmap(*nativeBitmap);
-    SkBitmap::Config config = bitmap.getConfig();
+    SkBitmap::Config config = bitmap.config();
     return getInternalFormat(config);
 }
 
@@ -642,7 +642,7 @@
     SkBitmap const * nativeBitmap =
             (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
     const SkBitmap& bitmap(*nativeBitmap);
-    SkBitmap::Config config = bitmap.getConfig();
+    SkBitmap::Config config = bitmap.config();
     return getType(config);
 }
 
@@ -653,7 +653,7 @@
     SkBitmap const * nativeBitmap =
             (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
     const SkBitmap& bitmap(*nativeBitmap);
-    SkBitmap::Config config = bitmap.getConfig();
+    SkBitmap::Config config = bitmap.config();
     if (internalformat < 0) {
         internalformat = getInternalFormat(config);
     }
@@ -681,7 +681,7 @@
             SkColorTable* ctable = bitmap.getColorTable();
             memcpy(data, ctable->lockColors(), ctable->count() * sizeof(SkPMColor));
             memcpy(pixels, p, size);
-            ctable->unlockColors(false);
+            ctable->unlockColors();
             glCompressedTexImage2D(target, level, internalformat, w, h, border, imageSize, data);
             free(data);
         } else {
@@ -702,7 +702,7 @@
     SkBitmap const * nativeBitmap =
             (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
     const SkBitmap& bitmap(*nativeBitmap);
-    SkBitmap::Config config = bitmap.getConfig();
+    SkBitmap::Config config = bitmap.config();
     if (format < 0) {
         format = getInternalFormat(config);
         if (format == GL_PALETTE8_RGBA8_OES)
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index 1fe4b08..5b0a4b2 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -630,7 +630,7 @@
     if (producer == NULL)
         goto not_valid_surface;
 
-    window = new android::Surface(producer);
+    window = new android::Surface(producer, true);
 
     if (window == NULL)
         goto not_valid_surface;
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 1868469..dd178d8 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -39,6 +39,7 @@
 
 #include <SkCanvas.h>
 #include <SkBitmap.h>
+#include <SkImage.h>
 #include <SkRegion.h>
 
 #include <utils/misc.h>
@@ -178,7 +179,8 @@
 static inline SkBitmap::Config convertPixelFormat(PixelFormat format) {
     /* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then
         we can map to SkBitmap::kARGB_8888_Config, and optionally call
-        bitmap.setIsOpaque(true) on the resulting SkBitmap (as an accelerator)
+        bitmap.setAlphaType(kOpaque_SkAlphaType) on the resulting SkBitmap
+        (as an accelerator)
     */
     switch (format) {
     case PIXEL_FORMAT_RGBX_8888:    return SkBitmap::kARGB_8888_Config;
@@ -234,7 +236,7 @@
     ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
     bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr);
     if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
-        bitmap.setIsOpaque(true);
+        bitmap.setAlphaType(kOpaque_SkAlphaType);
     }
     if (outBuffer.width > 0 && outBuffer.height > 0) {
         bitmap.setPixels(outBuffer.bits);
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 67eade8..b8d3c20 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -61,51 +61,21 @@
 
 class ScreenshotPixelRef : public SkPixelRef {
 public:
-    ScreenshotPixelRef(SkColorTable* ctable) {
-        fCTable = ctable;
-        SkSafeRef(ctable);
+    ScreenshotPixelRef(const SkImageInfo& info, ScreenshotClient* screenshot) :
+      SkPixelRef(info),
+      mScreenshot(screenshot) {
         setImmutable();
     }
 
     virtual ~ScreenshotPixelRef() {
-        SkSafeUnref(fCTable);
-    }
-
-    status_t update(const sp<IBinder>& display, int width, int height,
-            int minLayer, int maxLayer, bool allLayers) {
-        status_t res = (width > 0 && height > 0)
-                ? (allLayers
-                        ? mScreenshot.update(display, width, height)
-                        : mScreenshot.update(display, width, height, minLayer, maxLayer))
-                : mScreenshot.update(display);
-        if (res != NO_ERROR) {
-            return res;
-        }
-
-        return NO_ERROR;
-    }
-
-    uint32_t getWidth() const {
-        return mScreenshot.getWidth();
-    }
-
-    uint32_t getHeight() const {
-        return mScreenshot.getHeight();
-    }
-
-    uint32_t getStride() const {
-        return mScreenshot.getStride();
-    }
-
-    uint32_t getFormat() const {
-        return mScreenshot.getFormat();
+        delete mScreenshot;
     }
 
 protected:
     // overrides from SkPixelRef
     virtual void* onLockPixels(SkColorTable** ct) {
-        *ct = fCTable;
-        return (void*)mScreenshot.getPixels();
+        *ct = NULL;
+        return (void*)mScreenshot->getPixels();
     }
 
     virtual void onUnlockPixels() {
@@ -113,8 +83,7 @@
 
     SK_DECLARE_UNFLATTENABLE_OBJECT()
 private:
-    ScreenshotClient mScreenshot;
-    SkColorTable*    fCTable;
+    ScreenshotClient* mScreenshot;
 
     typedef SkPixelRef INHERITED;
 };
@@ -147,19 +116,6 @@
     ctrl->decStrong((void *)nativeCreate);
 }
 
-static inline SkBitmap::Config convertPixelFormat(PixelFormat format) {
-    /* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then
-        we can map to SkBitmap::kARGB_8888_Config, and optionally call
-        bitmap.setIsOpaque(true) on the resulting SkBitmap (as an accelerator)
-    */
-    switch (format) {
-    case PIXEL_FORMAT_RGBX_8888:    return SkBitmap::kARGB_8888_Config;
-    case PIXEL_FORMAT_RGBA_8888:    return SkBitmap::kARGB_8888_Config;
-    case PIXEL_FORMAT_RGB_565:      return SkBitmap::kRGB_565_Config;
-    default:                        return SkBitmap::kNo_Config;
-    }
-}
-
 static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject displayTokenObj,
         jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) {
     sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
@@ -167,26 +123,50 @@
         return NULL;
     }
 
-    ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
-    if (pixels->update(displayToken, width, height,
-            minLayer, maxLayer, allLayers) != NO_ERROR) {
-        delete pixels;
+    ScreenshotClient* screenshot = new ScreenshotClient();
+    status_t res = (width > 0 && height > 0)
+            ? (allLayers
+                    ? screenshot->update(displayToken, width, height)
+                    : screenshot->update(displayToken, width, height, minLayer, maxLayer))
+            : screenshot->update(displayToken);
+    if (res != NO_ERROR) {
+        delete screenshot;
         return NULL;
     }
 
-    uint32_t w = pixels->getWidth();
-    uint32_t h = pixels->getHeight();
-    uint32_t s = pixels->getStride();
-    uint32_t f = pixels->getFormat();
-    ssize_t bpr = s * android::bytesPerPixel(f);
+    SkImageInfo screenshotInfo;
+    screenshotInfo.fWidth = screenshot->getWidth();
+    screenshotInfo.fHeight = screenshot->getHeight();
 
-    SkBitmap* bitmap = new SkBitmap();
-    bitmap->setConfig(convertPixelFormat(f), w, h, bpr);
-    if (f == PIXEL_FORMAT_RGBX_8888) {
-        bitmap->setIsOpaque(true);
+    switch (screenshot->getFormat()) {
+        case PIXEL_FORMAT_RGBX_8888: {
+            screenshotInfo.fColorType = kRGBA_8888_SkColorType;
+            screenshotInfo.fAlphaType = kIgnore_SkAlphaType;
+            break;
+        }
+        case PIXEL_FORMAT_RGBA_8888: {
+            screenshotInfo.fColorType = kRGBA_8888_SkColorType;
+            screenshotInfo.fAlphaType = kPremul_SkAlphaType;
+            break;
+        }
+        case PIXEL_FORMAT_RGB_565: {
+            screenshotInfo.fColorType = kRGB_565_SkColorType;
+            screenshotInfo.fAlphaType = kIgnore_SkAlphaType;
+            break;
+        }
+        default: {
+            delete screenshot;
+            return NULL;
+        }
     }
 
-    if (w > 0 && h > 0) {
+    // takes ownership of ScreenshotClient
+    ScreenshotPixelRef* pixels = new ScreenshotPixelRef(screenshotInfo, screenshot);
+    ssize_t rowBytes = screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
+
+    SkBitmap* bitmap = new SkBitmap();
+    bitmap->setConfig(screenshotInfo, rowBytes);
+    if (screenshotInfo.fWidth > 0 && screenshotInfo.fHeight > 0) {
         bitmap->setPixelRef(pixels)->unref();
         bitmap->lockPixels();
     } else {
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index 0f429005..7a4a20a 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -27,6 +27,7 @@
 
 #include <SkBitmap.h>
 #include <SkCanvas.h>
+#include <SkImage.h>
 
 namespace android {
 
@@ -156,7 +157,7 @@
     bitmap.setConfig(convertPixelFormat(buffer.format), buffer.width, buffer.height, bytesCount);
 
     if (buffer.format == WINDOW_FORMAT_RGBX_8888) {
-        bitmap.setIsOpaque(true);
+        bitmap.setAlphaType(kOpaque_SkAlphaType);
     }
 
     if (buffer.width > 0 && buffer.height > 0) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e8f0ab7..ca31109 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1056,8 +1056,9 @@
         android:permissionGroupFlags="personalInfo"
         android:priority="370" />
 
-    <!-- Allows an application to modify or abort outgoing
-         calls. -->
+    <!-- Allows an application to see the number being dialed during an outgoing
+         call with the option to redirect the call to a different number or
+         abort the call altogether. -->
     <permission android:name="android.permission.PROCESS_OUTGOING_CALLS"
         android:permissionGroup="android.permission-group.PHONE_CALLS"
         android:protectionLevel="dangerous"
@@ -1191,7 +1192,7 @@
         android:permissionGroup="android.permission-group.STORAGE"
         android:label="@string/permlab_manageDocs"
         android:description="@string/permdesc_manageDocs"
-        android:protectionLevel="signature|system" />
+        android:protectionLevel="signature" />
 
     <!-- ================================== -->
     <!-- Permissions for screenlock         -->
@@ -1526,7 +1527,7 @@
         @hide -->
     <permission android:name="android.permission.FORCE_STOP_PACKAGES"
         android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
-        android:protectionLevel="signature"
+        android:protectionLevel="signature|system"
         android:label="@string/permlab_forceStopPackages"
         android:description="@string/permdesc_forceStopPackages" />
 
@@ -1992,6 +1993,14 @@
         android:description="@string/permdesc_bindWallpaper"
         android:protectionLevel="signature|system" />
 
+    <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
+         to ensure that only the system can bind to it.
+         @hide -->
+    <permission android:name="android.permission.BIND_REMOTE_DISPLAY"
+        android:label="@string/permlab_bindRemoteDisplay"
+        android:description="@string/permdesc_bindRemoteDisplay"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by device administration receiver, to ensure that only the
          system can interact with it. -->
     <permission android:name="android.permission.BIND_DEVICE_ADMIN"
@@ -2681,6 +2690,15 @@
         <service android:name="android.hardware.location.GeofenceHardwareService"
             android:permission="android.permission.LOCATION_HARDWARE"
             android:exported="false" />
+
+        <service android:name="com.android.internal.backup.LocalTransportService"
+                android:permission="android.permission.CONFIRM_FULL_BACKUP"
+                android:exported="false">
+            <intent-filter>
+                <action android:name="android.backup.TRANSPORT_HOST" />
+            </intent-filter>
+        </service>
+
     </application>
 
 </manifest>
diff --git a/core/res/res/drawable-hdpi/ic_media_group_collapse.png b/core/res/res/drawable-hdpi/ic_media_group_collapse.png
deleted file mode 100644
index 89abf2c..0000000
--- a/core/res/res/drawable-hdpi/ic_media_group_collapse.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_group_expand.png b/core/res/res/drawable-hdpi/ic_media_group_expand.png
deleted file mode 100644
index d9470b2..0000000
--- a/core/res/res/drawable-hdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png
index b47d666..e215b96 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png
index 03b0d2a..a014e91 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png
index 13d803c..bb8bec1 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png
index 3ae436b..aa1737e 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png
index 24824fc..2c1434b 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png
index af3819b..dbdce3e 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png
index 83dc251..1101864 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png
index 8d9d592..e8e9069 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png
index 1310ec9..8595158 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png
index 1705074..14844d4 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png
index 7027b88..1565a29 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png
index 7027b88..9b8fe87 100644
--- a/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png
+++ b/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_0.png b/core/res/res/drawable-hdpi/ic_notification_cast_0.png
new file mode 100644
index 0000000..74f7dc0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_notification_cast_0.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_1.png b/core/res/res/drawable-hdpi/ic_notification_cast_1.png
new file mode 100644
index 0000000..c6d267d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_notification_cast_1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_2.png b/core/res/res/drawable-hdpi/ic_notification_cast_2.png
new file mode 100644
index 0000000..699b299
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_notification_cast_2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_on.png b/core/res/res/drawable-hdpi/ic_notification_cast_on.png
new file mode 100644
index 0000000..3eaf13a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_notification_cast_on.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png
deleted file mode 100644
index 35f27df..0000000
--- a/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/toast_frame.9.png b/core/res/res/drawable-hdpi/toast_frame.9.png
index ca65994..a804a8a 100644
--- a/core/res/res/drawable-hdpi/toast_frame.9.png
+++ b/core/res/res/drawable-hdpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/toast_frame_holo.9.png b/core/res/res/drawable-hdpi/toast_frame_holo.9.png
deleted file mode 100644
index a804a8a..0000000
--- a/core/res/res/drawable-hdpi/toast_frame_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/toast_frame.9.png b/core/res/res/drawable-ldpi/toast_frame.9.png
index 3b344ff..e64dc75 100644
--- a/core/res/res/drawable-ldpi/toast_frame.9.png
+++ b/core/res/res/drawable-ldpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_group_collapse.png b/core/res/res/drawable-mdpi/ic_media_group_collapse.png
deleted file mode 100644
index 34454ac..0000000
--- a/core/res/res/drawable-mdpi/ic_media_group_collapse.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_group_expand.png b/core/res/res/drawable-mdpi/ic_media_group_expand.png
deleted file mode 100644
index 8ce5a44..0000000
--- a/core/res/res/drawable-mdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png
index fa22d82..52e3a5a 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png
index a686cd1..319c57e 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png
index 6764598..f98c0a8 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png
index 94e0bb6..b74cdb5 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png
index 5ce2f20..a6a4bd0 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png
index 5105e90..106fd3a 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png
index 68c06ed..2c141ab 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png
index 6e9b144..0b62d0b 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png
index 45dc56f3d..23442b0 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png
index 46e743a..42b329f 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png
index e384691..58ff506 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png
index e384691..25257f8 100644
--- a/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png
+++ b/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_0.png b/core/res/res/drawable-mdpi/ic_notification_cast_0.png
new file mode 100644
index 0000000..a51a3cb
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_notification_cast_0.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_1.png b/core/res/res/drawable-mdpi/ic_notification_cast_1.png
new file mode 100644
index 0000000..e081367
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_notification_cast_1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_2.png b/core/res/res/drawable-mdpi/ic_notification_cast_2.png
new file mode 100644
index 0000000..a7f4de4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_notification_cast_2.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_on.png b/core/res/res/drawable-mdpi/ic_notification_cast_on.png
new file mode 100644
index 0000000..42de8c4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_notification_cast_on.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png
deleted file mode 100644
index f9c8678..0000000
--- a/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/toast_frame.9.png b/core/res/res/drawable-mdpi/toast_frame.9.png
index 9e93fe7..778e4e6 100644
--- a/core/res/res/drawable-mdpi/toast_frame.9.png
+++ b/core/res/res/drawable-mdpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/toast_frame_holo.9.png b/core/res/res/drawable-mdpi/toast_frame_holo.9.png
deleted file mode 100644
index 778e4e6..0000000
--- a/core/res/res/drawable-mdpi/toast_frame_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/cab_background_top_holo_dark.9.png b/core/res/res/drawable-xhdpi/cab_background_top_holo_dark.9.png
index 6b31579..7b6d48b 100644
--- a/core/res/res/drawable-xhdpi/cab_background_top_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/cab_background_top_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/cab_background_top_holo_light.9.png b/core/res/res/drawable-xhdpi/cab_background_top_holo_light.9.png
index df0121b..bafe878 100644
--- a/core/res/res/drawable-xhdpi/cab_background_top_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/cab_background_top_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png
deleted file mode 100644
index 2fb7428..0000000
--- a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_group_expand.png b/core/res/res/drawable-xhdpi/ic_media_group_expand.png
deleted file mode 100644
index 5755b9d..0000000
--- a/core/res/res/drawable-xhdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png
index 1d48e12..4119cff 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png
index 2c8d1ec..b629a57 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png
index 00b2043..fe81128 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png
index ce1d939..9b59eaf 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png
index 3064b46..1a513c1 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png
index 4316686..ff78803 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png
index 25c4e31..4c4b624 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png
index 8e32bd2..60f8c4d 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png
index aeaa78f..cdb2f30 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png
index 85277fa..97a10a3 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png
index b01dbe8..a19a083 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png
index c19a2ad..db30613 100644
--- a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png
+++ b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_0.png b/core/res/res/drawable-xhdpi/ic_notification_cast_0.png
new file mode 100644
index 0000000..818c1cd
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_notification_cast_0.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_1.png b/core/res/res/drawable-xhdpi/ic_notification_cast_1.png
new file mode 100644
index 0000000..2a56e31
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_notification_cast_1.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_2.png b/core/res/res/drawable-xhdpi/ic_notification_cast_2.png
new file mode 100644
index 0000000..3515a76
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_notification_cast_2.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_on.png b/core/res/res/drawable-xhdpi/ic_notification_cast_on.png
new file mode 100644
index 0000000..142065b
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_notification_cast_on.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png
deleted file mode 100644
index 4cc0ee8..0000000
--- a/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/toast_frame.9.png b/core/res/res/drawable-xhdpi/toast_frame.9.png
index 1f63420..77e69c7 100644
--- a/core/res/res/drawable-xhdpi/toast_frame.9.png
+++ b/core/res/res/drawable-xhdpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/toast_frame_holo.9.png b/core/res/res/drawable-xhdpi/toast_frame_holo.9.png
deleted file mode 100644
index 77e69c7..0000000
--- a/core/res/res/drawable-xhdpi/toast_frame_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/cab_background_top_holo_dark.9.png b/core/res/res/drawable-xxhdpi/cab_background_top_holo_dark.9.png
index 418f322..cbb4f4c 100644
--- a/core/res/res/drawable-xxhdpi/cab_background_top_holo_dark.9.png
+++ b/core/res/res/drawable-xxhdpi/cab_background_top_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/cab_background_top_holo_light.9.png b/core/res/res/drawable-xxhdpi/cab_background_top_holo_light.9.png
index a5a59d4..6d467f7 100644
--- a/core/res/res/drawable-xxhdpi/cab_background_top_holo_light.9.png
+++ b/core/res/res/drawable-xxhdpi/cab_background_top_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png
index 7b0c383..6fad4a64 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png
index efb624e..865617c 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png
index 5ee57e4..44d98d5 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png
index 6bc2e4a..b5b29b0 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png
index c13af9c..c807b50 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png
index 744fb42..3fc7188 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png
index ca4d59c..d54f44a 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png
index fde5688..092fe8c 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png
index b8715c3..17c1d99 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png
index 668bb25..4fd5808 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png
index 7f54a62..906401e 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png
index 2df924d..d29e563 100644
--- a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png
+++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png
new file mode 100644
index 0000000..7ef0d3d
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png
new file mode 100644
index 0000000..ed04beb
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png
new file mode 100644
index 0000000..d62d27d
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png
new file mode 100644
index 0000000..d562602
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png
deleted file mode 100644
index fea4774..0000000
--- a/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/toast_frame.9.png b/core/res/res/drawable-xxhdpi/toast_frame.9.png
index 882b9c6..edecb63 100644
--- a/core/res/res/drawable-xxhdpi/toast_frame.9.png
+++ b/core/res/res/drawable-xxhdpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png b/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png
deleted file mode 100644
index edecb63..0000000
--- a/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/ic_notification_cast_connecting.xml b/core/res/res/drawable/ic_notification_cast_connecting.xml
new file mode 100644
index 0000000..a390bce
--- /dev/null
+++ b/core/res/res/drawable/ic_notification_cast_connecting.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="false">
+    <item android:drawable="@drawable/ic_notification_cast_0" android:duration="500" />
+    <item android:drawable="@drawable/ic_notification_cast_1" android:duration="500" />
+    <item android:drawable="@drawable/ic_notification_cast_2" android:duration="500" />
+    <item android:drawable="@drawable/ic_notification_cast_1" android:duration="500" />
+</animation-list>
diff --git a/core/res/res/layout/immersive_mode_cling.xml b/core/res/res/layout/immersive_mode_cling.xml
index f97225e..c0cd93d 100644
--- a/core/res/res/layout/immersive_mode_cling.xml
+++ b/core/res/res/layout/immersive_mode_cling.xml
@@ -37,8 +37,8 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:background="@drawable/cling_bg"
-            android:paddingLeft="14dp"
-            android:paddingRight="14dp"
+            android:paddingStart="14dp"
+            android:paddingEnd="14dp"
             android:paddingTop="24dp"
             android:paddingBottom="24dp">
             <TextView
diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml
new file mode 100644
index 0000000..d1c6267
--- /dev/null
+++ b/core/res/res/layout/media_route_chooser_dialog.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical"
+              android:divider="?android:attr/dividerHorizontal"
+              android:showDividers="middle">
+    <!-- List of routes. -->
+    <ListView android:id="@+id/media_route_list"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:layout_weight="1" />
+
+    <!-- Content to show when list is empty. -->
+    <LinearLayout android:id="@android:id/empty"
+              android:layout_width="match_parent"
+              android:layout_height="64dp"
+              android:orientation="horizontal"
+              android:paddingLeft="16dp"
+              android:paddingRight="16dp"
+              android:visibility="gone">
+        <ProgressBar android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_gravity="center" />
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_gravity="center"
+                  android:paddingLeft="16dp"
+                  android:text="@string/media_route_chooser_searching" />
+    </LinearLayout>
+
+    <!-- Settings button. -->
+    <LinearLayout android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  style="?attr/buttonBarStyle">
+        <Button android:id="@+id/media_route_extended_settings_button"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                style="?attr/buttonBarButtonStyle"
+                android:gravity="center"
+                android:text="@string/media_route_chooser_extended_settings"
+                android:visibility="gone" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/media_route_chooser_layout.xml b/core/res/res/layout/media_route_chooser_layout.xml
deleted file mode 100644
index 5fcb8c8..0000000
--- a/core/res/res/layout/media_route_chooser_layout.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content"
-              android:orientation="vertical"
-              android:showDividers="middle"
-              android:divider="?android:attr/dividerHorizontal">
-    <LinearLayout android:layout_width="match_parent"
-                  android:layout_height="?android:attr/listPreferredItemHeight"
-                  android:gravity="center_vertical"
-                  android:padding="8dp">
-        <ImageView android:id="@+id/volume_icon"
-                   android:layout_width="48dp"
-                   android:layout_height="48dp"
-                   android:src="@android:drawable/ic_audio_vol"
-                   android:gravity="center"
-                   android:scaleType="center" />
-        <SeekBar android:id="@+id/volume_slider"
-                 android:layout_width="0dp"
-                 android:layout_height="wrap_content"
-                 android:layout_weight="1"
-                 android:layout_marginStart="8dp"
-                 android:layout_marginEnd="8dp" />
-        <ImageButton android:id="@+id/extended_settings"
-                     android:layout_width="48dp"
-                     android:layout_height="48dp"
-                     android:background="?android:attr/selectableItemBackground"
-                     android:src="@android:drawable/ic_sysbar_quicksettings"
-                     android:visibility="gone" />
-    </LinearLayout>
-    <ListView android:id="@id/list"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content" />
-</LinearLayout>
diff --git a/core/res/res/layout/media_route_controller_dialog.xml b/core/res/res/layout/media_route_controller_dialog.xml
new file mode 100644
index 0000000..78287e0
--- /dev/null
+++ b/core/res/res/layout/media_route_controller_dialog.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical"
+              android:divider="?android:attr/dividerHorizontal"
+              android:showDividers="middle">
+    <!-- Optional volume slider section. -->
+    <LinearLayout android:id="@+id/media_route_volume_layout"
+                  android:layout_width="match_parent"
+                  android:layout_height="64dp"
+                  android:gravity="center_vertical"
+                  android:padding="8dp"
+                  android:visibility="gone">
+        <ImageView android:layout_width="48dp"
+                   android:layout_height="48dp"
+                   android:src="@drawable/ic_audio_vol"
+                   android:gravity="center"
+                   android:scaleType="center" />
+        <SeekBar android:id="@+id/media_route_volume_slider"
+                 android:layout_width="0dp"
+                 android:layout_height="wrap_content"
+                 android:layout_weight="1"
+                 android:layout_marginLeft="8dp"
+                 android:layout_marginRight="8dp" />
+    </LinearLayout>
+
+    <!-- Optional content view section. -->
+    <FrameLayout android:id="@+id/media_route_control_frame"
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:visibility="gone" />
+
+    <!-- Disconnect button. -->
+    <LinearLayout android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  style="?attr/buttonBarStyle">
+        <Button android:id="@+id/media_route_disconnect_button"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                style="?attr/buttonBarButtonStyle"
+                android:gravity="center"
+                android:text="@string/media_route_controller_disconnect" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml
index 423d544..bdca433 100644
--- a/core/res/res/layout/media_route_list_item.xml
+++ b/core/res/res/layout/media_route_list_item.xml
@@ -20,13 +20,6 @@
               android:background="@drawable/item_background_activated_holo_dark"
               android:gravity="center_vertical">
 
-    <ImageView android:layout_width="56dp"
-               android:layout_height="56dp"
-               android:scaleType="center"
-               android:id="@+id/icon"
-               android:visibility="gone"
-               android:duplicateParentState="true" />
-
     <LinearLayout android:layout_width="0dp"
                   android:layout_height="match_parent"
                   android:layout_weight="1"
@@ -53,14 +46,4 @@
                   android:duplicateParentState="true" />
     </LinearLayout>
 
-    <ImageButton
-        android:layout_width="56dp"
-        android:layout_height="56dp"
-        android:id="@+id/expand_button"
-        android:background="?android:attr/selectableItemBackground"
-        android:src="@drawable/ic_media_group_expand"
-        android:scaleType="center"
-        android:visibility="gone"
-        android:duplicateParentState="true" />
-
 </LinearLayout>
diff --git a/core/res/res/layout/media_route_list_item_checkable.xml b/core/res/res/layout/media_route_list_item_checkable.xml
deleted file mode 100644
index 5fb82f7..0000000
--- a/core/res/res/layout/media_route_list_item_checkable.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<com.android.internal.view.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="?android:attr/listPreferredItemHeight"
-              android:background="?android:attr/selectableItemBackground"
-              android:gravity="center_vertical">
-
-    <ImageView android:layout_width="56dp"
-               android:layout_height="56dp"
-               android:scaleType="center"
-               android:id="@+id/icon"
-               android:visibility="gone" />
-
-    <LinearLayout android:layout_width="0dp"
-                  android:layout_height="match_parent"
-                  android:layout_weight="1"
-                  android:orientation="vertical"
-                  android:gravity="start|center_vertical"
-                  android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-                  android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
-        <TextView android:id="@android:id/text1"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:singleLine="true"
-                  android:ellipsize="marquee"
-                  android:textAppearance="?android:attr/textAppearanceMedium" />
-
-        <TextView android:id="@android:id/text2"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:singleLine="true"
-                  android:ellipsize="marquee"
-                  android:textAppearance="?android:attr/textAppearanceSmall" />
-    </LinearLayout>
-
-    <CheckBox
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="16dp"
-        android:id="@+id/check"
-        android:focusable="false"
-        android:clickable="false" />
-
-</com.android.internal.view.CheckableLinearLayout>
diff --git a/core/res/res/layout/media_route_list_item_collapse_group.xml b/core/res/res/layout/media_route_list_item_collapse_group.xml
deleted file mode 100644
index 323e24d..0000000
--- a/core/res/res/layout/media_route_list_item_collapse_group.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:layout_width="match_parent"
-             android:layout_height="wrap_content"
-             android:background="?android:attr/selectableItemBackground">
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="?android:attr/listPreferredItemHeightSmall"
-        android:background="#19ffffff"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:gravity="center_vertical">
-
-        <TextView android:layout_width="0dp"
-                  android:layout_height="wrap_content"
-                  android:layout_weight="1"
-                  android:singleLine="true"
-                  android:ellipsize="marquee"
-                  android:text="@string/media_route_chooser_grouping_done"
-                  android:textAppearance="?android:attr/textAppearanceMedium" />
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/ic_media_group_collapse"
-            android:scaleType="center" />
-
-    </LinearLayout>
-</FrameLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/media_route_list_item_section_header.xml b/core/res/res/layout/media_route_list_item_section_header.xml
deleted file mode 100644
index 949635f..0000000
--- a/core/res/res/layout/media_route_list_item_section_header.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:layout_width="match_parent"
-             android:layout_height="wrap_content"
-             android:paddingTop="16dp">
-    <TextView
-        android:id="@android:id/text1"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:background="#19ffffff"
-        android:textStyle="bold"
-        android:textAllCaps="true"
-        android:gravity="center_vertical"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:minHeight="24dp"
-        />
-</FrameLayout>
diff --git a/core/res/res/layout/media_route_list_item_top_header.xml b/core/res/res/layout/media_route_list_item_top_header.xml
deleted file mode 100644
index 0c49b24..0000000
--- a/core/res/res/layout/media_route_list_item_top_header.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/text1"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:textAppearance="?android:attr/textAppearanceSmall"
-    android:background="#19ffffff"
-    android:textStyle="bold"
-    android:textAllCaps="true"
-    android:gravity="center_vertical"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:minHeight="24dp"
-/>
diff --git a/core/res/res/values-mcc302-mnc500/config.xml b/core/res/res/values-mcc302-mnc500/config.xml
deleted file mode 100644
index 706570c..0000000
--- a/core/res/res/values-mcc302-mnc500/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Don't use roaming icon for considered operators -->
-    <string-array translatable="false" name="config_operatorConsideredNonRoaming">
-        <item>302</item>
-    </string-array>
-</resources>
diff --git a/core/res/res/values-mcc302-mnc510/config.xml b/core/res/res/values-mcc302-mnc510/config.xml
deleted file mode 100644
index 706570c..0000000
--- a/core/res/res/values-mcc302-mnc510/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Don't use roaming icon for considered operators -->
-    <string-array translatable="false" name="config_operatorConsideredNonRoaming">
-        <item>302</item>
-    </string-array>
-</resources>
diff --git a/core/res/res/values-mcc310-mnc260/config.xml b/core/res/res/values-mcc310-mnc260/config.xml
index 886ecbe..d602c9f 100644
--- a/core/res/res/values-mcc310-mnc260/config.xml
+++ b/core/res/res/values-mcc310-mnc260/config.xml
@@ -21,22 +21,6 @@
      for different hardware and product builds. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-      <item>1</item>
-      <item>4</item>
-      <item>7</item>
-      <item>9</item>
-    </integer-array>
-
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">T-Mobile Tethering,pcweb.tmobile.com,,,,,,,,,310,260,,DUN</string>
-
     <!-- Configure mobile network MTU. Carrier specific value is set here.
     -->
     <integer name="config_mobile_mtu">1440</integer>
diff --git a/core/res/res/values-mcc311-mnc190/config.xml b/core/res/res/values-mcc311-mnc190/config.xml
new file mode 100644
index 0000000..a6c4d1b
--- /dev/null
+++ b/core/res/res/values-mcc311-mnc190/config.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You my obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
+    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
+    <integer-array translatable="false" name="config_tether_upstream_types">
+      <item>1</item>
+      <item>4</item>
+      <item>7</item>
+      <item>9</item>
+    </integer-array>
+
+    <!-- String containing the apn value for tethering.  May be overriden by secure settings
+         TETHER_DUN_APN.  Value is a comma separated series of strings:
+         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
+         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
+    <string translatable="false" name="config_tether_apndata">Tether,broadband.cellular1.net,,,,,,,,,311,190,,DUN</string>
+
+</resources>
diff --git a/core/res/res/values-mcc404-mnc17/config.xml b/core/res/res/values-mcc404-mnc17/config.xml
deleted file mode 100644
index 685d012..0000000
--- a/core/res/res/values-mcc404-mnc17/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Show roaming icon though same named operators. -->
-    <string-array translatable="false" name="config_sameNamedOperatorConsideredRoaming">
-        <item>40491</item>
-    </string-array>
-</resources>
diff --git a/core/res/res/values-mcc404-mnc85/config.xml b/core/res/res/values-mcc404/config.xml
similarity index 94%
copy from core/res/res/values-mcc404-mnc85/config.xml
copy to core/res/res/values-mcc404/config.xml
index fd780ab..a106b0a 100644
--- a/core/res/res/values-mcc404-mnc85/config.xml
+++ b/core/res/res/values-mcc404/config.xml
@@ -20,6 +20,7 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Show roaming icon though same named operators. -->
     <string-array translatable="false" name="config_sameNamedOperatorConsideredRoaming">
-        <item>40483</item>
+        <item>404</item>
+        <item>405</item>
     </string-array>
 </resources>
diff --git a/core/res/res/values-mcc404-mnc85/config.xml b/core/res/res/values-mcc405/config.xml
similarity index 94%
copy from core/res/res/values-mcc404-mnc85/config.xml
copy to core/res/res/values-mcc405/config.xml
index fd780ab..a106b0a 100644
--- a/core/res/res/values-mcc404-mnc85/config.xml
+++ b/core/res/res/values-mcc405/config.xml
@@ -20,6 +20,7 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Show roaming icon though same named operators. -->
     <string-array translatable="false" name="config_sameNamedOperatorConsideredRoaming">
-        <item>40483</item>
+        <item>404</item>
+        <item>405</item>
     </string-array>
 </resources>
diff --git a/core/res/res/values-mcc404-mnc85/config.xml b/core/res/res/values-mcc520/config.xml
similarity index 96%
rename from core/res/res/values-mcc404-mnc85/config.xml
rename to core/res/res/values-mcc520/config.xml
index fd780ab..b2f3efa 100644
--- a/core/res/res/values-mcc404-mnc85/config.xml
+++ b/core/res/res/values-mcc520/config.xml
@@ -20,6 +20,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Show roaming icon though same named operators. -->
     <string-array translatable="false" name="config_sameNamedOperatorConsideredRoaming">
-        <item>40483</item>
+        <item>520</item>
     </string-array>
 </resources>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index ef30b98..91af50a 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -22,7 +22,7 @@
     <!-- Do not translate. These are all of the drawable resources that should be preloaded by
          the zygote process before it starts forking application processes. -->
     <array name="preloaded_drawables">
-       <item>@drawable/toast_frame_holo</item>
+       <item>@drawable/toast_frame</item>
        <item>@drawable/btn_check_on_pressed_holo_light</item>
        <item>@drawable/btn_check_on_pressed_holo_dark</item>
        <item>@drawable/btn_check_on_holo_light</item>
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 10a5d85..18e4f2f 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -26,5 +26,4 @@
     <bool name="show_ongoing_ime_switcher">true</bool>
     <bool name="action_bar_expanded_action_views_exclusive">true</bool>
     <bool name="target_honeycomb_needs_options_menu">true</bool>
-    <bool name="flip_controller_fallback_keys">false</bool>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 42ea384..98ed72d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -352,6 +352,10 @@
     <!-- Wifi driver supports batched scan -->
     <bool translatable="false" name="config_wifi_batched_scan_supported">false</bool>
 
+    <!-- Wifi driver's fallback country code; WS is ISO-Alpha2 code for Samoa which
+         has restrictions on can be scanned; which may satisfy quite a few regulatory issues. -->
+    <string translatable="false" name="config_wifi_unknown_country_code">WS</string>
+
     <!-- Flag indicating whether the we should enable the automatic brightness in Settings.
          Software implementation will be used if config_hardware_auto_brightness_available is not set -->
     <bool name="config_automatic_brightness_available">false</bool>
@@ -585,8 +589,8 @@
     <!-- Disable lockscreen rotation by default -->
     <bool name="config_enableLockScreenRotation">false</bool>
 
-    <!-- Disable lockscreen translucent decor by default -->
-    <bool name="config_enableLockScreenTranslucentDecor">false</bool>
+    <!-- Enable lockscreen translucent decor by default -->
+    <bool name="config_enableLockScreenTranslucentDecor">true</bool>
 
     <!-- Enable translucent decor by default -->
     <bool name="config_enableTranslucentDecor">true</bool>
@@ -919,7 +923,7 @@
     <!-- Max space (in MB) allocated to DownloadManager to store the downloaded
          files if they are to be stored in DownloadManager's data dir,
          which typically is /data/data/com.android.providers.downloads/files -->
-    <integer name="config_downloadDataDirSize">100</integer>
+    <integer name="config_downloadDataDirSize">200</integer>
 
     <!-- Max number of downloads allowed to proceed concurrently -->
     <integer name="config_MaxConcurrentDownloadsAllowed">5</integer>
@@ -1176,6 +1180,22 @@
          where if the preferred is used we don't try the others. -->
     <bool name="config_dontPreferApn">false</bool>
 
+    <!-- The list of ril radio technologies (see ServiceState.java) which only support
+         a single data connection at one time.  This may change by carrier via
+         overlays (some don't support multiple pdp on UMTS).  All unlisted radio
+         tech types support unlimited types (practically only 2-4 used). -->
+    <integer-array name="config_onlySingleDcAllowed">
+        <item>4</item>  <!-- IS95A -->
+        <item>5</item>  <!-- IS95B -->
+        <item>6</item>  <!-- 1xRTT -->
+        <item>7</item>  <!-- EVDO_0 -->
+        <item>8</item>  <!-- EVDO_A -->
+        <item>12</item> <!-- EVDO_B -->
+    </integer-array>
+
+    <!-- Set to true if after a provisioning apn the radio should be restarted -->
+    <bool name="config_restartRadioAfterProvisioning">false</bool>
+
     <!-- Vibrator pattern to be used as the default for notifications
          that specify DEFAULT_VIBRATE.
      -->
@@ -1288,4 +1308,12 @@
         <item>333</item>
         <item>353</item>
     </string-array>
+
+    <!-- Override the default detection behavior for the framework method
+         android.view.ViewConfiguration#hasPermanentMenuKey().
+         Valid settings are:
+         0 - No change. Use the default autodetection behavior.
+         1 - The device DOES have a permanent menu key; ignore autodetection.
+         2 - The device DOES NOT have a permanent menu key; ignore autodetection. -->
+    <integer name="config_overrideHasPermanentMenuKey">0</integer>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 62f26c6..098fbbe 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -625,9 +625,9 @@
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_processOutgoingCalls">reroute outgoing calls</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_processOutgoingCalls">Allows the app to process
-      outgoing calls and change the number to be dialed. This permission allows
-      the app to monitor, redirect, or prevent outgoing calls.</string>
+    <string name="permdesc_processOutgoingCalls">Allows the app to see the
+        number being dialed during an outgoing call with the option to redirect
+        the call to a different number or abort the call altogether.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_receiveSms">receive text messages (SMS)</string>
@@ -932,13 +932,13 @@
     <!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_getAppOpsStats">Allows the app to retrieve
         collected application operation statistics. Not for use by normal apps.</string>
-    
+
     <!-- [CHAR LIMIT=NONE] Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_updateAppOpsStats">modify app ops statistics</string>
     <!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_updateAppOpsStats">Allows the app to modify
         collected application operation statistics. Not for use by normal apps.</string>
-    
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_backup">control system backup and restore</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1052,6 +1052,12 @@
         interface of a wallpaper. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindRemoteDisplay">bind to a remote display</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindRemoteDisplay">Allows the holder to bind to the top-level
+        interface of a remote display. Should never be needed for normal apps.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_bindRemoteViews">bind to a widget service</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_bindRemoteViews">Allows the holder to bind to the top-level
@@ -2241,6 +2247,10 @@
     <!-- Other SIP address type. Same context as Other phone type. -->
     <string name="sipAddressTypeOther">Other</string>
 
+    <!-- Error message that is displayed when the user clicks on a quick contacts badge, but
+         there is no contacts application installed that can display the quick contact -->
+    <string name="quick_contacts_not_available">No application found to view this contact.</string>
+
     <!-- Instructions telling the user to enter their SIM PIN to unlock the keyguard.
          Displayed in one line in a large font.  -->
     <string name="keyguard_password_enter_pin_code">Type PIN code</string>
@@ -4090,12 +4100,24 @@
     <!-- Description of a wireless display route. [CHAR LIMIT=50] -->
     <string name="wireless_display_route_description">Wireless display</string>
 
-    <!-- "Done" button for MediaRouter chooser dialog when grouping routes. [CHAR LIMIT=NONE] -->
-    <string name="media_route_chooser_grouping_done">Done</string>
-
     <!-- Content description of a MediaRouteButton for accessibility support -->
     <string name="media_route_button_content_description">Media output</string>
 
+    <!-- Title of the media route chooser dialog. [CHAR LIMIT=40] -->
+    <string name="media_route_chooser_title">Connect to device</string>
+
+    <!-- Title of the media route chooser dialog for selecting remote display routes. [CHAR LIMIT=40] -->
+    <string name="media_route_chooser_title_for_remote_display">Cast screen to device</string>
+
+    <!-- Placeholder text to show when no devices have been found. [CHAR LIMIT=50] -->
+    <string name="media_route_chooser_searching">Searching for devices\u2026</string>
+
+    <!-- Button to access extended settings.  [CHAR LIMIT=30] -->
+    <string name="media_route_chooser_extended_settings">Settings</string>
+
+    <!-- Button to disconnect from a media route.  [CHAR LIMIT=30] -->
+    <string name="media_route_controller_disconnect">Disconnect</string>
+
     <!-- Status message for remote routes attempting to scan/determine availability -->
     <string name="media_route_status_scanning">Scanning...</string>
 
@@ -4128,10 +4150,14 @@
     <!-- Title text to append when the display is secure.  [CHAR LIMIT=30] -->
     <string name="display_manager_overlay_display_secure_suffix">, secure</string>
 
+    <!-- Title of the notification to indicate the process of connecting to a wifi display.  [CHAR LIMIT=50] -->
+    <string name="wifi_display_notification_connecting_title">Casting screen</string>
+    <!-- Message of the notification to indicate the process of connecting to a wifi display.  [CHAR LIMIT=80] -->
+    <string name="wifi_display_notification_connecting_message">Connecting to <xliff:g id="name">%1$s</xliff:g></string>
     <!-- Title of the notification to indicate an active wifi display connection.  [CHAR LIMIT=50] -->
-    <string name="wifi_display_notification_title">Wireless display is connected</string>
+    <string name="wifi_display_notification_connected_title">Casting screen</string>
     <!-- Message of the notification to indicate an active wifi display connection.  [CHAR LIMIT=80] -->
-    <string name="wifi_display_notification_message">This screen is showing on another device</string>
+    <string name="wifi_display_notification_connected_message">Connected to <xliff:g id="name">%1$s</xliff:g></string>
     <!-- Label of a button to disconnect an active wifi display connection.  [CHAR LIMIT=25] -->
     <string name="wifi_display_notification_disconnect">Disconnect</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 22a9402..1200276 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -283,11 +283,11 @@
   <java-symbol type="bool" name="config_safe_media_volume_enabled" />
   <java-symbol type="bool" name="config_camera_sound_forced" />
   <java-symbol type="bool" name="config_dontPreferApn" />
+  <java-symbol type="bool" name="config_restartRadioAfterProvisioning" />
   <java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" />
   <java-symbol type="bool" name="config_useFixedVolume" />
   <java-symbol type="bool" name="config_forceDefaultOrientation" />
   <java-symbol type="bool" name="config_wifi_batched_scan_supported" />
-  <java-symbol type="bool" name="flip_controller_fallback_keys" />
 
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_extraFreeKbytesAdjust" />
@@ -318,6 +318,7 @@
   <java-symbol type="integer" name="config_mobile_mtu" />
   <java-symbol type="integer" name="config_volte_replacement_rat"/>
   <java-symbol type="integer" name="config_valid_wappush_index" />
+  <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
 
@@ -477,6 +478,7 @@
   <java-symbol type="string" name="config_ntpServer" />
   <java-symbol type="string" name="config_tether_apndata" />
   <java-symbol type="string" name="config_useragentprofile_url" />
+  <java-symbol type="string" name="config_wifi_unknown_country_code" />
   <java-symbol type="string" name="config_wifi_p2p_device_type" />
   <java-symbol type="string" name="contentServiceSync" />
   <java-symbol type="string" name="contentServiceSyncNotificationTitle" />
@@ -689,6 +691,7 @@
   <java-symbol type="string" name="mobile_provisioning_apn" />
   <java-symbol type="string" name="mobile_provisioning_url" />
   <java-symbol type="string" name="mobile_redirected_provisioning_url" />
+  <java-symbol type="string" name="quick_contacts_not_available" />
   <java-symbol type="string" name="reboot_safemode_confirm" />
   <java-symbol type="string" name="reboot_safemode_title" />
   <java-symbol type="string" name="relationTypeAssistant" />
@@ -1096,7 +1099,11 @@
   <java-symbol type="drawable" name="notification_template_icon_bg" />
   <java-symbol type="drawable" name="notification_template_icon_low_bg" />
   <java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
+  <java-symbol type="drawable" name="ic_media_route_off_holo_dark" />
+  <java-symbol type="drawable" name="ic_media_route_connecting_holo_dark" />
   <java-symbol type="drawable" name="ic_media_route_disabled_holo_dark" />
+  <java-symbol type="drawable" name="ic_notification_cast_connecting" />
+  <java-symbol type="drawable" name="ic_notification_cast_on" />
   <java-symbol type="drawable" name="cling_button" />
   <java-symbol type="drawable" name="cling_arrow_up" />
   <java-symbol type="drawable" name="cling_bg" />
@@ -1253,17 +1260,17 @@
 
   <java-symbol type="attr" name="mediaRouteButtonStyle" />
   <java-symbol type="attr" name="externalRouteEnabledDrawable" />
-  <java-symbol type="id" name="extended_settings" />
-  <java-symbol type="id" name="check" />
-  <java-symbol type="id" name="volume_slider" />
-  <java-symbol type="id" name="volume_icon" />
-  <java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
-  <java-symbol type="layout" name="media_route_chooser_layout" />
-  <java-symbol type="layout" name="media_route_list_item_top_header" />
-  <java-symbol type="layout" name="media_route_list_item_section_header" />
+  <java-symbol type="layout" name="media_route_chooser_dialog" />
+  <java-symbol type="layout" name="media_route_controller_dialog" />
   <java-symbol type="layout" name="media_route_list_item" />
-  <java-symbol type="layout" name="media_route_list_item_checkable" />
-  <java-symbol type="layout" name="media_route_list_item_collapse_group" />
+  <java-symbol type="id" name="media_route_list" />
+  <java-symbol type="id" name="media_route_volume_layout" />
+  <java-symbol type="id" name="media_route_volume_slider" />
+  <java-symbol type="id" name="media_route_control_frame" />
+  <java-symbol type="id" name="media_route_disconnect_button" />
+  <java-symbol type="id" name="media_route_extended_settings_button" />
+  <java-symbol type="string" name="media_route_chooser_title" />
+  <java-symbol type="string" name="media_route_chooser_title_for_remote_display" />
   <java-symbol type="string" name="bluetooth_a2dp_audio_route_name" />
 
   <java-symbol type="dimen" name="config_minScalingSpan" />
@@ -1435,6 +1442,7 @@
   <java-symbol type="array" name="config_locationProviderPackageNames" />
   <java-symbol type="array" name="config_defaultNotificationVibePattern" />
   <java-symbol type="array" name="config_notificationFallbackVibePattern" />
+  <java-symbol type="array" name="config_onlySingleDcAllowed" />
   <java-symbol type="bool" name="config_animateScreenLights" />
   <java-symbol type="bool" name="config_automatic_brightness_available" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
@@ -1450,7 +1458,6 @@
   <java-symbol type="color" name="config_defaultNotificationColor" />
   <java-symbol type="color" name="input_method_navigation_guard" />
   <java-symbol type="drawable" name="ic_notification_ime_default" />
-  <java-symbol type="drawable" name="ic_notify_wifidisplay" />
   <java-symbol type="drawable" name="ic_menu_refresh" />
   <java-symbol type="drawable" name="stat_notify_car_mode" />
   <java-symbol type="drawable" name="stat_notify_disabled" />
@@ -1593,8 +1600,10 @@
   <java-symbol type="string" name="vpn_lockdown_error" />
   <java-symbol type="string" name="vpn_lockdown_config" />
   <java-symbol type="string" name="wallpaper_binding_label" />
-  <java-symbol type="string" name="wifi_display_notification_title" />
-  <java-symbol type="string" name="wifi_display_notification_message" />
+  <java-symbol type="string" name="wifi_display_notification_connecting_title" />
+  <java-symbol type="string" name="wifi_display_notification_connecting_message" />
+  <java-symbol type="string" name="wifi_display_notification_connected_title" />
+  <java-symbol type="string" name="wifi_display_notification_connected_message" />
   <java-symbol type="string" name="wifi_display_notification_disconnect" />
   <java-symbol type="style" name="Theme.Dialog.AppError" />
   <java-symbol type="style" name="Theme.Toast" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 4c80e7d..c8d9fc6 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -1048,7 +1048,7 @@
         <item name="presentationTheme">@android:style/Theme.Holo.Dialog.Presentation</item>
 
         <!-- Toast attributes -->
-        <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item>
+        <item name="toastFrameBackground">@android:drawable/toast_frame</item>
 
         <!-- Panel attributes -->
         <item name="panelBackground">@android:drawable/menu_hardkey_panel_holo_dark</item>
@@ -1363,7 +1363,7 @@
         <item name="presentationTheme">@android:style/Theme.Holo.Light.Dialog.Presentation</item>
 
         <!-- Toast attributes -->
-        <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item>
+        <item name="toastFrameBackground">@android:drawable/toast_frame</item>
 
         <!-- Panel attributes -->
         <item name="panelBackground">@android:drawable/menu_hardkey_panel_holo_light</item>
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index 7af3b9c..3215e17 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -58,4 +58,12 @@
   </array>
   <!-- This is the battery capacity in mAh (measured at nominal voltage) -->
   <item name="battery.capacity">1000</item>
+
+  <array name="wifi.batchedscan"> <!-- mA -->
+      <value>.0002</value> <!-- 1-8/hr -->
+      <value>.002</value>  <!-- 9-64/hr -->
+      <value>.02</value>   <!-- 65-512/hr -->
+      <value>.2</value>    <!-- 513-4,096/hr -->
+      <value>2</value>    <!-- 4097-/hr -->
+  </array>
 </device>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java
index 5a4a2d0..9d97ac5 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java
@@ -101,9 +101,10 @@
     }
 
     /*
-     * Transition from CONNECTED -> DISCONNECTED:
-     *    CONNECTED->DISCONNECTING->DISCONNECTED
-     * return false if any state transition is not valid and save a message in mReason
+     * Verifies state transition from CONNECTED->...-> DISCONNECTED.
+     *
+     * returns false if initial state or target state is not correct, or if there is
+     * any transition from DISCONNECTING/DISCONNECTED -> CONNECTED.
      */
     public boolean transitToDisconnection () {
         mReason = "states: " + printStates();
@@ -120,13 +121,13 @@
         for (int i = 1; i < mStateDepository.size() - 1; i++) {
             State preState = mStateDepository.get(i-1);
             State curState = mStateDepository.get(i);
-            if ((preState == State.CONNECTED) && ((curState == State.DISCONNECTING) ||
+            if (preState == curState) {
+                continue;
+            } else if ((preState == State.CONNECTED) && ((curState == State.DISCONNECTING) ||
                     (curState == State.DISCONNECTED))) {
                 continue;
             } else if ((preState == State.DISCONNECTING) && (curState == State.DISCONNECTED)) {
                 continue;
-            } else if ((preState == State.DISCONNECTED) && (curState == State.DISCONNECTED)) {
-                continue;
             } else {
                 mReason += " Transition state from " + preState.toString() + " to " +
                         curState.toString() + " is not valid.";
@@ -136,7 +137,12 @@
         return true;
     }
 
-    // DISCONNECTED->CONNECTING->CONNECTED
+    /*
+     * Verifies state transition from DISCONNECTED->...-> CONNECTED.
+     *
+     * returns false if initial state or target state is not correct, or if there is
+     * any transition from CONNECED -> DISCONNECTED.
+     */
     public boolean transitToConnection() {
         mReason = "states: " + printStates();
         if (mStateDepository.get(0) != State.DISCONNECTED) {
@@ -152,14 +158,15 @@
         for (int i = 1; i < mStateDepository.size(); i++) {
             State preState = mStateDepository.get(i-1);
             State curState = mStateDepository.get(i);
+            if (preState == curState) {
+                continue;
+            }
             if ((preState == State.DISCONNECTED) && ((curState == State.CONNECTING) ||
-                    (curState == State.CONNECTED) || (curState == State.DISCONNECTED))) {
+                    (curState == State.CONNECTED))) {
                 continue;
-            } else if ((preState == State.CONNECTING) && (curState == State.CONNECTED)) {
-                continue;
-            } else if ((preState == State.CONNECTED) && (curState == State.CONNECTED)) {
-                continue;
-            } else {
+             } else if ((preState == State.CONNECTING) && (curState == State.CONNECTED)) {
+                 continue;
+             } else {
                 mReason += " Transition state from " + preState.toString() + " to " +
                         curState.toString() + " is not valid.";
                 return false;
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
index 76b702e..4a58f88 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
@@ -36,8 +36,6 @@
 import com.android.bandwidthtest.util.ConnectionUtil;
 
 import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * Test that downloads files from a test server and reports the bandwidth metrics collected.
@@ -131,8 +129,8 @@
         results.putString("device_id", mDeviceId);
         results.putString("timestamp", ts);
         results.putInt("size", FILE_SIZE);
-        AddStatsToResults(PROF_LABEL, prof_stats, results);
-        AddStatsToResults(PROC_LABEL, proc_stats, results);
+        addStatsToResults(PROF_LABEL, prof_stats, results, mUid);
+        addStatsToResults(PROC_LABEL, proc_stats, results, mUid);
         getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
 
         // Clean up.
@@ -185,8 +183,8 @@
         results.putString("device_id", mDeviceId);
         results.putString("timestamp", ts);
         results.putInt("size", FILE_SIZE);
-        AddStatsToResults(PROF_LABEL, prof_stats, results);
-        AddStatsToResults(PROC_LABEL, proc_stats, results);
+        addStatsToResults(PROF_LABEL, prof_stats, results, mUid);
+        addStatsToResults(PROC_LABEL, proc_stats, results, mUid);
         getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
 
         // Clean up.
@@ -242,8 +240,9 @@
         results.putString("device_id", mDeviceId);
         results.putString("timestamp", ts);
         results.putInt("size", FILE_SIZE);
-        AddStatsToResults(PROF_LABEL, prof_stats, results);
-        AddStatsToResults(PROC_LABEL, proc_stats, results);
+        addStatsToResults(PROF_LABEL, prof_stats, results, mUid);
+        // remember to use download manager uid for proc stats
+        addStatsToResults(PROC_LABEL, proc_stats, results, downloadManagerUid);
         getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
 
         // Clean up.
@@ -302,46 +301,35 @@
      * @param label to attach to this given stats.
      * @param stats {@link NetworkStats} to add.
      * @param results {@link Bundle} to be added to.
+     * @param uid for which to report the results.
      */
-    public void AddStatsToResults(String label, NetworkStats stats, Bundle results){
+    public void addStatsToResults(String label, NetworkStats stats, Bundle results, int uid){
         if (results == null || results.isEmpty()) {
             Log.e(LOG_TAG, "Empty bundle provided.");
             return;
         }
-        // Merge stats across all sets.
-        Map<Integer, Entry> totalStats = new HashMap<Integer, Entry>();
+        Entry totalStats = null;
         for (int i = 0; i < stats.size(); ++i) {
             Entry statsEntry = stats.getValues(i, null);
             // We are only interested in the all inclusive stats.
             if (statsEntry.tag != 0) {
                 continue;
             }
-            Entry mapEntry = null;
-            if (totalStats.containsKey(statsEntry.uid)) {
-                mapEntry = totalStats.get(statsEntry.uid);
-                switch (statsEntry.set) {
-                    case NetworkStats.SET_ALL:
-                        mapEntry.rxBytes = statsEntry.rxBytes;
-                        mapEntry.txBytes = statsEntry.txBytes;
-                        break;
-                    case NetworkStats.SET_DEFAULT:
-                    case NetworkStats.SET_FOREGROUND:
-                        mapEntry.rxBytes += statsEntry.rxBytes;
-                        mapEntry.txBytes += statsEntry.txBytes;
-                        break;
-                    default:
-                        Log.w(LOG_TAG, "Invalid state found in NetworkStats.");
-                }
+            // skip stats for other uids
+            if (statsEntry.uid != uid) {
+                continue;
+            }
+            if (totalStats == null || statsEntry.set == NetworkStats.SET_ALL) {
+                totalStats = statsEntry;
             } else {
-                totalStats.put(statsEntry.uid, statsEntry);
+                totalStats.rxBytes += statsEntry.rxBytes;
+                totalStats.txBytes += statsEntry.txBytes;
             }
         }
-        // Ouput merged stats to bundle.
-        for (Entry entry : totalStats.values()) {
-            results.putInt(label + "uid", entry.uid);
-            results.putLong(label + "tx", entry.txBytes);
-            results.putLong(label + "rx", entry.rxBytes);
-        }
+        // Output merged stats to bundle.
+        results.putInt(label + "uid", totalStats.uid);
+        results.putLong(label + "tx", totalStats.txBytes);
+        results.putLong(label + "rx", totalStats.rxBytes);
     }
 
     /**
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index 695a74f..01d22ee 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -477,4 +477,128 @@
     ctrl:                               fallback MENU
 }
 
-### Gamepad buttons are handled by the view root ###
+### Gamepad buttons ###
+
+key BUTTON_A {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_B {
+    base:                               fallback BACK
+}
+
+key BUTTON_C {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_X {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_Y {
+    base:                               fallback BACK
+}
+
+key BUTTON_Z {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_L1 {
+    base:                               none
+}
+
+key BUTTON_R1 {
+    base:                               none
+}
+
+key BUTTON_L2 {
+    base:                               none
+}
+
+key BUTTON_R2 {
+    base:                               none
+}
+
+key BUTTON_THUMBL {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_THUMBR {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_START {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_SELECT {
+    base:                               fallback MENU
+}
+
+key BUTTON_MODE {
+    base:                               fallback MENU
+}
+
+key BUTTON_1 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_2 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_3 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_4 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_5 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_6 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_7 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_8 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_9 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_10 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_11 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_12 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_13 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_14 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_15 {
+    base:                               fallback DPAD_CENTER
+}
+
+key BUTTON_16 {
+    base:                               fallback DPAD_CENTER
+}
diff --git a/data/sounds/AudioPackage10.mk b/data/sounds/AudioPackage10.mk
index 783e1f8..ae4bc88 100644
--- a/data/sounds/AudioPackage10.mk
+++ b/data/sounds/AudioPackage10.mk
@@ -17,19 +17,19 @@
         $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \
         $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
 	$(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
 	$(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
-	$(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \
-	$(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
+	$(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:system/media/audio/ui/Lock.ogg \
+	$(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:system/media/audio/ui/Unlock.ogg \
 	$(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
 	$(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
 	$(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \
diff --git a/data/sounds/AudioPackage11.mk b/data/sounds/AudioPackage11.mk
index b30be56..3c09297 100644
--- a/data/sounds/AudioPackage11.mk
+++ b/data/sounds/AudioPackage11.mk
@@ -17,19 +17,19 @@
 	$(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \
 	$(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
 	$(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
 	$(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
-	$(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \
-	$(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
+	$(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:system/media/audio/ui/Lock.ogg \
+	$(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:system/media/audio/ui/Unlock.ogg \
 	$(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
 	$(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
 	$(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \
diff --git a/data/sounds/AudioPackage8.mk b/data/sounds/AudioPackage8.mk
index 49b6154..0f4b8ad 100644
--- a/data/sounds/AudioPackage8.mk
+++ b/data/sounds/AudioPackage8.mk
@@ -17,11 +17,11 @@
 	$(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:system/media/audio/alarms/Plutonium.ogg \
 	$(LOCAL_PATH)/alarms/ogg/Promethium.ogg:system/media/audio/alarms/Promethium.ogg \
 	$(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:system/media/audio/ui/KeypressStandard.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:system/media/audio/ui/KeypressDelete.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
diff --git a/data/sounds/AudioPackage9.mk b/data/sounds/AudioPackage9.mk
index 87b7764..36dc921 100644
--- a/data/sounds/AudioPackage9.mk
+++ b/data/sounds/AudioPackage9.mk
@@ -17,11 +17,11 @@
         $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \
         $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
 	$(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:system/media/audio/ui/KeypressStandard.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:system/media/audio/ui/KeypressDelete.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
-	$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
+	$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
diff --git a/data/sounds/notifications/ogg/Tethys_48k.ogg b/data/sounds/notifications/ogg/Tethys_48k.ogg
index a9d8bbd..355d522 100644
--- a/data/sounds/notifications/ogg/Tethys_48k.ogg
+++ b/data/sounds/notifications/ogg/Tethys_48k.ogg
Binary files differ
diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd
index 6d29c69..92ecd24 100644
--- a/docs/html/about/dashboards/index.jd
+++ b/docs/html/about/dashboards/index.jd
@@ -61,7 +61,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on March 3, 2014.
+<p style="clear:both"><em>Data collected during a 7-day period ending on April 1, 2014.
 <br/>Any versions with less than 0.1% distribution are not shown.</em>
 </p>
 
@@ -92,7 +92,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on March 3, 2014.
+<p style="clear:both"><em>Data collected during a 7-day period ending on April 1, 2014.
 <br/>Any screen configurations with less than 0.1% distribution are not shown.</em></p>
 
 
@@ -133,17 +133,17 @@
 </tr>
 <tr>
 <td>2.0</th>
-<td>91.1%</td>
+<td>89.4%</td>
 </tr>
 <tr>
 <td>3.0</th>
-<td>8.8%</td>
+<td>10.5%</td>
 </tr>
 </table>
 
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on March 3, 2014</em></p>
+<p style="clear:both"><em>Data collected during a 7-day period ending on April 1, 2014</em></p>
 
 
 
@@ -161,17 +161,17 @@
 var VERSION_DATA =
 [
   {
-    "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CHoneycomb%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat&chd=t%3A1.2%2C19.0%2C0.1%2C15.2%2C62.0%2C2.5&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&chs=500x250&cht=p",
+    "chart": "//chart.googleapis.com/chart?cht=p&chs=500x250&chco=c4df9b%2C6fad0c&chf=bg%2Cs%2C00000000&chd=t%3A1.1%2C17.8%2C0.1%2C14.3%2C61.4%2C5.3&chl=Froyo%7CGingerbread%7CHoneycomb%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat",
     "data": [
       {
         "api": 8,
         "name": "Froyo",
-        "perc": "1.2"
+        "perc": "1.1"
       },
       {
         "api": 10,
         "name": "Gingerbread",
-        "perc": "19.0"
+        "perc": "17.8"
       },
       {
         "api": 13,
@@ -181,27 +181,27 @@
       {
         "api": 15,
         "name": "Ice Cream Sandwich",
-        "perc": "15.2"
+        "perc": "14.3"
       },
       {
         "api": 16,
         "name": "Jelly Bean",
-        "perc": "35.3"
+        "perc": "34.4"
       },
       {
         "api": 17,
         "name": "Jelly Bean",
-        "perc": "17.1"
+        "perc": "18.1"
       },
       {
         "api": 18,
         "name": "Jelly Bean",
-        "perc": "9.6"
+        "perc": "8.9"
       },
       {
         "api": 19,
         "name": "KitKat",
-        "perc": "2.5"
+        "perc": "5.3"
       }
     ]
   }
@@ -218,16 +218,15 @@
       "Large": {
         "hdpi": "0.6",
         "ldpi": "0.7",
-        "mdpi": "4.3",
+        "mdpi": "4.4",
         "tvdpi": "1.5",
         "xhdpi": "0.6"
       },
       "Normal": {
         "hdpi": "33.7",
-        "ldpi": "0.2",
-        "mdpi": "13.6",
-        "xhdpi": "19.9",
-        "xxhdpi": "11.9"
+        "mdpi": "13.2",
+        "xhdpi": "19.8",
+        "xxhdpi": "12.5"
       },
       "Small": {
         "ldpi": "8.1"
@@ -235,12 +234,12 @@
       "Xlarge": {
         "hdpi": "0.3",
         "ldpi": "0.1",
-        "mdpi": "4.3",
-        "xhdpi": "0.2"
+        "mdpi": "4.2",
+        "xhdpi": "0.3"
       }
     },
-    "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chd=t%3A9.1%2C22.2%2C1.5%2C34.6%2C20.7%2C11.9&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&chs=400x250&cht=p",
-    "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chd=t%3A4.9%2C7.7%2C79.3%2C8.1&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&chs=400x250&cht=p"
+    "densitychart": "//chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b%2C6fad0c&chf=bg%2Cs%2C00000000&chd=t%3A8.9%2C21.8%2C1.5%2C34.6%2C20.7%2C12.6&chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi",
+    "layoutchart": "//chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b%2C6fad0c&chf=bg%2Cs%2C00000000&chd=t%3A4.9%2C7.8%2C79.3%2C8.1&chl=Xlarge%7CLarge%7CNormal%7CSmall"
   }
 ];
 
diff --git a/docs/html/google/index.jd b/docs/html/google/index.jd
index b743b66..2e97d62 100644
--- a/docs/html/google/index.jd
+++ b/docs/html/google/index.jd
@@ -125,7 +125,7 @@
 <img src="{@docRoot}images/google/analytics.png" width="40" />
   </div>
     <h4><a class="external-link" 
-href="https://developers.google.com/analytics/devguides/collection/android/v2/"
+href="https://developers.google.com/analytics/devguides/collection/android/v4/"
   >Google Analytics</a></h4>
 
 <p>Measure your success and gain insights into how users engage with your app content
diff --git a/docs/html/guide/components/intents-common.jd b/docs/html/guide/components/intents-common.jd
index 506cf9d..826dcff 100644
--- a/docs/html/guide/components/intents-common.jd
+++ b/docs/html/guide/components/intents-common.jd
@@ -11,6 +11,18 @@
         <span class="less" style="display:none">show less</span></a></h2>
 
 <ol id="tocIntents" class="hide-nested">
+  <li><a href="#Clock">Alarm Clock</a>
+    <ol>
+      <li><a href="#CreateAlarm">Create an alarm</a></li>
+      <li><a href="#CreateTimer">Create a timer</a></li>
+      <li><a href="#ShowAlarms">Show all alarms</a></li>
+    </ol>
+  </li>
+  <li><a href="#Calendar">Calendar</a>
+    <ol>
+      <li><a href="#AddEvent">Add a calendar event</a></li>
+    </ol>
+  </li>
   <li><a href="#Camera">Camera</a>
     <ol>
       <li><a href="#ImageCapture">Capture a picture or video and return it</a></li>
@@ -116,6 +128,292 @@
 
 
 
+
+
+
+
+<h2 id="Clock">Alarm Clock</h2>
+
+
+<h3 id="CreateAlarm">Create an alarm</h3>
+
+<p>To create a new alarm, use the {@link android.provider.AlarmClock#ACTION_SET_ALARM}
+action and specify alarm details such as the time and message using extras defined below.</p>
+
+<p class="note"><strong>Note:</strong> Only the hour, minutes, and message extras are available
+since Android 2.3 (API level 9). The other extras were added in later versions of the platform.</p>
+
+<dl>
+<dt><b>Action</b></dt>
+<dd>{@link android.provider.AlarmClock#ACTION_SET_ALARM}</dd>
+
+<dt><b>Data URI</b></dt>
+<dd>None</dd>
+
+<dt><b>MIME Type</b></dt>
+<dd>None
+</dd>
+
+<dt><b>Extras</b></dt>
+<dd>
+  <dl>
+    <dt>{@link android.provider.AlarmClock#EXTRA_HOUR}</dt>
+      <dd>The hour for the alarm.</dd>
+    <dt>{@link android.provider.AlarmClock#EXTRA_MINUTES}</dt>
+      <dd>The minutes for the alarm.</dd>
+    <dt>{@link android.provider.AlarmClock#EXTRA_MESSAGE}</dt>
+      <dd>A custom message to identify the alarm.</dd>
+    <dt>{@link android.provider.AlarmClock#EXTRA_DAYS}</dt>
+      <dd>An {@link java.util.ArrayList} including each week day on which this alarm should
+      be repeated. Each day must be declared with an integer from the {@link java.util.Calendar}
+      class such as {@link java.util.Calendar#MONDAY}.
+      <p>For a one-time alarm, do not specify this extra.</dd>
+    <dt>{@link android.provider.AlarmClock#EXTRA_RINGTONE}</dt>
+      <dd>A {@code content:} URI specifying a ringtone to use with the alarm, or {@link
+      android.provider.AlarmClock#VALUE_RINGTONE_SILENT} for no ringtone.
+      <p>To use the default ringtone, do not specify this extra.</dd>
+    <dt>{@link android.provider.AlarmClock#EXTRA_VIBRATE}</dt>
+      <dd>A boolean specifying whether to vibrate for this alarm.</dd>
+    <dt>{@link android.provider.AlarmClock#EXTRA_SKIP_UI}</dt>
+      <dd>A boolean specifying whether the responding app should skip its UI when setting the alarm.
+      If true, the app should bypass any confirmation UI and simply set the specified alarm.</dd>
+  </dl>
+</dd>
+
+
+</dl>
+
+<p><b>Example intent:</b></p>
+<pre>
+public void createAlarm(String message, int hour, int minutes) {
+    Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM)
+            .putExtra(AlarmClock.EXTRA_MESSAGE, message)
+            .putExtra(AlarmClock.EXTRA_HOUR, hour)
+            .putExtra(AlarmClock.EXTRA_MINUTES, minutes);
+    if (intent.resolveActivity(getPackageManager()) != null) {
+        startActivity(intent);
+    }
+}
+</pre>
+
+<div class="note"><strong>Note:</strong>
+<p>In order to invoke the {@link
+android.provider.AlarmClock#ACTION_SET_ALARM} intent, your app must have the
+{@link android.Manifest.permission#SET_ALARM} permission:</p>
+<pre>
+&lt;uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
+</pre>
+</div>
+
+
+<p><b>Example intent filter:</b></p>
+<pre>
+&lt;activity ...>
+    &lt;intent-filter>
+        &lt;action android:name="android.intent.action.SET_ALARM" />
+        &lt;category android:name="android.intent.category.DEFAULT" />
+    &lt;/intent-filter>
+&lt;/activity>
+</pre>
+
+
+
+<h3 id="CreateTimer">Create a timer</h3>
+
+<p>To create a countdown timer, use the {@link android.provider.AlarmClock#ACTION_SET_TIMER}
+action and specify timer details such as the duration using extras defined below.</p>
+
+<p class="note"><strong>Note:</strong> This intent was added
+in Android 4.4 (API level 19).</p>
+
+<dl>
+<dt><b>Action</b></dt>
+<dd>{@link android.provider.AlarmClock#ACTION_SET_TIMER}</dd>
+
+<dt><b>Data URI</b></dt>
+<dd>None</dd>
+
+<dt><b>MIME Type</b></dt>
+<dd>None
+</dd>
+
+<dt><b>Extras</b></dt>
+<dd>
+  <dl>
+    <dt>{@link android.provider.AlarmClock#EXTRA_LENGTH}</dt>
+      <dd>The length of the timer in seconds.</dd>
+    <dt>{@link android.provider.AlarmClock#EXTRA_MESSAGE}</dt>
+      <dd>A custom message to identify the timer.</dd>
+    <dt>{@link android.provider.AlarmClock#EXTRA_SKIP_UI}</dt>
+      <dd>A boolean specifying whether the responding app should skip its UI when setting the timer.
+      If true, the app should bypass any confirmation UI and simply start the specified timer.</dd>
+  </dl>
+</dd>
+
+
+</dl>
+
+<p><b>Example intent:</b></p>
+<pre>
+public void startTimer(String message, int seconds) {
+    Intent intent = new Intent(AlarmClock.ACTION_SET_TIMER)
+            .putExtra(AlarmClock.EXTRA_MESSAGE, message)
+            .putExtra(AlarmClock.EXTRA_LENGTH, seconds)
+            .putExtra(AlarmClock.EXTRA_SKIP_UI, true);
+    if (intent.resolveActivity(getPackageManager()) != null) {
+        startActivity(intent);
+    }
+}
+</pre>
+
+<div class="note"><strong>Note:</strong>
+<p>In order to invoke the {@link
+android.provider.AlarmClock#ACTION_SET_TIMER} intent, your app must have the
+{@link android.Manifest.permission#SET_ALARM} permission:</p>
+<pre>
+&lt;uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
+</pre>
+</div>
+
+
+<p><b>Example intent filter:</b></p>
+<pre>
+&lt;activity ...>
+    &lt;intent-filter>
+        &lt;action android:name="android.intent.action.SET_TIMER" />
+        &lt;category android:name="android.intent.category.DEFAULT" />
+    &lt;/intent-filter>
+&lt;/activity>
+</pre>
+
+
+
+
+
+<h3 id="ShowAlarms">Show all alarms</h3>
+
+<p>To show the list of alarms, use the {@link android.provider.AlarmClock#ACTION_SHOW_ALARMS}
+action.</p>
+
+<p>Although not many apps will invoke this intent (it's primarily used by system apps),
+any app that behaves as an alarm clock should implement
+this intent filter and respond by showing the list of current alarms.</p>
+
+<p class="note"><strong>Note:</strong> This intent was added
+in Android 4.4 (API level 19).</p>
+
+<dl>
+<dt><b>Action</b></dt>
+<dd>{@link android.provider.AlarmClock#ACTION_SHOW_ALARMS}</dd>
+
+<dt><b>Data URI</b></dt>
+<dd>None</dd>
+
+<dt><b>MIME Type</b></dt>
+<dd>None
+</dd>
+</dl>
+
+<p><b>Example intent filter:</b></p>
+<pre>
+&lt;activity ...>
+    &lt;intent-filter>
+        &lt;action android:name="android.intent.action.SHOW_ALARMS" />
+        &lt;category android:name="android.intent.category.DEFAULT" />
+    &lt;/intent-filter>
+&lt;/activity>
+</pre>
+
+
+
+
+
+
+<h2 id="Calendar">Calendar</h2>
+
+
+<h3 id="AddEvent">Add a calendar event</h3>
+
+<p>To add a new event to the user's calendar, use the {@link android.content.Intent#ACTION_INSERT}
+action and specify the data URI with {@link android.provider.CalendarContract.Events#CONTENT_URI
+Events.CONTENT_URI}. You can then specify various event details using extras defined below.</p>
+
+<dl>
+<dt><b>Action</b></dt>
+<dd>{@link android.content.Intent#ACTION_INSERT}</dd>
+
+<dt><b>Data URI</b></dt>
+<dd>{@link android.provider.CalendarContract.Events#CONTENT_URI
+Events.CONTENT_URI}</dd>
+
+<dt><b>MIME Type</b></dt>
+<dd>{@code "vnd.android.cursor.dir/event"}
+</dd>
+
+<dt><b>Extras</b></dt>
+<dd>
+  <dl>
+    <dt>{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY}</dt>
+      <dd>A boolean specifying whether this is an all-day event.</dd>
+    <dt>{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME}</dt>
+      <dd>The start time of the event (milliseconds since epoch).</dd>
+    <dt>{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME}</dt>
+      <dd>The end time of the event (milliseconds since epoch).</dd>
+    <dt>{@link android.provider.CalendarContract.EventsColumns#TITLE}</dt>
+      <dd>The event title.</dd>
+    <dt>{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}</dt>
+      <dd>The event description.</dd>
+    <dt>{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}</dt>
+      <dd>The event location.</dd>
+    <dt>{@link android.content.Intent#EXTRA_EMAIL}</dt>
+      <dd>A comma-separated list of email addresses that specify the invitees.</dd>
+  </dl>
+  <p>Many more event details can be specified using the constants defined in the
+  {@link android.provider.CalendarContract.EventsColumns} class.</p>
+</dd>
+
+
+</dl>
+
+<p><b>Example intent:</b></p>
+<pre>
+public void addEvent(String title, String location, Calendar begin, Calendar end) {
+    Intent intent = new Intent(Intent.ACTION_INSERT)
+            .setData(Events.CONTENT_URI)
+            .putExtra(Events.TITLE, title)
+            .putExtra(Events.EVENT_LOCATION, location)
+            .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, begin)
+            .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end);
+    if (intent.resolveActivity(getPackageManager()) != null) {
+        startActivity(intent);
+    }
+}
+</pre>
+
+
+<p><b>Example intent filter:</b></p>
+<pre>
+&lt;activity ...>
+    &lt;intent-filter>
+        &lt;action android:name="android.intent.action.INSERT" />
+        &lt;data android:mimeType="vnd.android.cursor.dir/event" />
+        &lt;category android:name="android.intent.category.DEFAULT" />
+    &lt;/intent-filter>
+&lt;/activity>
+</pre>
+
+
+
+
+
+
+
+
+
+
+
+
+
 <h2 id="Camera">Camera</h2>
 
 
@@ -211,6 +509,7 @@
 
 
 
+
 <h2 id="Contacts">Contacts/People App</h2>
 
 
@@ -427,7 +726,7 @@
 <dd>The type is inferred from contact URI.
 </dd>
 
-<dt><b>Extras</b> (optional)</dt>
+<dt><b>Extras</b></dt>
 <dd>One or more of the extras defined in {@link android.provider.ContactsContract.Intents.Insert}
 so you can populate fields of the contact details.
 </dd>
@@ -469,7 +768,7 @@
 <dt><b>MIME Type</b></dt>
 <dd>{@link android.provider.ContactsContract.Contacts#CONTENT_TYPE Contacts.CONTENT_TYPE}</dd>
 
-<dt><b>Extras</b> (optional)</dt>
+<dt><b>Extras</b></dt>
 <dd>One or more of the extras defined in {@link android.provider.ContactsContract.Intents.Insert}.
 </dd>
 </dl>
@@ -523,7 +822,7 @@
   </dl>
 </dd>
 
-<dt><b>Extras</b> (optional)</dt>
+<dt><b>Extras</b></dt>
 <dd>
   <dl>
     <dt>{@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}</dt>
@@ -648,7 +947,7 @@
 <dd>The MIME type corresponding to the file type the user should select.
 </dd>
 
-<dt><b>Extras</b> (optional)</dt>
+<dt><b>Extras</b></dt>
 <dd>
   <dl>
     <dt>{@link android.content.Intent#EXTRA_ALLOW_MULTIPLE}
@@ -768,7 +1067,7 @@
 <dd>The MIME type corresponding to the file type the user should select.
 </dd>
 
-<dt><b>Extras</b> (optional)</dt>
+<dt><b>Extras</b></dt>
 <dd>
   <dl>
     <dt>{@link android.content.Intent#EXTRA_MIME_TYPES}
@@ -1139,7 +1438,7 @@
   </dl>
 </dd>
 
-<dt><b>Extras</b> (optional)</dt>
+<dt><b>Extras</b></dt>
 <dd>
   <dl>
     <dt><code>"subject"</code></dt>
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index 73d5b74..0a234aa 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -368,7 +368,10 @@
                   <span class="en">Media Playback</span></a>
                 </li>
             <li><a href="<?cs var:toroot ?>guide/topics/media/mediarouter.html">
-                  <span class="en">MediaRouter</span></a>
+                  <span class="en">Media Router</span></a>
+                </li>
+            <li><a href="<?cs var:toroot ?>guide/topics/media/mediarouteprovider.html">
+                  <span class="en">Media Route Provider</span></a>
                 </li>
             <li><a href="<?cs var:toroot ?>guide/appendix/media-formats.html">
                    <span class="en">Supported Media Formats</span></a>
diff --git a/docs/html/guide/topics/media/mediarouteprovider.jd b/docs/html/guide/topics/media/mediarouteprovider.jd
new file mode 100644
index 0000000..389fbfb
--- /dev/null
+++ b/docs/html/guide/topics/media/mediarouteprovider.jd
@@ -0,0 +1,453 @@
+page.title=Media Route Provider
+page.tags="mediarouteprovider","mediacontrolintent"
+@jd:body
+
+<div id="qv-wrapper">
+  <div id="qv">
+    <h2>In this document</h2>
+    <ol>
+      <li><a href="#overview">Overview</a>
+        <ol>
+          <li><a href="#dist">Distribution of route providers</a></li>
+          <li><a href="#playback-types">Types of playback</a></li>
+          <li><a href="#mr-packages">Media router packages</a></li>
+        </ol>
+      </li>
+      <li><a href="#provider-service">Creating a Provider Service</a></li>
+      <li><a href="#route-caps">Specifying Route Capabilities</a>
+        <ol>
+          <li><a href="#route-cat">Route categories</a></li>
+          <li><a href="#media-types">Media types and protocols</a></li>
+          <li><a href="#playback-ctrls">Playback controls</a></li>
+          <li><a href="#mrpd">MediaRouteProviderDescriptor</a></li>
+        </ol>
+      </li>
+      <li><a href="#ctrl-routes">Controlling Routes</a></li>
+    </ol>
+    <h2>Key Classes</h2>
+    <ol>
+      <li>{@link android.support.v7.media.MediaRouteProvider}</li>
+      <li>{@link android.support.v7.media.MediaRouteProviderDescriptor}</li>
+      <li>{@link android.support.v7.media.MediaRouteProvider.RouteController RouteController}</li>
+    </ol>
+    <h2>Related Samples</h2>
+    <ol>
+      <li><a href="{@docRoot}samples/MediaRouter/index.html">MediaRouter</a></li>
+    </ol>
+  </div>
+</div>
+
+<p>Users want to play media content from their Android devices bigger, brighter, and louder on
+  connected playback devices such as televisions, stereos,
+  and home theater equipment. As a manufacturer of these devices, allowing Android users to
+  instantly show a picture, play a song, or share a video for friends and family using your product
+  can make it much more compelling and engaging.</p>
+
+<p>The Android media router framework allows manufacturers to enable playback on their devices
+  through a standardized interface called a {@link android.support.v7.media.MediaRouteProvider}.
+  A route provider defines a common interface for playing media on a receiver device, making it
+  possible to play media on your equipment from any Android application that supports media
+  routes.</p>
+
+<p>This guide discusses how to create a media route provider for a receiver device and make it
+  available to other media playback applications that run on Android.</p>
+
+<h2 id="overview">Overview</h2>
+
+<p>The Android media router framework enables media app developers and media playback device
+  manufacturers to connect through a common API and common user interface. App developers that
+  implement a {@link android.support.v7.media.MediaRouter} interface can then connect to the
+  framework and play content to devices that participate in the media router framework. Media
+  playback device manufacturers can participate in the framework by publishing a {@link
+  android.support.v7.media.MediaRouteProvider} that allows other applications to connect to and
+  play media on the receiver devices. Figure 1 illustrates how an app connects to a receiving
+  device through the media router framework.</p>
+
+<img src="{@docRoot}images/mediarouter/media-route-provider-framework.png" alt="" id="figure1"/>
+<p class="img-caption">
+  <strong>Figure 1.</strong> Overview of how media route provider classes provide communication
+  from a media app to a receiver device.
+</p>
+
+<p>When you build a media route provider for your receiver device, the provider serves the
+following purposes:</p>
+
+<ul>
+  <li>Describe and publish the capabilities of the receiver device so other apps can discover it
+    and use its playback features.</li>
+  <li>Wrap the programming interface of the receiver device and its communication
+    transport mechanisms to make the device compatible with the media router framework.</li>
+</ul>
+
+
+<h3 id="dist">Distribution of route providers</h3>
+
+<p>A media route provider is distributed as part of an Android app. Your route provider can be
+  made available to other apps by extending
+  {@link android.support.v7.media.MediaRouteProviderService} or wrapping your implementation of
+  {@link android.support.v7.media.MediaRouteProvider} with your own service and declaring an intent
+  filter for the media route provider. These steps allow other apps to discover and make use of
+  your media route.</p>
+
+<p>
+  <strong>Note:</strong> The app containing the media route provider can also include a
+  <a href="{@docRoot}guide/topics/media/mediarouter.html">MediaRouter</a> interface to the
+  route provider, but this is not required.
+</p>
+
+
+<h3 id="playback-types">Types of playback</h3>
+
+<p>There are two main types of playback supported by the media router framework. A media route
+  provider can support one or both types of playback, depending on the capabilities of your playback
+  equipment and the functionality you want to support:</p>
+
+<ul>
+  <li><strong>Remote Playback</strong> — This approach uses the receiver device to handle the
+    content data retrieval, decoding, and playback, while an Android device in the user's hand is
+    used as a remote control. This approach is used by Android apps that support
+    <a href="https://developers.google.com/cast/">Google Cast</a>.</li>
+  <li><strong>Secondary Output</strong> — With this approach, the Android media application
+    retrieves, renders and streams video or music directly to the receiver device. This approach is
+    used to support Wireless Display output on Android.</li>
+</ul>
+
+
+<h3 id="mr-packages">Media router packages</h3>
+
+<p>
+  The media router APIs are provided as part of the Android Support Library version 18 and higher,
+  in the <a href="{@docRoot}tools/support-library/features.html#v7-mediarouter">v7-mediarouter</a>
+  support library. You should use the classes in the
+  {@link android.support.v7.media} package for media route provider functions.
+  These APIs are compatible with devices running Android 2.1 (API level 7) and higher.
+</p>
+
+<p class="caution">
+  <strong>Caution:</strong> There is another set of media router APIs provided in the
+  {@link android.media} class package that have been superseded by the
+  <a href="{@docRoot}tools/support-library/features.html#v7-mediarouter">v7-mediarouter</a>
+  support library. You <em>should not</em> use the {@link android.media} classes for
+  implementing media route provider functions.
+</p>
+
+<p>In order to use the {@link android.support.v7.media} media router classes, you
+  must add the <a href="{@docRoot}tools/support-library/features.html#v7-mediarouter"
+  >v7-mediarouter support library package</a> to your app development project. For more
+  information on adding support libraries to your app development project, see
+  <a href="{@docRoot}tools/support-library/setup.html">Support Library Setup</a>.
+</p>
+
+
+<h2 id="provider-service">Creating a Provider Service</h2>
+
+<p>The media router framework must be able to discover and connect to your media route provider
+  to allow other applications to use your route. In order to do this, the media router framework
+  looks for apps that declare a media route provider intent action. When another app wants to
+  connect to your provider, the framework must be able to invoke and connect to it, so your provider
+  must be encapsulated in a {@link android.app.Service}.</p>
+
+<p>The following example code shows the declaration of a media route provider service and the
+  intent filter in a manifest, which allows it to be discovered and used by the media router
+  framework:</p>
+
+<pre>
+&lt;service android:name=".provider.SampleMediaRouteProviderService"
+    android:label="&#64;string/sample_media_route_provider_service"
+    android:process=":mrp"&gt;
+    &lt;intent-filter&gt;
+        &lt;action android:name="android.media.MediaRouteProviderService" /&gt;
+    &lt;/intent-filter&gt;
+&lt;/service&gt;
+</pre>
+
+<p>This manifest example declares a service that wraps the actual media route provider classes.
+  The Android media router framework provides the
+  {@link android.support.v7.media.MediaRouteProviderService} class for use as a service wrapper for
+  media route providers. The following example code demonstrates how to use this wrapper
+  class:</p>
+
+<pre>
+public class SampleMediaRouteProviderService extends MediaRouteProviderService {
+
+    &#64;Override
+    public MediaRouteProvider onCreateMediaRouteProvider() {
+        return new SampleMediaRouteProvider(this);
+    }
+}
+</pre>
+
+
+<h2 id="route-caps">Specifying Route Capabilities</h2>
+
+<p>Apps connecting to the media router framework can discover your media route through your
+  app's manifest declarations, but they also need to know the capabilities of the media routes you
+  are providing. Media routes can be of different types and have different features, and other apps
+  need to be able to discover these details to determine if they are compatible with your route.</p>
+
+<p>The media router framework allows you to define and publish the capabilities of your media
+  route through {@link android.content.IntentFilter} objects, {@link
+  android.support.v7.media.MediaRouteDescriptor} objects and a {@link
+  android.support.v7.media.MediaRouteProviderDescriptor}. This section explains how to use these
+  classes to publish the details of your media route for other apps.</p>
+
+
+<h3 id="route-cat">Route categories</h3>
+
+<p>As part of the programmatic description of your media route provider, you must specify
+  whether your provider supports remote playback, secondary output, or both. These are the route
+  categories provided by the media router framework:</p>
+
+<ul>
+  <li>{@link android.support.v7.media.MediaControlIntent#CATEGORY_LIVE_AUDIO CATEGORY_LIVE_AUDIO}
+    &mdash; Output of audio to a secondary output device, such as a wireless-enabled music system.
+    </li>
+  <li>{@link android.support.v7.media.MediaControlIntent#CATEGORY_LIVE_VIDEO CATEGORY_LIVE_VIDEO}
+    &mdash; Output of video to a secondary output device, such as Wireless Display devices.</li>
+  <li>{@link android.support.v7.media.MediaControlIntent#CATEGORY_REMOTE_PLAYBACK
+    CATEGORY_REMOTE_PLAYBACK} &mdash; Play video or audio on a separate device which handles media
+    retrieval, decoding, and playback, such as
+    <a href="https://www.google.com/url?q=http://www.google.com/chromecast">Chromecast</a> devices.
+    </li>
+</ul>
+
+<p>In order to include these settings in a description of your media route, you insert them into
+  an {@link android.content.IntentFilter} object, which you later add to a
+  {@link android.support.v7.media.MediaRouteDescriptor} object:</p>
+
+<pre>
+public final class SampleMediaRouteProvider extends MediaRouteProvider {
+    private static final ArrayList&lt;IntentFilter&gt; CONTROL_FILTERS_BASIC;
+    static {
+        IntentFilter videoPlayback = new IntentFilter();
+        <strong>videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);</strong>
+        CONTROL_FILTERS_BASIC = new ArrayList&lt;IntentFilter&gt;();
+        CONTROL_FILTERS_BASIC.add(videoPlayback);
+    }
+}
+
+</pre>
+
+<p>If you specify the {@link android.support.v7.media.MediaControlIntent#CATEGORY_REMOTE_PLAYBACK
+  CATEGORY_REMOTE_PLAYBACK} intent, you must also define what media types and
+  playback controls are supported by your media route provider. The next section describes how to
+  specify these settings for your device.</p>
+
+
+<h3 id="media-types">Media types and protocols</h3>
+
+<p>A media route provider for a remote playback device must specify the media types and transfer
+  protocols it supports. You specify these settings using the {@link android.content.IntentFilter}
+  class and the {@link android.content.IntentFilter#addDataScheme addDataScheme()} and
+  {@link android.content.IntentFilter#addDataType addDataType()} methods of that object. The
+  following code snippet demonstrates how to define an intent filter for supporting remote video
+  playback using http, https, and Real Time Streaming Protocol (RTSP):</p>
+
+<pre>
+public final class SampleMediaRouteProvider extends MediaRouteProvider {
+
+    private static final ArrayList&lt;IntentFilter&gt; CONTROL_FILTERS_BASIC;
+
+    static {
+        IntentFilter videoPlayback = new IntentFilter();
+        videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        videoPlayback.addAction(MediaControlIntent.ACTION_PLAY);
+        videoPlayback.addDataScheme("http");
+        videoPlayback.addDataScheme("https");
+        videoPlayback.addDataScheme("rtsp");
+        addDataTypeUnchecked(videoPlayback, "video/*");
+        CONTROL_FILTERS_BASIC = new ArrayList&lt;IntentFilter&gt;();
+        CONTROL_FILTERS_BASIC.add(videoPlayback);
+    }
+    ...
+
+    private static void addDataTypeUnchecked(IntentFilter filter, String type) {
+        try {
+            filter.addDataType(type);
+        } catch (MalformedMimeTypeException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}
+
+</pre>
+
+
+<h3 id="playback-ctrls">Playback controls</h3>
+
+<p>A media route provider that offers remote playback must specify the types of media controls
+  it supports. These are the general types of control that media routes can provide:</p>
+
+<ul>
+  <li><strong>Playback controls</strong>, such as play, pause, rewind, and fast-forward.</li>
+  <li><strong>Queuing features</strong>, which allow the sending app to add and remove items
+    from a playlist which is maintained by the receiver device.</li>
+  <li><strong>Session features</strong>, which prevent sending apps from interfering with each
+    other by having the receiver device provide a session id to the requesting app and then checking
+    that id with each subsequent playback control request.</li>
+</ul>
+
+<p>The following code example demonstrates how to construct an intent filter for supporting
+  basic media route playback controls:</p>
+
+<pre>
+public final class SampleMediaRouteProvider extends MediaRouteProvider {
+    private static final ArrayList&lt;IntentFilter&gt; CONTROL_FILTERS_BASIC;
+    static {
+        ...
+        IntentFilter playControls = new IntentFilter();
+        playControls.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        playControls.addAction(MediaControlIntent.ACTION_SEEK);
+        playControls.addAction(MediaControlIntent.ACTION_GET_STATUS);
+        playControls.addAction(MediaControlIntent.ACTION_PAUSE);
+        playControls.addAction(MediaControlIntent.ACTION_RESUME);
+        playControls.addAction(MediaControlIntent.ACTION_STOP);
+        CONTROL_FILTERS_BASIC = new ArrayList&lt;IntentFilter&gt;();
+        CONTROL_FILTERS_BASIC.add(videoPlayback);
+        CONTROL_FILTERS_BASIC.add(playControls);
+    }
+    ...
+}
+</pre>
+
+<p>For more information about the available playback control intents, see the
+  {@link android.support.v7.media.MediaControlIntent} class.</p>
+
+
+<h3 id="mrpd">MediaRouteProviderDescriptor</h3>
+
+<p>After defining the capabilities of your media route using {@link
+  android.content.IntentFilter} objects, you can then create a descriptor object for publishing to
+  the Android media router framework. This descriptor object contains the specifics of your media
+  route's capabilities so that other applications can determine how to interact with your media
+  route.</p>
+
+<p>The following example code demonstrates how to add the previously created intent filters to a
+  {@link android.support.v7.media.MediaRouteProviderDescriptor} and set the descriptor for use by
+  the media router framework:</p>
+
+<pre>
+public SampleMediaRouteProvider(Context context) {
+    super(context);
+    publishRoutes();
+}
+
+private void publishRoutes() {
+    Resources r = getContext().getResources();
+    // Create a route descriptor using previously created IntentFilters
+    MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
+            VARIABLE_VOLUME_BASIC_ROUTE_ID,
+            r.getString(R.string.variable_volume_basic_route_name))
+            .setDescription(r.getString(R.string.sample_route_description))
+            .addControlFilters(CONTROL_FILTERS_BASIC)
+            .setPlaybackStream(AudioManager.STREAM_MUSIC)
+            .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+            .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+            .setVolumeMax(VOLUME_MAX)
+            .setVolume(mVolume)
+            .build();
+    // Add the route descriptor to the provider descriptor
+    MediaRouteProviderDescriptor providerDescriptor =
+            new MediaRouteProviderDescriptor.Builder()
+            .addRoute(routeDescriptor)
+            .build();
+
+    // Publish the descriptor to the framework
+    setDescriptor(providerDescriptor);
+}
+</pre>
+
+<p>For more information on the available descriptor settings, see the reference documentation
+  for {@link android.support.v7.media.MediaRouteDescriptor} and {@link
+  android.support.v7.media.MediaRouteProviderDescriptor}.</p>
+
+
+<h2 id="ctrl-routes">Controlling Routes</h2>
+
+<p>When an application connects to your media route provider, the provider receives playback
+  commands through the media router framework sent to your route by other apps. To handle these
+  requests, you must provide an implementation of a {@link
+  android.support.v7.media.MediaRouteProvider.RouteController} class, which processes the commands
+  and handles the actual communication to your receiver device.</p>
+
+<p>The media router framework calls the {@link
+  android.support.v7.media.MediaRouteProvider#onCreateRouteController onCreateRouteController()}
+  method of your route provider to obtain an instance of this class and then routes requests to it.
+  These are the key methods of the {@link
+  android.support.v7.media.MediaRouteProvider.RouteController} class, which you must implement for
+  your media route provider:</p>
+
+<ul>
+  <li>{@link android.support.v7.media.MediaRouteProvider.RouteController#onSelect onSelect()}
+    &mdash; Called when an application selects your route for playback. You use this method to do
+    any preparation work that may be required before media playback begins.</li>
+  <li>{@link android.support.v7.media.MediaRouteProvider.RouteController#onControlRequest
+    onControlRequest()} &mdash; Sends specific playback commands to the receiving device.</li>
+  <li>{@link android.support.v7.media.MediaRouteProvider.RouteController#onSetVolume
+    onSetVolume()} &mdash; Sends a request to the receiving device to set the playback volume to a
+    specific value.</li>
+  <li>{@link android.support.v7.media.MediaRouteProvider.RouteController#onUpdateVolume
+    onUpdateVolume()} &mdash; Sends a request to the receiving device to modify the playback
+    volume by a specified amount.</li>
+  <li>{@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect
+    onUnselect()} &mdash; Called when an application unselects a route.</li>
+  <li>{@link android.support.v7.media.MediaRouteProvider.RouteController#onRelease onRelease()}
+    &mdash; Called when the route is no longer needed by the framework, allowing it to free its
+    resources.</li>
+</ul>
+
+<p>All playback control requests, except for volume changes, are directed to the {@link
+  android.support.v7.media.MediaRouteProvider.RouteController#onControlRequest onControlRequest()}
+  method. Your implementation of this method must parse the control requests and respond to them
+  appropriately. Here is an example implementation of this method which processes commands for a
+  remote playback media route:</p>
+
+<pre>
+private final class SampleRouteController extends
+        MediaRouteProvider.RouteController {
+    ...
+
+    &#64;Override
+    public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
+
+        String action = intent.getAction();
+
+        if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
+            boolean success = false;
+            if (action.equals(MediaControlIntent.ACTION_PLAY)) {
+                success = handlePlay(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
+                success = handleEnqueue(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_REMOVE)) {
+                success = handleRemove(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
+                success = handleSeek(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
+                success = handleGetStatus(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
+                success = handlePause(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
+                success = handleResume(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_STOP)) {
+                success = handleStop(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
+                success = handleStartSession(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
+                success = handleGetSessionStatus(intent, callback);
+            } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
+                success = handleEndSession(intent, callback);
+            }
+
+            Log.d(TAG, mSessionManager.toString());
+            return success;
+        }
+        return false;
+    }
+    ...
+}
+</pre>
+
+<p>It is important to understand that the {@link
+  android.support.v7.media.MediaRouteProvider.RouteController} class is intended to act as a wrapper
+  for the API to your media playback equipment. The implementation of the methods in this class is
+  entirely dependent on the programmatic interface provided by your receiving device.</p>
diff --git a/docs/html/guide/topics/media/mediarouter.jd b/docs/html/guide/topics/media/mediarouter.jd
index 1b10265..e0bf889 100644
--- a/docs/html/guide/topics/media/mediarouter.jd
+++ b/docs/html/guide/topics/media/mediarouter.jd
@@ -1,5 +1,5 @@
-page.title=MediaRouter
-page.tags="cast","chromecast","wireless display","miracast"
+page.title=Media Router
+page.tags="mediarouter","cast","chromecast","wireless display","miracast"
 @jd:body
 
 <div id="qv-wrapper">
@@ -36,6 +36,10 @@
       <li>{@link android.support.v7.media.MediaRouter.Callback}</li>
       <li>{@link android.support.v7.media.MediaRouteProvider}</li>
     </ol>
+    <h2>Related Samples</h2>
+    <ol>
+      <li><a href="{@docRoot}guide/topics/media/mediarouter.html">MediaRouter</a></li>
+    </ol>
   </div>
 </div>
 
@@ -105,15 +109,17 @@
   (API level 7) and higher.
 </p>
 
-<p class="note">
-  <strong>Note:</strong> There is another set of media router APIs provided in the
+<p class="caution">
+  <strong>Caution:</strong> There is another set of media router APIs provided in the
   {@link android.media} that have been superseded by the v7-mediarouter support library.
   You <em>should not</em> use the {@link android.media} classes for media router functions.
 </p>
 
 <p>In order to use the {@link android.support.v7.media} media router classes, you must add
   the <a href="{@docRoot}tools/support-library/features.html#v7-mediarouter">v7-mediarouter
-  support library package</a> to your app development project.
+  support library package</a> to your app development project.  For more
+  information on adding support libraries to your app development project, see
+  <a href="{@docRoot}tools/support-library/setup.html">Support Library Setup</a>.
 </p>
 
 
@@ -211,9 +217,9 @@
     CATEGORY_LIVE_VIDEO} &mdash; Output of video to a secondary output device, such as Wireless
     Display devices.</li>
   <li>{@link android.support.v7.media.MediaControlIntent#CATEGORY_REMOTE_PLAYBACK
-    CATEGORY_REMOTE_PLAYBACK} &mdash; Play video or audio on a separate device that supports the
-    <a href="https://developers.google.com/cast/">Google Cast</a> remote control protocol, such
-    as <a href="https://www.google.com/url?q=http://www.google.com/chromecast">Chromecast</a>.
+    CATEGORY_REMOTE_PLAYBACK} &mdash; Play video or audio on a separate device that handles media
+    retrieval, decoding, and playback, such as
+    <a href="https://www.google.com/url?q=http://www.google.com/chromecast">Chromecast</a> devices.
     </li>
 </ul>
 
@@ -279,7 +285,7 @@
 <p>In order to connect to a media route selected by the user, your app must obtain the {@link
   android.support.v7.media.MediaRouter} framework object and then attach a {@link
   android.support.v7.media.MediaRouter.Callback} object. The callback object receives messages
-  from the media router framework when a route selected, changed or disconnected by the user.</p>
+  from the media router framework when a route is selected, changed, or disconnected by the user.</p>
 
 <p>To obtain an instance of the {@link android.support.v7.media.MediaRouter} framework object,
   call {@link android.support.v7.media.MediaRouter#getInstance MediaRouter.getInstance()}
@@ -299,11 +305,11 @@
 <p>The media router framework communicates with an app through a callback object that
   you attach to the {@link android.support.v7.media.MediaRouter} framework object. An app
   that uses the media router framework must extend the {@link
-  android.support.v7.media.MediaRouter.Callback} object to receive messages when a media route is
-  connected and provide content to the connected device through that route.</p>
+  android.support.v7.media.MediaRouter.Callback} object in order to receive messages when a
+  media route is connected.</p>
 
-<p>There are several methods in the callback that can be overwritten to receive messages about
-  media router events. At the minimum, your implementation of the {@link
+<p>There are several methods in the callback that you can override to receive information about
+  media router events. At minimum, your implementation of the {@link
   android.support.v7.media.MediaRouter.Callback} class should override the following
   methods:</p>
 
@@ -440,12 +446,12 @@
 
 <p class="note">
   <strong>Note:</strong> The media route framework also provides a
-  {@link android.support.v7.app.MediaRouteDiscoveryFragment} class which takes care of adding and
-  removing the call back for an activity.
+  {@link android.support.v7.app.MediaRouteDiscoveryFragment} class, which takes care of adding and
+  removing the callback for an activity.
 </p>
 
 <p>Now when you run your application, you should see a Cast button appear in your activity.
-  When you press the button the media router framework, a route selection dialog appears as shown
+  When you touch the button, a route selection dialog appears as shown
   in figure 3, allowing your user to select an available media route. Make sure you have a
   supported device available on your local network when testing this interface.</p>
 
diff --git a/docs/html/images/mediarouter/media-route-provider-framework.png b/docs/html/images/mediarouter/media-route-provider-framework.png
new file mode 100644
index 0000000..60cc29a
--- /dev/null
+++ b/docs/html/images/mediarouter/media-route-provider-framework.png
Binary files differ
diff --git a/docs/html/sdk/installing/studio-build.jd b/docs/html/sdk/installing/studio-build.jd
index 41ad5de..2a616a0 100644
--- a/docs/html/sdk/installing/studio-build.jd
+++ b/docs/html/sdk/installing/studio-build.jd
@@ -1,4 +1,4 @@
-page.title=Building your Project
+page.title=Building Your Project with Gradle
 
 @jd:body
 
@@ -23,6 +23,7 @@
             <li><a href="#addLibModule">Add a library module</a></li>
             <li><a href="#buildProject">Build the project</a></li>
             <li><a href="#buildCmd">Build from the command line</a></li>
+            <li><a href="#buildRelease">Build a release version</a></li>
         </ol>
     </li>
     <li><a href="#configBuild">Configure the Build</a>
@@ -463,6 +464,24 @@
 </pre>
 
 
+<h3 id="buildRelease">Build a release version</h3>
+
+<p>You can build the release version of your application from the command line or using Android
+Studio. To build it from the command line, invoke the <code>assembleRelease</code> build task using
+the Gradle wrapper script (<code>gradlew assembleRelease</code>). To build it from Android
+Studio:</p>
+
+<ol>
+    <li>Click <strong>Gradle</strong> on the right side of the IDE window.</li>
+    <li>On the <em>All tasks</em> section of the sidebar that appears, expand
+        <strong>BuildSystemExample</strong>.</li>
+    <li>Expand <strong>:app</strong> and double-click <strong>assembleRelease</strong>.</li>
+</ol>
+
+<p>You can use this procedure to invoke any build task from Android Studio.</p>
+
+
+
 <h2 id="configBuild">Configure the Build</h2>
 
 <p>This section uses the <code>BuildSystemExample</code> project from the previous section and
@@ -643,20 +662,6 @@
 <code>proguard-rules.txt</code> at the root of the module, where you can add custom ProGuard
 rules.</p>
 
-<p>You can build the release version of your application from the command line or using Android
-Studio. To build it from the command line, invoke the <code>assembleRelease</code> build task using
-the Gradle wrapper script (<code>gradlew assembleRelease</code>). To build it from Android
-Studio:</p>
-
-<ol>
-    <li>Click <strong>Gradle</strong> on the right side of the IDE window.</li>
-    <li>On the <em>All tasks</em> section of the sidebar that appears, expand
-        <strong>BuildSystemExample</strong>.</li>
-    <li>Expand <strong>:app</strong> and double-click <strong>assembleRelease</strong>.</li>
-</ol>
-
-<p>You can use this procedure to invoke any build task from Android Studio.</p>
-
 <h3 id="configureSigning">Configure signing settings</h3>
 
 <p>The debug and the release versions of the app differ on whether the application can be
diff --git a/docs/html/tools/support-library/index.jd b/docs/html/tools/support-library/index.jd
index 389238f..e905285 100644
--- a/docs/html/tools/support-library/index.jd
+++ b/docs/html/tools/support-library/index.jd
@@ -58,10 +58,36 @@
 
 <p>This section provides details about the Support Library package releases.</p>
 
-
 <div class="toggle-content opened">
   <p><a href="#" onclick="return toggleContent(this)">
     <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img" alt=""
+/>Android Support Library, revision 19.1.0</a> <em>(March 2014)</em>
+  </p>
+  <div class="toggle-content-toggleme">
+    <dl>
+      <dt>Changes for v4 support library:</dt>
+      <dd>
+        <ul>
+          <li>Added the {@link android.support.v4.widget.SwipeRefreshLayout} class,
+              which enables users to refresh the contents of a view with a vertical
+              swipe gesture.</li>
+          <li>Fixed accessibility issues with navigation drawers.</li>
+        </ul>
+      </dd>
+
+      <dt>Changes for v7 appcompat library:</dt>
+      <dd>
+        <ul>
+          <li>Fixed background issues with the action bar.</li>
+        </ul>
+      </dd>
+    </dl>
+  </div>
+</div>
+
+<div class="toggle-content closed">
+  <p><a href="#" onclick="return toggleContent(this)">
+    <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img" alt=""
 />Android Support Library, revision 19.0.1</a> <em>(December 2013)</em>
   </p>
   <div class="toggle-content-toggleme">
diff --git a/docs/html/tools/tools_toc.cs b/docs/html/tools/tools_toc.cs
index 0e543e4..c281644 100644
--- a/docs/html/tools/tools_toc.cs
+++ b/docs/html/tools/tools_toc.cs
@@ -37,7 +37,7 @@
           <li><a href="<?cs var:toroot ?>sdk/installing/studio-layout.html">
               Using the Layout Editor</a></li>
           <li><a href="<?cs var:toroot ?>sdk/installing/studio-build.html">
-              Building your Project</a></li>
+              Building Your Project with Gradle</a></li>
           </ul>
       </li>
       <li><a href="<?cs var:toroot ?>sdk/exploring.html">
diff --git a/docs/html/training/basics/firstapp/creating-project.jd b/docs/html/training/basics/firstapp/creating-project.jd
index 50485db..c4cb362 100644
--- a/docs/html/training/basics/firstapp/creating-project.jd
+++ b/docs/html/training/basics/firstapp/creating-project.jd
@@ -10,9 +10,9 @@
 
 
 <!-- This is the training bar -->
-<div id="tb-wrapper"> 
-<div id="tb"> 
- 
+<div id="tb-wrapper">
+<div id="tb">
+
 <h2>This lesson teaches you to</h2>
 
 <ol>
@@ -27,10 +27,10 @@
 SDK</a></li>
   <li><a href="{@docRoot}tools/projects/index.html">Managing Projects</a></li>
 </ul>
- 
- 
-</div> 
-</div> 
+
+
+</div>
+</div>
 
 <p>An Android project contains all the files that comprise the source code for your Android
 app. The Android SDK tools make it easy to start a new Android project with a set of
@@ -42,7 +42,7 @@
 
 <p class="note"><strong>Note:</strong> You should already have the Android SDK installed, and if
 you're using Eclipse, you should also have the <a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT
-plugin</a> installed (version 21.0.0 or higher). If you don't have these, follow the guide to <a
+plugin</a> installed (version 22.6.2 or higher). If you don't have these, follow the guide to <a
 href="{@docRoot}sdk/installing/index.html">Installing the Android SDK</a> before you start this
 lesson.</p>
 
@@ -50,7 +50,7 @@
 <h2 id="Eclipse">Create a Project with Eclipse</h2>
 
 <ol>
-  <li>Click <strong>New</strong> <img src="{@docRoot}images/tools/eclipse-new.png" 
+  <li>Click <strong>New</strong> <img src="{@docRoot}images/tools/eclipse-new.png"
   style="vertical-align:baseline;margin:0" /> in the toolbar.</li>
   <li>In the window that appears, open the <strong>Android</strong> folder,
   select <strong>Android Application Project</strong>, and click <strong>Next</strong>.</li>
@@ -116,11 +116,11 @@
   <li>Now you can select an activity template from which to begin building your app.
     <p>For this project, select <strong>BlankActivity</strong> and click <strong>Next</strong>.</p>
   </li>
-  <li>Leave all the details for the activity in their default state and click 
+  <li>Leave all the details for the activity in their default state and click
     <strong>Finish</strong>.</li>
 </ol>
 
-<p>Your Android project is now a basic "Hello World" app that contains some default files. 
+<p>Your Android project is now a basic "Hello World" app that contains some default files.
 To run the app, continue to the <a href="running-app.html">next lesson</a>.</p>
 
 
@@ -155,7 +155,7 @@
 projects.</p></li>
 </ol>
 
-<p>Your Android project is now a basic "Hello World" app that contains some default files. 
+<p>Your Android project is now a basic "Hello World" app that contains some default files.
 To run the app, continue to the <a href="running-app.html">next lesson</a>.</p>
 
 <p class="note"><strong>Tip:</strong> Add the <code>platform-tools/</code> as well as the
diff --git a/docs/html/training/basics/firstapp/index.jd b/docs/html/training/basics/firstapp/index.jd
index 4c1a0dc..1b49096 100644
--- a/docs/html/training/basics/firstapp/index.jd
+++ b/docs/html/training/basics/firstapp/index.jd
@@ -8,21 +8,21 @@
 
 @jd:body
 
-<div id="tb-wrapper"> 
-<div id="tb"> 
- 
-<h2>Dependencies and prerequisites</h2> 
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>Dependencies and prerequisites</h2>
 
 <ul>
   <li><a href="http://developer.android.com/sdk/index.html">Android SDK</a></li>
-  <li><a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> 20.0.0 or higher
+  <li><a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> 22.6.2 or higher
     (if you're using Eclipse)</li>
 </ul>
- 
-</div> 
-</div> 
- 
-<p>Welcome to Android application development!</p> 
+
+</div>
+</div>
+
+<p>Welcome to Android application development!</p>
 
 <p>This class teaches you how to build your first Android app. You’ll learn how to create an Android
 project and run a debuggable version of the app. You'll also learn some fundamentals of Android app
@@ -36,6 +36,10 @@
   <li>Download the latest SDK tools and platforms using the SDK Manager.</li>
 </ol>
 
+<p class="note"><strong>Note:</strong> Make sure you install the most recent versions of the ADT
+plugin and the Android SDK before you start this class. The procedures described in this class may
+not apply to earlier versions.</p>
+
 <p>If you haven't already done these tasks, start by downloading the
   <a href="{@docRoot}sdk/index.html">Android SDK</a> and following the install steps.
   Once you've finished the setup, you're ready to begin this class.</p>
diff --git a/docs/html/training/basics/firstapp/starting-activity.jd b/docs/html/training/basics/firstapp/starting-activity.jd
index 9aa25a3..27d2c10 100644
--- a/docs/html/training/basics/firstapp/starting-activity.jd
+++ b/docs/html/training/basics/firstapp/starting-activity.jd
@@ -10,9 +10,9 @@
 
 
 <!-- This is the training bar -->
-<div id="tb-wrapper"> 
-<div id="tb"> 
- 
+<div id="tb-wrapper">
+<div id="tb">
+
 <h2>This lesson teaches you to</h2>
 
 <ol>
@@ -30,10 +30,10 @@
   <li><a href="{@docRoot}sdk/installing/index.html">Installing the
 SDK</a></li>
 </ul>
- 
- 
-</div> 
-</div> 
+
+
+</div>
+</div>
 
 
 
@@ -151,7 +151,7 @@
 </pre>
 
 <p class="note"><strong>Note:</strong>
-You now need an import statement for <code>android.widget.EditText</code>. 
+You now need an import statement for <code>android.widget.EditText</code>.
 You'll define the <code>EXTRA_MESSAGE</code> constant in a moment.</p>
 
 <p>An {@link android.content.Intent} can carry a collection of various data types as key-value
@@ -212,7 +212,7 @@
 <p>To create a new activity using Eclipse:</p>
 
 <ol>
-  <li>Click <strong>New</strong> <img src="{@docRoot}images/tools/eclipse-new.png" 
+  <li>Click <strong>New</strong> <img src="{@docRoot}images/tools/eclipse-new.png"
   style="vertical-align:baseline;margin:0" /> in the toolbar.</li>
   <li>In the window that appears, open the <strong>Android</strong> folder
   and select <strong>Android Activity</strong>. Click <strong>Next</strong>.</li>
@@ -247,15 +247,19 @@
   <li>There's also an implementation of {@link android.app.Activity#onOptionsItemSelected
   onOptionsItemSelected()} which handles the behavior for the action bar's <em>Up</em> behavior.
   Keep this one the way it is.</li>
-  <li>There's also a <code>PlaceholderFragment</code> class that extends 
+  <li>There's also a <code>PlaceholderFragment</code> class that extends
 {@link android.app.Fragment}. You will not need this class in the final version of this
 activity.</li>
 </ul>
 
-<p>Fragments decompose application functionality and UI into reusable modules. For more 
-information on fragments, see the <a href="{@docRoot}guide/components/fragments.html">Fragments 
+<p>Fragments decompose application functionality and UI into reusable modules. For more
+information on fragments, see the <a href="{@docRoot}guide/components/fragments.html">Fragments
 API Guide</a>. The final version of this activity does not use fragments.</p>
 
+<p class="note"><strong>Note:</strong> Your activity may look different if you did not use
+the latest version of the ADT plugin. Make sure you install the latest version of the
+<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT plugin</a> to complete this tutorial.</p>
+
 <p>The {@code DisplayMessageActivity} class should now look like this:</p>
 
 <pre>
diff --git a/docs/html/wear/images/laptop-bridge.png b/docs/html/wear/images/laptop-bridge.png
index b481224..083b82b 100644
--- a/docs/html/wear/images/laptop-bridge.png
+++ b/docs/html/wear/images/laptop-bridge.png
Binary files differ
diff --git a/docs/image_sources/mediarouter/media-route-provider-framework.graffle/._data.plist b/docs/image_sources/mediarouter/media-route-provider-framework.graffle/._data.plist
new file mode 100644
index 0000000..d82ea05
--- /dev/null
+++ b/docs/image_sources/mediarouter/media-route-provider-framework.graffle/._data.plist
Binary files differ
diff --git a/docs/image_sources/mediarouter/media-route-provider-framework.graffle/._image1.png b/docs/image_sources/mediarouter/media-route-provider-framework.graffle/._image1.png
new file mode 100644
index 0000000..3435e35
--- /dev/null
+++ b/docs/image_sources/mediarouter/media-route-provider-framework.graffle/._image1.png
Binary files differ
diff --git a/docs/image_sources/mediarouter/media-route-provider-framework.graffle/data.plist b/docs/image_sources/mediarouter/media-route-provider-framework.graffle/data.plist
new file mode 100644
index 0000000..07791df
--- /dev/null
+++ b/docs/image_sources/mediarouter/media-route-provider-framework.graffle/data.plist
Binary files differ
diff --git a/docs/image_sources/mediarouter/media-route-provider-framework.graffle/image1.png b/docs/image_sources/mediarouter/media-route-provider-framework.graffle/image1.png
new file mode 100644
index 0000000..d6e3e95
--- /dev/null
+++ b/docs/image_sources/mediarouter/media-route-provider-framework.graffle/image1.png
Binary files differ
diff --git a/docs/image_sources/mediarouter/media-router-framework.graffle/data.plist b/docs/image_sources/mediarouter/media-router-framework.graffle/data.plist
new file mode 100644
index 0000000..ffd8212
--- /dev/null
+++ b/docs/image_sources/mediarouter/media-router-framework.graffle/data.plist
Binary files differ
diff --git a/docs/image_sources/mediarouter/media-router-framework.graffle/image1.png b/docs/image_sources/mediarouter/media-router-framework.graffle/image1.png
new file mode 100644
index 0000000..d6e3e95
--- /dev/null
+++ b/docs/image_sources/mediarouter/media-router-framework.graffle/image1.png
Binary files differ
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 6d60dd2..3db12c5 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -67,6 +67,7 @@
      * setPremultiplied() aren't order dependent, despite being setters.
      */
     private boolean mIsPremultiplied;
+
     private byte[] mNinePatchChunk;   // may be null
     private int[] mLayoutBounds;   // may be null
     private int mWidth;
@@ -390,7 +391,7 @@
          * No color information is stored.
          * With this configuration, each pixel requires 1 byte of memory.
          */
-        ALPHA_8     (2),
+        ALPHA_8     (1),
 
         /**
          * Each pixel is stored on 2 bytes and only the RGB channels are
@@ -406,7 +407,7 @@
          * This configuration may be useful when using opaque bitmaps
          * that do not require high color fidelity.
          */
-        RGB_565     (4),
+        RGB_565     (3),
 
         /**
          * Each pixel is stored on 2 bytes. The three RGB color channels
@@ -428,7 +429,7 @@
          *             it is advised to use {@link #ARGB_8888} instead.
          */
         @Deprecated
-        ARGB_4444   (5),
+        ARGB_4444   (4),
 
         /**
          * Each pixel is stored on 4 bytes. Each channel (RGB and alpha
@@ -438,13 +439,13 @@
          * This configuration is very flexible and offers the best
          * quality. It should be used whenever possible.
          */
-        ARGB_8888   (6);
+        ARGB_8888   (5);
 
         final int nativeInt;
 
         @SuppressWarnings({"deprecation"})
         private static Config sConfigs[] = {
-            null, null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
+            null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
         };
         
         Config(int ni) {
@@ -554,7 +555,7 @@
         checkRecycled("Can't copy a recycled bitmap");
         Bitmap b = nativeCopy(mNativeBitmap, config.nativeInt, isMutable);
         if (b != null) {
-            b.mIsPremultiplied = mIsPremultiplied;
+            b.setAlphaAndPremultiplied(hasAlpha(), mIsPremultiplied);
             b.mDensity = mDensity;
         }
         return b;
@@ -727,12 +728,12 @@
                 paint.setAntiAlias(true);
             }
         }
-        
+
         // The new bitmap was created from a known bitmap source so assume that
         // they use the same density
         bitmap.mDensity = source.mDensity;
-        bitmap.mIsPremultiplied = source.mIsPremultiplied;
-        
+        bitmap.setAlphaAndPremultiplied(source.hasAlpha(), source.mIsPremultiplied);
+
         canvas.setBitmap(bitmap);
         canvas.drawBitmap(source, srcR, dstR, paint);
         canvas.setBitmap(null);
@@ -810,9 +811,9 @@
         if (display != null) {
             bm.mDensity = display.densityDpi;
         }
+        bm.setHasAlpha(hasAlpha);
         if (config == Config.ARGB_8888 && !hasAlpha) {
             nativeErase(bm.mNativeBitmap, 0xff000000);
-            nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha);
         }
         // No need to initialize the bitmap to zeroes with other configs;
         // it is backed by a VM byte array which is by definition preinitialized
@@ -1041,11 +1042,23 @@
      * <p>This method will not affect the behavior of a bitmap without an alpha
      * channel, or if {@link #hasAlpha()} returns false.</p>
      *
+     * <p>Calling createBitmap() or createScaledBitmap() with a source
+     * Bitmap whose colors are not pre-multiplied may result in a RuntimeException,
+     * since those functions require drawing the source, which is not supported for
+     * un-pre-multiplied Bitmaps.</p>
+     *
      * @see Bitmap#isPremultiplied()
      * @see BitmapFactory.Options#inPremultiplied
      */
     public final void setPremultiplied(boolean premultiplied) {
         mIsPremultiplied = premultiplied;
+        nativeSetAlphaAndPremultiplied(mNativeBitmap, hasAlpha(), premultiplied);
+    }
+
+    /** Helper function to set both alpha and premultiplied. **/
+    private final void setAlphaAndPremultiplied(boolean hasAlpha, boolean premultiplied) {
+        mIsPremultiplied = premultiplied;
+        nativeSetAlphaAndPremultiplied(mNativeBitmap, hasAlpha, premultiplied);
     }
 
     /** Returns the bitmap's width */
@@ -1167,6 +1180,11 @@
      * @see #reconfigure(int, int, Config)
      */
     public final int getAllocationByteCount() {
+        if (mBuffer == null) {
+            // native backed bitmaps don't support reconfiguration,
+            // so alloc size is always content size
+            return getByteCount();
+        }
         return mBuffer.length;
     }
 
@@ -1201,7 +1219,7 @@
      * non-opaque per-pixel alpha values.
      */
     public void setHasAlpha(boolean hasAlpha) {
-        nativeSetHasAlpha(mNativeBitmap, hasAlpha);
+        nativeSetAlphaAndPremultiplied(mNativeBitmap, hasAlpha, mIsPremultiplied);
     }
 
     /**
@@ -1606,7 +1624,8 @@
 
     private static native void nativePrepareToDraw(int nativeBitmap);
     private static native boolean nativeHasAlpha(int nativeBitmap);
-    private static native void nativeSetHasAlpha(int nBitmap, boolean hasAlpha);
+    private static native void nativeSetAlphaAndPremultiplied(int nBitmap, boolean hasAlpha,
+                                                              boolean isPremul);
     private static native boolean nativeHasMipMap(int nativeBitmap);
     private static native void nativeSetHasMipMap(int nBitmap, boolean hasMipMap);
     private static native boolean nativeSameAs(int nb0, int nb1);
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 429be49..4575364 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -153,8 +153,12 @@
          *
          * <p>This does not affect bitmaps without an alpha channel.</p>
          *
+         * <p>Setting this flag to false while setting {@link #inScaled} to true
+         * may result in incorrect colors.</p>
+         *
          * @see Bitmap#hasAlpha()
          * @see Bitmap#isPremultiplied()
+         * @see #inScaled
          */
         public boolean inPremultiplied;
 
@@ -249,6 +253,9 @@
          * <p>This flag is turned on by default and should be turned off if you need
          * a non-scaled version of the bitmap.  Nine-patch bitmaps ignore this
          * flag and are always scaled.
+         *
+         * <p>If {@link #inPremultiplied} is set to false, and the image has alpha,
+         * setting this flag to true may result in incorrect colors.
          */
         public boolean inScaled;
 
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index e350e8d..aac7876 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -23,6 +23,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.SystemClock;
+import android.util.LayoutDirection;
 import android.util.SparseArray;
 
 /**
@@ -59,6 +60,8 @@
     private long mExitAnimationEnd;
     private Drawable mLastDrawable;
 
+    private Insets mInsets = Insets.NONE;
+
     // overrides from Drawable
 
     @Override
@@ -78,18 +81,31 @@
                 | mDrawableContainerState.mChildrenChangingConfigurations;
     }
 
+    private boolean needsMirroring() {
+        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
+    }
+
     @Override
     public boolean getPadding(Rect padding) {
         final Rect r = mDrawableContainerState.getConstantPadding();
+        boolean result;
         if (r != null) {
             padding.set(r);
-            return true;
-        }
-        if (mCurrDrawable != null) {
-            return mCurrDrawable.getPadding(padding);
+            result = (r.left | r.top | r.bottom | r.right) != 0;
         } else {
-            return super.getPadding(padding);
+            if (mCurrDrawable != null) {
+                result = mCurrDrawable.getPadding(padding);
+            } else {
+                result = super.getPadding(padding);
+            }
         }
+        if (needsMirroring()) {
+            final int left = padding.left;
+            final int right = padding.right;
+            padding.left = right;
+            padding.right = left;
+        }
+        return result;
     }
 
     /**
@@ -97,7 +113,7 @@
      */
     @Override
     public Insets getOpticalInsets() {
-        return (mCurrDrawable == null) ? Insets.NONE : mCurrDrawable.getOpticalInsets();
+        return mInsets;
     }
 
     @Override
@@ -334,6 +350,7 @@
             mCurrDrawable = d;
             mCurIndex = idx;
             if (d != null) {
+                mInsets = d.getOpticalInsets();
                 d.mutate();
                 if (mDrawableContainerState.mEnterFadeDuration > 0) {
                     mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
@@ -348,9 +365,12 @@
                 d.setBounds(getBounds());
                 d.setLayoutDirection(getLayoutDirection());
                 d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
+            } else {
+                mInsets = Insets.NONE;
             }
         } else {
             mCurrDrawable = null;
+            mInsets = Insets.NONE;
             mCurIndex = -1;
         }
 
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index ab34c0f..9c57a2c 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -244,7 +244,7 @@
         } else {
             padding.set(mPadding);
         }
-        return true;
+        return (padding.left | padding.top | padding.right | padding.bottom) != 0;
     }
 
     /**
diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java
index 81e523d..29d14a2 100644
--- a/graphics/java/android/graphics/pdf/PdfDocument.java
+++ b/graphics/java/android/graphics/pdf/PdfDocument.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Rect;
 
 import dalvik.system.CloseGuard;
@@ -69,6 +70,12 @@
  */
 public class PdfDocument {
 
+    // TODO: We need a constructor that will take an OutputStream to
+    // support online data serialization as opposed to the current
+    // on demand one. The current approach is fine until Skia starts
+    // to support online PDF generation at which point we need to
+    // handle this.
+
     private final byte[] mChunk = new byte[4096];
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -111,7 +118,7 @@
         if (pageInfo == null) {
             throw new IllegalArgumentException("page cannot be null");
         }
-        Canvas canvas = new PdfCanvas(nativeCreatePage(pageInfo.mPageWidth,
+        Canvas canvas = new PdfCanvas(nativeStartPage(mNativeDocument, pageInfo.mPageWidth,
                 pageInfo.mPageHeight, pageInfo.mContentRect.left, pageInfo.mContentRect.top,
                 pageInfo.mContentRect.right, pageInfo.mContentRect.bottom));
         mCurrentPage = new Page(canvas, pageInfo);
@@ -142,7 +149,7 @@
         }
         mPages.add(page.getInfo());
         mCurrentPage = null;
-        nativeAppendPage(mNativeDocument, page.mCanvas.mNativeCanvas);
+        nativeFinishPage(mNativeDocument);
         page.finish();
     }
 
@@ -204,7 +211,7 @@
 
     private void dispose() {
         if (mNativeDocument != 0) {
-            nativeFinalize(mNativeDocument);
+            nativeClose(mNativeDocument);
             mCloseGuard.close();
             mNativeDocument = 0;
         }
@@ -230,14 +237,14 @@
 
     private native int nativeCreateDocument();
 
-    private native void nativeFinalize(int document);
+    private native void nativeClose(int document);
 
-    private native void nativeAppendPage(int document, int page);
+    private native void nativeFinishPage(int document);
 
     private native void nativeWriteTo(int document, OutputStream out, byte[] chunk);
 
-    private static native int nativeCreatePage(int pageWidth, int pageHeight, int contentLeft,
-            int contentTop, int contentRight, int contentBottom);
+    private static native int nativeStartPage(int documentPtr, int pageWidth, int pageHeight,
+            int contentLeft, int contentTop, int contentRight, int contentBottom);
 
     private final class PdfCanvas extends Canvas {
 
@@ -392,28 +399,28 @@
          * Gets the {@link Canvas} of the page.
          *
          * <p>
-         * <strong>Note: </strong> There are some draw operations that are
-         * not yet supported by the canvas returned by this method. More
-         * specifically:
+         * <strong>Note: </strong> There are some draw operations that are not yet
+         * supported by the canvas returned by this method. More specifically:
          * <ul>
-         * <li>{@link Canvas#clipPath(android.graphics.Path)
-         *     Canvas.clipPath(android.graphics.Path)}</li>
-         * <li>All flavors of {@link Canvas#drawText(String, float, float,
-         *     android.graphics.Paint) Canvas.drawText(String, float, float,
-         *     android.graphics.Paint)}</li>
-         * <li>All flavors of {@link Canvas#drawPosText(String, float[],
-         *     android.graphics.Paint) Canvas.drawPosText(String, float[],
-         *     android.graphics.Paint)}</li>
+         * <li>Inverse path clipping performed via {@link Canvas#clipPath(android.graphics.Path,
+         *     android.graphics.Region.Op) Canvas.clipPath(android.graphics.Path,
+         *     android.graphics.Region.Op)} for {@link
+         *     android.graphics.Region.Op#REVERSE_DIFFERENCE
+         *     Region.Op#REVERSE_DIFFERENCE} operations.</li>
          * <li>{@link Canvas#drawVertices(android.graphics.Canvas.VertexMode, int,
          *     float[], int, float[], int, int[], int, short[], int, int,
          *     android.graphics.Paint) Canvas.drawVertices(
          *     android.graphics.Canvas.VertexMode, int, float[], int, float[],
          *     int, int[], int, short[], int, int, android.graphics.Paint)}</li>
-         * <li>{@link android.graphics.PorterDuff.Mode#SRC_ATOP PorterDuff.Mode SRC},
+         * <li>Color filters set via {@link Paint#setColorFilter(
+         *     android.graphics.ColorFilter)}</li>
+         * <li>Mask filters set via {@link Paint#setMaskFilter(
+         *     android.graphics.MaskFilter)}</li>
+         * <li>Some XFER modes such as
+         *     {@link android.graphics.PorterDuff.Mode#SRC_ATOP PorterDuff.Mode SRC},
          *     {@link android.graphics.PorterDuff.Mode#DST_ATOP PorterDuff.DST_ATOP},
          *     {@link android.graphics.PorterDuff.Mode#XOR PorterDuff.XOR},
          *     {@link android.graphics.PorterDuff.Mode#ADD PorterDuff.ADD}</li>
-         * <li>Perspective transforms</li>
          * </ul>
          *
          * @return The canvas if the page is not finished, null otherwise.
diff --git a/graphics/java/android/renderscript/AllocationAdapter.java b/graphics/java/android/renderscript/AllocationAdapter.java
index a6645bb..84a9ad3 100644
--- a/graphics/java/android/renderscript/AllocationAdapter.java
+++ b/graphics/java/android/renderscript/AllocationAdapter.java
@@ -224,7 +224,6 @@
     }
 
     static public AllocationAdapter create2D(RenderScript rs, Allocation a) {
-        android.util.Log.e("rs", "create2d " + a);
         rs.validate();
         AllocationAdapter aa = new AllocationAdapter(0, rs, a);
         aa.mConstrainedLOD = true;
diff --git a/graphics/java/android/renderscript/BaseObj.java b/graphics/java/android/renderscript/BaseObj.java
index e17d79a..fc54532 100644
--- a/graphics/java/android/renderscript/BaseObj.java
+++ b/graphics/java/android/renderscript/BaseObj.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.util.Log;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
  * BaseObj is the base class for all RenderScript objects owned by a RS context.
@@ -109,17 +109,31 @@
         return mName;
     }
 
-    protected void finalize() throws Throwable {
-        if (!mDestroyed) {
-            if(mID != 0 && mRS.isAlive()) {
+    private void helpDestroy() {
+        boolean shouldDestroy = false;
+        synchronized(this) {
+            if (!mDestroyed) {
+                shouldDestroy = true;
+                mDestroyed = true;
+            }
+        }
+
+        if (shouldDestroy) {
+            // must include nObjDestroy in the critical section
+            ReentrantReadWriteLock.ReadLock rlock = mRS.mRWLock.readLock();
+            rlock.lock();
+            // AllocationAdapters are BaseObjs with an ID of 0 but should not be passed to nObjDestroy
+            if(mRS.isAlive() && mID != 0) {
                 mRS.nObjDestroy(mID);
             }
+            rlock.unlock();
             mRS = null;
             mID = 0;
-            mDestroyed = true;
-            //Log.v(RenderScript.LOG_TAG, getClass() +
-            // " auto finalizing object without having released the RS reference.");
         }
+    }
+
+    protected void finalize() throws Throwable {
+        helpDestroy();
         super.finalize();
     }
 
@@ -128,12 +142,11 @@
      * primary use is to force immediate cleanup of resources when it is
      * believed the GC will not respond quickly enough.
      */
-    synchronized public void destroy() {
+    public void destroy() {
         if(mDestroyed) {
             throw new RSInvalidStateException("Object already destroyed.");
         }
-        mDestroyed = true;
-        mRS.nObjDestroy(mID);
+        helpDestroy();
     }
 
     /**
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 7d4a5c4..35fe8c4 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -19,6 +19,7 @@
 import java.io.File;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -102,6 +103,20 @@
 
     static File mCacheDir;
 
+    // this should be a monotonically increasing ID
+    // used in conjunction with the API version of a device
+    static final long sMinorID = 1;
+
+    /**
+     * Returns an identifier that can be used to identify a particular
+     * minor version of RS.
+     *
+     * @hide
+     */
+    public static long getMinorID() {
+        return sMinorID;
+    }
+
      /**
      * Sets the directory to use as a persistent storage for the
      * renderscript object file cache.
@@ -151,6 +166,7 @@
     }
 
     ContextType mContextType;
+    ReentrantReadWriteLock mRWLock;
 
     // Methods below are wrapped to protect the non-threadsafe
     // lockless fifo.
@@ -178,7 +194,18 @@
     native void rsnContextDestroy(int con);
     synchronized void nContextDestroy() {
         validate();
-        rsnContextDestroy(mContext);
+
+        // take teardown lock
+        // teardown lock can only be taken when no objects are being destroyed
+        ReentrantReadWriteLock.WriteLock wlock = mRWLock.writeLock();
+        wlock.lock();
+
+        int curCon = mContext;
+        // context is considered dead as of this point
+        mContext = 0;
+
+        wlock.unlock();
+        rsnContextDestroy(curCon);
     }
     native void rsnContextSetSurface(int con, int w, int h, Surface sur);
     synchronized void nContextSetSurface(int w, int h, Surface sur) {
@@ -263,8 +290,9 @@
         validate();
         return rsnGetName(mContext, obj);
     }
+    // nObjDestroy is explicitly _not_ synchronous to prevent crashes in finalizers
     native void rsnObjDestroy(int con, int id);
-    synchronized void nObjDestroy(int id) {
+    void nObjDestroy(int id) {
         // There is a race condition here.  The calling code may be run
         // by the gc while teardown is occuring.  This protects againts
         // deleting dead objects.
@@ -1136,6 +1164,7 @@
         if (ctx != null) {
             mApplicationContext = ctx.getApplicationContext();
         }
+        mRWLock = new ReentrantReadWriteLock();
     }
 
     /**
@@ -1230,6 +1259,8 @@
      */
     public void destroy() {
         validate();
+        nContextFinish();
+
         nContextDeinitToClient(mContext);
         mMessageThread.mRun = false;
         try {
@@ -1238,7 +1269,6 @@
         }
 
         nContextDestroy();
-        mContext = 0;
 
         nDeviceDestroy(mDev);
         mDev = 0;
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
index 7814a01..58f5325 100644
--- a/libs/hwui/Program.cpp
+++ b/libs/hwui/Program.cpp
@@ -163,7 +163,7 @@
 
 void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix,
         const mat4& transformMatrix, bool offset) {
-    if (projectionMatrix != mProjection) {
+    if (projectionMatrix != mProjection || offset != mOffset) {
         if (CC_LIKELY(!offset)) {
             glUniformMatrix4fv(projection, 1, GL_FALSE, &projectionMatrix.data[0]);
         } else {
@@ -177,6 +177,7 @@
             glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]);
         }
         mProjection = projectionMatrix;
+        mOffset = offset;
     }
 
     mat4 t(transformMatrix);
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 4f94afc..f6ac8ec 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -431,6 +431,7 @@
     bool mHasSampler;
 
     mat4 mProjection;
+    bool mOffset;
 }; // class Program
 
 }; // namespace uirenderer
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index ed0a79a..8d0874f 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -285,10 +285,9 @@
 void TextureCache::uploadLoFiTexture(bool resize, SkBitmap* bitmap,
         uint32_t width, uint32_t height) {
     SkBitmap rgbaBitmap;
-    rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+    rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0, bitmap->alphaType());
     rgbaBitmap.allocPixels();
     rgbaBitmap.eraseColor(0);
-    rgbaBitmap.setIsOpaque(bitmap->isOpaque());
 
     SkCanvas canvas(rgbaBitmap);
     canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL);
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index 18983d8..12a9c23 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -23,6 +23,7 @@
 #include <utils/Trace.h>
 
 #include <SkGlyph.h>
+#include <SkGlyphCache.h>
 #include <SkUtils.h>
 
 #include "FontUtil.h"
@@ -271,9 +272,9 @@
     if (cachedGlyph) {
         // Is the glyph still in texture cache?
         if (!cachedGlyph->mIsValid) {
-            const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit,
-                    &mDescription.mLookupTransform);
-            updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching);
+            SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform);
+            const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), textUnit);
+            updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), cachedGlyph, precaching);
         }
     } else {
         cachedGlyph = cacheGlyph(paint, textUnit, precaching);
@@ -415,8 +416,8 @@
     }
 }
 
-void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph,
-        bool precaching) {
+void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, SkGlyphCache* skiaGlyphCache,
+        CachedGlyphInfo* glyph, bool precaching) {
     glyph->mAdvanceX = skiaGlyph.fAdvanceX;
     glyph->mAdvanceY = skiaGlyph.fAdvanceY;
     glyph->mBitmapLeft = skiaGlyph.fLeft;
@@ -429,7 +430,7 @@
 
     // Get the bitmap for the glyph
     if (!skiaGlyph.fImage) {
-        paint->findImage(skiaGlyph, &mDescription.mLookupTransform);
+        skiaGlyphCache->findImage(skiaGlyph);
     }
     mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching);
 
@@ -463,11 +464,12 @@
     CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
     mCachedGlyphs.add(glyph, newGlyph);
 
-    const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform);
+    SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform);
+    const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), glyph);
     newGlyph->mIsValid = false;
     newGlyph->mGlyphIndex = skiaGlyph.fID;
 
-    updateGlyphCache(paint, skiaGlyph, newGlyph, precaching);
+    updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), newGlyph, precaching);
 
     return newGlyph;
 }
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 9e7ec2d..f68b430 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -19,6 +19,7 @@
 
 #include <utils/KeyedVector.h>
 
+#include <SkGlyphCache.h>
 #include <SkScalerContext.h>
 #include <SkPaint.h>
 #include <SkPathMeasure.h>
@@ -117,8 +118,8 @@
     void invalidateTextureCache(CacheTexture* cacheTexture = NULL);
 
     CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching);
-    void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph,
-            bool precaching);
+    void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, SkGlyphCache* skiaGlyphCache,
+            CachedGlyphInfo* glyph, bool precaching);
 
     void measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h
index cdcb23c..c2fd5f5 100644
--- a/libs/hwui/font/FontUtil.h
+++ b/libs/hwui/font/FontUtil.h
@@ -40,7 +40,7 @@
 #if RENDER_TEXT_AS_GLYPHS
     typedef uint16_t glyph_t;
     #define TO_GLYPH(g) g
-    #define GET_METRICS(paint, glyph, matrix) paint->getGlyphMetrics(glyph, matrix)
+    #define GET_METRICS(cache, glyph) cache->getGlyphIDMetrics(glyph)
     #define GET_GLYPH(text) nextGlyph((const uint16_t**) &text)
     #define IS_END_OF_STRING(glyph) false
 
@@ -53,7 +53,7 @@
 #else
     typedef SkUnichar glyph_t;
     #define TO_GLYPH(g) ((SkUnichar) g)
-    #define GET_METRICS(paint, glyph, matrix) paint->getUnicharMetrics(glyph, matrix)
+    #define GET_METRICS(cache, glyph) cache->getUnicharMetrics(glyph)
     #define GET_GLYPH(text) SkUTF16_NextUnichar((const uint16_t**) &text)
     #define IS_END_OF_STRING(glyph) glyph < 0
 #endif
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 1f5fefd..92474df 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -49,6 +49,8 @@
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
 import android.net.Uri;
+import android.net.http.CertificateChainValidator;
+import android.net.http.SslError;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -81,10 +83,12 @@
 
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.ByteArrayInputStream;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.HashMap;
@@ -116,6 +120,8 @@
     protected static final boolean DEBUG_RC = false;
     /** Debug volumes */
     protected static final boolean DEBUG_VOL = false;
+    /** Debug cert verification */
+    private static final boolean DEBUG_CERTS = false;
 
     /** How long to delay before persisting a change in volume/ringer mode. */
     private static final int PERSIST_DELAY = 500;
@@ -1019,7 +1025,7 @@
                 (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
                 synchronized (mA2dpAvrcpLock) {
                     if (mA2dp != null && mAvrcpAbsVolSupported) {
-                        mA2dp.setAvrcpAbsoluteVolume(index);
+                        mA2dp.setAvrcpAbsoluteVolume(index / 10);
                     }
                 }
             }
@@ -2913,12 +2919,10 @@
             int index;
             if (isMuted()) {
                 index = 0;
-            } else if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC &&
-                       (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
+            } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
                        mAvrcpAbsVolSupported) {
                 index = (mIndexMax + 5)/10;
-            }
-            else {
+            } else {
                 index = (getIndex(device) + 5)/10;
             }
             AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
@@ -2943,6 +2947,9 @@
                 if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
                     if (isMuted()) {
                         index = 0;
+                    } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
+                            mAvrcpAbsVolSupported) {
+                        index = (mIndexMax + 5)/10;
                     } else {
                         index = ((Integer)entry.getValue() + 5)/10;
                     }
@@ -3215,7 +3222,14 @@
             for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                 if (streamType != streamState.mStreamType &&
                         mStreamVolumeAlias[streamType] == streamState.mStreamType) {
-                    mStreamStates[streamType].applyDeviceVolume(getDeviceForStream(streamType));
+                    // Make sure volume is also maxed out on A2DP device for aliased stream
+                    // that may have a different device selected
+                    int streamDevice = getDeviceForStream(streamType);
+                    if ((device != streamDevice) && mAvrcpAbsVolSupported &&
+                            ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
+                        mStreamStates[streamType].applyDeviceVolume(device);
+                    }
+                    mStreamStates[streamType].applyDeviceVolume(streamDevice);
                 }
             }
 
@@ -3843,9 +3857,12 @@
         // address is not used for now, but may be used when multiple a2dp devices are supported
         synchronized (mA2dpAvrcpLock) {
             mAvrcpAbsVolSupported = support;
-            VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
             sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
-                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, streamState, 0);
+                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
+                    mStreamStates[AudioSystem.STREAM_MUSIC], 0);
+            sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
+                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
+                    mStreamStates[AudioSystem.STREAM_RING], 0);
         }
     }
 
@@ -4570,6 +4587,43 @@
         }
     }
 
+    public int verifyX509CertChain(int numcerts, byte [] chain, String domain, String authType) {
+
+        if (DEBUG_CERTS) {
+            Log.v(TAG, "java side verify for "
+                    + numcerts + " certificates (" + chain.length + " bytes"
+                            + ")for "+ domain + "/" + authType);
+        }
+
+        byte[][] certChain = new byte[numcerts][];
+
+        ByteBuffer buf = ByteBuffer.wrap(chain);
+        for (int i = 0; i < numcerts; i++) {
+            int certlen = buf.getInt();
+            if (DEBUG_CERTS) {
+                Log.i(TAG, "cert " + i +": " + certlen);
+            }
+            certChain[i] = new byte[certlen];
+            buf.get(certChain[i]);
+        }
+
+        try {
+            SslError err = CertificateChainValidator.verifyServerCertificates(certChain,
+                    domain, authType);
+            if (DEBUG_CERTS) {
+                Log.i(TAG, "verified: " + err);
+            }
+            if (err == null) {
+                return -1;
+            } else {
+                return err.getPrimaryError();
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "failed to verify chain: " + e);
+        }
+        return SslError.SSL_INVALID;
+    }
+
 
     //==========================================================================================
     // Camera shutter sound policy.
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 20eb356..9db35fc 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -98,7 +98,7 @@
     private static SimpleDateFormat sFormatter;
 
     static {
-        System.loadLibrary("exif_jni");
+        System.loadLibrary("jhead_jni");
         sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
         sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
     }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 2f08325..b5c3631 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -35,6 +35,8 @@
  */
 interface IAudioService {
 
+    int verifyX509CertChain(int chainsize, in byte[] chain, String host, String authtype);
+
     void adjustVolume(int direction, int flags, String callingPackage);
 
     boolean isLocalOrRemoteMusicActive();
@@ -236,4 +238,5 @@
     AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
 
     boolean isCameraSoundForced();
+
 }
diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl
new file mode 100644
index 0000000..9640dcb
--- /dev/null
+++ b/media/java/android/media/IMediaRouterClient.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * {@hide}
+ */
+oneway interface IMediaRouterClient {
+    void onStateChanged();
+}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
new file mode 100644
index 0000000..f8f5fdf
--- /dev/null
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.IMediaRouterClient;
+import android.media.MediaRouterClientState;
+
+/**
+ * {@hide}
+ */
+interface IMediaRouterService {
+    void registerClientAsUser(IMediaRouterClient client, String packageName, int userId);
+    void unregisterClient(IMediaRouterClient client);
+
+    MediaRouterClientState getState(IMediaRouterClient client);
+
+    void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
+    void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
+    void requestSetVolume(IMediaRouterClient client, String routeId, int volume);
+    void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction);
+}
diff --git a/media/java/android/media/IRemoteDisplayCallback.aidl b/media/java/android/media/IRemoteDisplayCallback.aidl
new file mode 100644
index 0000000..19cf070
--- /dev/null
+++ b/media/java/android/media/IRemoteDisplayCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.RemoteDisplayState;
+
+/**
+ * {@hide}
+ */
+oneway interface IRemoteDisplayCallback {
+    void onStateChanged(in RemoteDisplayState state);
+}
diff --git a/media/java/android/media/IRemoteDisplayProvider.aidl b/media/java/android/media/IRemoteDisplayProvider.aidl
new file mode 100644
index 0000000..b0d7379
--- /dev/null
+++ b/media/java/android/media/IRemoteDisplayProvider.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.IRemoteDisplayCallback;
+
+/**
+ * {@hide}
+ */
+oneway interface IRemoteDisplayProvider {
+    void setCallback(in IRemoteDisplayCallback callback);
+    void setDiscoveryMode(int mode);
+    void connect(String id);
+    void disconnect(String id);
+    void setVolume(String id, int volume);
+    void adjustVolume(String id, int delta);
+}
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index 59d411d..34008bb 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -1930,7 +1930,6 @@
         int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
         synchronized(mAudioFocusLock) {
             synchronized(mRCStack) {
-                boolean wasCurrentRcController = isCurrentRcController(mediaIntent);
                 // store the new display information
                 try {
                     for (int index = mRCStack.size()-1; index >= 0; index--) {
@@ -1977,9 +1976,9 @@
                     Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
                 }
 
-                // if the eventReceiver is now at the top of the stack but wasn't before
+                // if the eventReceiver is at the top of the stack
                 // then check for potential refresh of the remote controls
-                if (isCurrentRcController(mediaIntent) && !wasCurrentRcController) {
+                if (isCurrentRcController(mediaIntent)) {
                     checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
                 }
             }//synchronized(mRCStack)
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 706258a..2c16b5e 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -3128,7 +3128,7 @@
                 if (refreshTime ||
                         nanoTime >= mLastNanoTime + MAX_NS_WITHOUT_POSITION_CHECK) {
                     try {
-                        mLastTimeUs = mPlayer.getCurrentPosition() * 1000;
+                        mLastTimeUs = mPlayer.getCurrentPosition() * 1000L;
                         mPaused = !mPlayer.isPlaying();
                         if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs);
                     } catch (IllegalStateException e) {
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index d73d814..de20227 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -16,11 +16,15 @@
 
 package android.media;
 
+import com.android.internal.util.Objects;
+
+import android.Manifest;
 import android.app.ActivityThread;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
@@ -28,8 +32,10 @@
 import android.hardware.display.WifiDisplayStatus;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.Display;
@@ -52,14 +58,14 @@
  */
 public class MediaRouter {
     private static final String TAG = "MediaRouter";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     static class Static implements DisplayManager.DisplayListener {
-        // Time between wifi display scans when actively scanning in milliseconds.
-        private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
-
+        final Context mAppContext;
         final Resources mResources;
         final IAudioService mAudioService;
         final DisplayManager mDisplayService;
+        final IMediaRouterService mMediaRouterService;
         final Handler mHandler;
         final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
                 new CopyOnWriteArrayList<CallbackInfo>();
@@ -76,8 +82,16 @@
 
         RouteInfo mSelectedRoute;
 
-        WifiDisplayStatus mLastKnownWifiDisplayStatus;
+        final boolean mCanConfigureWifiDisplays;
         boolean mActivelyScanningWifiDisplays;
+        String mPreviousActiveWifiDisplayAddress;
+
+        int mDiscoveryRequestRouteTypes;
+        boolean mDiscoverRequestActiveScan;
+
+        int mCurrentUserId = -1;
+        IMediaRouterClient mClient;
+        MediaRouterClientState mClientState;
 
         final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
             @Override
@@ -90,17 +104,8 @@
             }
         };
 
-        final Runnable mScanWifiDisplays = new Runnable() {
-            @Override
-            public void run() {
-                if (mActivelyScanningWifiDisplays) {
-                    mDisplayService.scanWifiDisplays();
-                    mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL);
-                }
-            }
-        };
-
         Static(Context appContext) {
+            mAppContext = appContext;
             mResources = Resources.getSystem();
             mHandler = new Handler(appContext.getMainLooper());
 
@@ -109,10 +114,20 @@
 
             mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
 
+            mMediaRouterService = IMediaRouterService.Stub.asInterface(
+                    ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
+
             mSystemCategory = new RouteCategory(
                     com.android.internal.R.string.default_audio_route_category_name,
                     ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
             mSystemCategory.mIsSystem = true;
+
+            // Only the system can configure wifi displays.  The display manager
+            // enforces this with a permission check.  Set a flag here so that we
+            // know whether this process is actually allowed to scan and connect.
+            mCanConfigureWifiDisplays = appContext.checkPermission(
+                    Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                    Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;
         }
 
         // Called after sStatic is initialized
@@ -120,8 +135,7 @@
             mDefaultAudioVideo = new RouteInfo(mSystemCategory);
             mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
             mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
-            mDefaultAudioVideo.mPresentationDisplay = choosePresentationDisplayForRoute(
-                    mDefaultAudioVideo, getAllPresentationDisplays());
+            mDefaultAudioVideo.updatePresentationDisplay();
             addRouteStatic(mDefaultAudioVideo);
 
             // This will select the active wifi display route if there is one.
@@ -146,10 +160,13 @@
                 updateAudioRoutes(newAudioRoutes);
             }
 
+            // Bind to the media router service.
+            rebindAsUser(UserHandle.myUserId());
+
             // Select the default route if the above didn't sync us up
             // appropriately with relevant system state.
             if (mSelectedRoute == null) {
-                selectRouteStatic(mDefaultAudioVideo.getSupportedTypes(), mDefaultAudioVideo);
+                selectDefaultRouteStatic();
             }
         }
 
@@ -197,7 +214,7 @@
                         dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
                     }
                 } else if (sStatic.mBluetoothA2dpRoute != null) {
-                    removeRoute(sStatic.mBluetoothA2dpRoute);
+                    removeRouteStatic(sStatic.mBluetoothA2dpRoute);
                     sStatic.mBluetoothA2dpRoute = null;
                 }
             }
@@ -205,38 +222,79 @@
             if (mBluetoothA2dpRoute != null) {
                 if (mainType != AudioRoutesInfo.MAIN_SPEAKER &&
                         mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
-                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
+                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
                 } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
                         a2dpEnabled) {
-                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
+                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
                 }
             }
         }
 
-        void updateActiveScan() {
-            if (hasActiveScanCallbackOfType(ROUTE_TYPE_LIVE_VIDEO)) {
-                if (!mActivelyScanningWifiDisplays) {
-                    mActivelyScanningWifiDisplays = true;
-                    mHandler.post(mScanWifiDisplays);
-                }
-            } else {
-                if (mActivelyScanningWifiDisplays) {
-                    mActivelyScanningWifiDisplays = false;
-                    mHandler.removeCallbacks(mScanWifiDisplays);
-                }
-            }
-        }
-
-        private boolean hasActiveScanCallbackOfType(int type) {
+        void updateDiscoveryRequest() {
+            // What are we looking for today?
+            int routeTypes = 0;
+            int passiveRouteTypes = 0;
+            boolean activeScan = false;
+            boolean activeScanWifiDisplay = false;
             final int count = mCallbacks.size();
             for (int i = 0; i < count; i++) {
                 CallbackInfo cbi = mCallbacks.get(i);
-                if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0
-                        && (cbi.type & type) != 0) {
-                    return true;
+                if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
+                        | CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0) {
+                    // Discovery explicitly requested.
+                    routeTypes |= cbi.type;
+                } else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0) {
+                    // Discovery only passively requested.
+                    passiveRouteTypes |= cbi.type;
+                } else {
+                    // Legacy case since applications don't specify the discovery flag.
+                    // Unfortunately we just have to assume they always need discovery
+                    // whenever they have a callback registered.
+                    routeTypes |= cbi.type;
+                }
+                if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
+                    activeScan = true;
+                    if ((cbi.type & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
+                        activeScanWifiDisplay = true;
+                    }
                 }
             }
-            return false;
+            if (routeTypes != 0 || activeScan) {
+                // If someone else requests discovery then enable the passive listeners.
+                // This is used by the MediaRouteButton and MediaRouteActionProvider since
+                // they don't receive lifecycle callbacks from the Activity.
+                routeTypes |= passiveRouteTypes;
+            }
+
+            // Update wifi display scanning.
+            // TODO: All of this should be managed by the media router service.
+            if (mCanConfigureWifiDisplays) {
+                if (mSelectedRoute != null
+                        && mSelectedRoute.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) {
+                    // Don't scan while already connected to a remote display since
+                    // it may interfere with the ongoing transmission.
+                    activeScanWifiDisplay = false;
+                }
+                if (activeScanWifiDisplay) {
+                    if (!mActivelyScanningWifiDisplays) {
+                        mActivelyScanningWifiDisplays = true;
+                        mDisplayService.startWifiDisplayScan();
+                    }
+                } else {
+                    if (mActivelyScanningWifiDisplays) {
+                        mActivelyScanningWifiDisplays = false;
+                        mDisplayService.stopWifiDisplayScan();
+                    }
+                }
+            }
+
+            // Tell the media router service all about it.
+            if (routeTypes != mDiscoveryRequestRouteTypes
+                    || activeScan != mDiscoverRequestActiveScan) {
+                mDiscoveryRequestRouteTypes = routeTypes;
+                mDiscoverRequestActiveScan = activeScan;
+                publishClientDiscoveryRequest();
+            }
         }
 
         @Override
@@ -259,18 +317,268 @@
         }
 
         private void updatePresentationDisplays(int changedDisplayId) {
-            final Display[] displays = getAllPresentationDisplays();
             final int count = mRoutes.size();
             for (int i = 0; i < count; i++) {
-                final RouteInfo info = mRoutes.get(i);
-                Display display = choosePresentationDisplayForRoute(info, displays);
-                if (display != info.mPresentationDisplay
-                        || (display != null && display.getDisplayId() == changedDisplayId)) {
-                    info.mPresentationDisplay = display;
-                    dispatchRoutePresentationDisplayChanged(info);
+                final RouteInfo route = mRoutes.get(i);
+                if (route.updatePresentationDisplay() || (route.mPresentationDisplay != null
+                        && route.mPresentationDisplay.getDisplayId() == changedDisplayId)) {
+                    dispatchRoutePresentationDisplayChanged(route);
                 }
             }
         }
+
+        void setSelectedRoute(RouteInfo info, boolean explicit) {
+            // Must be non-reentrant.
+            mSelectedRoute = info;
+            publishClientSelectedRoute(explicit);
+        }
+
+        void rebindAsUser(int userId) {
+            if (mCurrentUserId != userId || userId < 0 || mClient == null) {
+                if (mClient != null) {
+                    try {
+                        mMediaRouterService.unregisterClient(mClient);
+                    } catch (RemoteException ex) {
+                        Log.e(TAG, "Unable to unregister media router client.", ex);
+                    }
+                    mClient = null;
+                }
+
+                mCurrentUserId = userId;
+
+                try {
+                    Client client = new Client();
+                    mMediaRouterService.registerClientAsUser(client,
+                            mAppContext.getPackageName(), userId);
+                    mClient = client;
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to register media router client.", ex);
+                }
+
+                publishClientDiscoveryRequest();
+                publishClientSelectedRoute(false);
+                updateClientState();
+            }
+        }
+
+        void publishClientDiscoveryRequest() {
+            if (mClient != null) {
+                try {
+                    mMediaRouterService.setDiscoveryRequest(mClient,
+                            mDiscoveryRequestRouteTypes, mDiscoverRequestActiveScan);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to publish media router client discovery request.", ex);
+                }
+            }
+        }
+
+        void publishClientSelectedRoute(boolean explicit) {
+            if (mClient != null) {
+                try {
+                    mMediaRouterService.setSelectedRoute(mClient,
+                            mSelectedRoute != null ? mSelectedRoute.mGlobalRouteId : null,
+                            explicit);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to publish media router client selected route.", ex);
+                }
+            }
+        }
+
+        void updateClientState() {
+            // Update the client state.
+            mClientState = null;
+            if (mClient != null) {
+                try {
+                    mClientState = mMediaRouterService.getState(mClient);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to retrieve media router client state.", ex);
+                }
+            }
+            final ArrayList<MediaRouterClientState.RouteInfo> globalRoutes =
+                    mClientState != null ? mClientState.routes : null;
+            final String globallySelectedRouteId = mClientState != null ?
+                    mClientState.globallySelectedRouteId : null;
+
+            // Add or update routes.
+            final int globalRouteCount = globalRoutes != null ? globalRoutes.size() : 0;
+            for (int i = 0; i < globalRouteCount; i++) {
+                final MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(i);
+                RouteInfo route = findGlobalRoute(globalRoute.id);
+                if (route == null) {
+                    route = makeGlobalRoute(globalRoute);
+                    addRouteStatic(route);
+                } else {
+                    updateGlobalRoute(route, globalRoute);
+                }
+            }
+
+            // Synchronize state with the globally selected route.
+            if (globallySelectedRouteId != null) {
+                final RouteInfo route = findGlobalRoute(globallySelectedRouteId);
+                if (route == null) {
+                    Log.w(TAG, "Could not find new globally selected route: "
+                            + globallySelectedRouteId);
+                } else if (route != mSelectedRoute) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Selecting new globally selected route: " + route);
+                    }
+                    selectRouteStatic(route.mSupportedTypes, route, false);
+                }
+            } else if (mSelectedRoute != null && mSelectedRoute.mGlobalRouteId != null) {
+                if (DEBUG) {
+                    Log.d(TAG, "Unselecting previous globally selected route: " + mSelectedRoute);
+                }
+                selectDefaultRouteStatic();
+            }
+
+            // Remove defunct routes.
+            outer: for (int i = mRoutes.size(); i-- > 0; ) {
+                final RouteInfo route = mRoutes.get(i);
+                final String globalRouteId = route.mGlobalRouteId;
+                if (globalRouteId != null) {
+                    for (int j = 0; j < globalRouteCount; j++) {
+                        MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(j);
+                        if (globalRouteId.equals(globalRoute.id)) {
+                            continue outer; // found
+                        }
+                    }
+                    // not found
+                    removeRouteStatic(route);
+                }
+            }
+        }
+
+        void requestSetVolume(RouteInfo route, int volume) {
+            if (route.mGlobalRouteId != null && mClient != null) {
+                try {
+                    mMediaRouterService.requestSetVolume(mClient,
+                            route.mGlobalRouteId, volume);
+                } catch (RemoteException ex) {
+                    Log.w(TAG, "Unable to request volume change.", ex);
+                }
+            }
+        }
+
+        void requestUpdateVolume(RouteInfo route, int direction) {
+            if (route.mGlobalRouteId != null && mClient != null) {
+                try {
+                    mMediaRouterService.requestUpdateVolume(mClient,
+                            route.mGlobalRouteId, direction);
+                } catch (RemoteException ex) {
+                    Log.w(TAG, "Unable to request volume change.", ex);
+                }
+            }
+        }
+
+        RouteInfo makeGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) {
+            RouteInfo route = new RouteInfo(sStatic.mSystemCategory);
+            route.mGlobalRouteId = globalRoute.id;
+            route.mName = globalRoute.name;
+            route.mDescription = globalRoute.description;
+            route.mSupportedTypes = globalRoute.supportedTypes;
+            route.mEnabled = globalRoute.enabled;
+            route.setRealStatusCode(globalRoute.statusCode);
+            route.mPlaybackType = globalRoute.playbackType;
+            route.mPlaybackStream = globalRoute.playbackStream;
+            route.mVolume = globalRoute.volume;
+            route.mVolumeMax = globalRoute.volumeMax;
+            route.mVolumeHandling = globalRoute.volumeHandling;
+            route.mPresentationDisplayId = globalRoute.presentationDisplayId;
+            route.updatePresentationDisplay();
+            return route;
+        }
+
+        void updateGlobalRoute(RouteInfo route, MediaRouterClientState.RouteInfo globalRoute) {
+            boolean changed = false;
+            boolean volumeChanged = false;
+            boolean presentationDisplayChanged = false;
+
+            if (!Objects.equal(route.mName, globalRoute.name)) {
+                route.mName = globalRoute.name;
+                changed = true;
+            }
+            if (!Objects.equal(route.mDescription, globalRoute.description)) {
+                route.mDescription = globalRoute.description;
+                changed = true;
+            }
+            final int oldSupportedTypes = route.mSupportedTypes;
+            if (oldSupportedTypes != globalRoute.supportedTypes) {
+                route.mSupportedTypes = globalRoute.supportedTypes;
+                changed = true;
+            }
+            if (route.mEnabled != globalRoute.enabled) {
+                route.mEnabled = globalRoute.enabled;
+                changed = true;
+            }
+            if (route.mRealStatusCode != globalRoute.statusCode) {
+                route.setRealStatusCode(globalRoute.statusCode);
+                changed = true;
+            }
+            if (route.mPlaybackType != globalRoute.playbackType) {
+                route.mPlaybackType = globalRoute.playbackType;
+                changed = true;
+            }
+            if (route.mPlaybackStream != globalRoute.playbackStream) {
+                route.mPlaybackStream = globalRoute.playbackStream;
+                changed = true;
+            }
+            if (route.mVolume != globalRoute.volume) {
+                route.mVolume = globalRoute.volume;
+                changed = true;
+                volumeChanged = true;
+            }
+            if (route.mVolumeMax != globalRoute.volumeMax) {
+                route.mVolumeMax = globalRoute.volumeMax;
+                changed = true;
+                volumeChanged = true;
+            }
+            if (route.mVolumeHandling != globalRoute.volumeHandling) {
+                route.mVolumeHandling = globalRoute.volumeHandling;
+                changed = true;
+                volumeChanged = true;
+            }
+            if (route.mPresentationDisplayId != globalRoute.presentationDisplayId) {
+                route.mPresentationDisplayId = globalRoute.presentationDisplayId;
+                route.updatePresentationDisplay();
+                changed = true;
+                presentationDisplayChanged = true;
+            }
+
+            if (changed) {
+                dispatchRouteChanged(route, oldSupportedTypes);
+            }
+            if (volumeChanged) {
+                dispatchRouteVolumeChanged(route);
+            }
+            if (presentationDisplayChanged) {
+                dispatchRoutePresentationDisplayChanged(route);
+            }
+        }
+
+        RouteInfo findGlobalRoute(String globalRouteId) {
+            final int count = mRoutes.size();
+            for (int i = 0; i < count; i++) {
+                final RouteInfo route = mRoutes.get(i);
+                if (globalRouteId.equals(route.mGlobalRouteId)) {
+                    return route;
+                }
+            }
+            return null;
+        }
+
+        final class Client extends IMediaRouterClient.Stub {
+            @Override
+            public void onStateChanged() {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (Client.this == mClient) {
+                            updateClientState();
+                        }
+                    }
+                });
+            }
+        }
     }
 
     static Static sStatic;
@@ -285,7 +593,7 @@
      * <p>Once initiated this routing is transparent to the application. All audio
      * played on the media stream will be routed to the selected destination.</p>
      */
-    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
+    public static final int ROUTE_TYPE_LIVE_AUDIO = 1 << 0;
 
     /**
      * Route type flag for live video.
@@ -302,7 +610,13 @@
      * @see RouteInfo#getPresentationDisplay()
      * @see android.app.Presentation
      */
-    public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
+    public static final int ROUTE_TYPE_LIVE_VIDEO = 1 << 1;
+
+    /**
+     * Temporary interop constant to identify remote displays.
+     * @hide To be removed when media router API is updated.
+     */
+    public static final int ROUTE_TYPE_REMOTE_DISPLAY = 1 << 2;
 
     /**
      * Route type flag for application-specific usage.
@@ -312,7 +626,10 @@
      * is expected to interpret the meaning of these events and perform the requested
      * routing tasks.</p>
      */
-    public static final int ROUTE_TYPE_USER = 0x00800000;
+    public static final int ROUTE_TYPE_USER = 1 << 23;
+
+    static final int ROUTE_TYPE_ANY = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO
+            | ROUTE_TYPE_REMOTE_DISPLAY | ROUTE_TYPE_USER;
 
     /**
      * Flag for {@link #addCallback}: Actively scan for routes while this callback
@@ -336,11 +653,40 @@
      * Flag for {@link #addCallback}: Do not filter route events.
      * <p>
      * When this flag is specified, the callback will be invoked for event that affect any
-     * route event if they do not match the callback's associated media route selector.
+     * route even if they do not match the callback's filter.
      * </p>
      */
     public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
 
+    /**
+     * Explicitly requests discovery.
+     *
+     * @hide Future API ported from support library.  Revisit this later.
+     */
+    public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
+
+    /**
+     * Requests that discovery be performed but only if there is some other active
+     * callback already registered.
+     *
+     * @hide Compatibility workaround for the fact that applications do not currently
+     * request discovery explicitly (except when using the support library API).
+     */
+    public static final int CALLBACK_FLAG_PASSIVE_DISCOVERY = 1 << 3;
+
+    /**
+     * Flag for {@link #isRouteAvailable}: Ignore the default route.
+     * <p>
+     * This flag is used to determine whether a matching non-default route is available.
+     * This constraint may be used to decide whether to offer the route chooser dialog
+     * to the user.  There is no point offering the chooser if there are no
+     * non-default choices.
+     * </p>
+     *
+     * @hide Future API ported from support library.  Revisit this later.
+     */
+    public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
+
     // Maps application contexts
     static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
 
@@ -352,6 +698,9 @@
         if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) {
             result.append("ROUTE_TYPE_LIVE_VIDEO ");
         }
+        if ((types & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
+            result.append("ROUTE_TYPE_REMOTE_DISPLAY ");
+        }
         if ((types & ROUTE_TYPE_USER) != 0) {
             result.append("ROUTE_TYPE_USER ");
         }
@@ -388,6 +737,11 @@
         return sStatic.mSystemCategory;
     }
 
+    /** @hide */
+    public RouteInfo getSelectedRoute() {
+        return getSelectedRoute(ROUTE_TYPE_ANY);
+    }
+
     /**
      * Return the currently selected route for any of the given types
      *
@@ -411,6 +765,38 @@
     }
 
     /**
+     * Returns true if there is a route that matches the specified types.
+     * <p>
+     * This method returns true if there are any available routes that match the types
+     * regardless of whether they are enabled or disabled.  If the
+     * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
+     * the method will only consider non-default routes.
+     * </p>
+     *
+     * @param types The types to match.
+     * @param flags Flags to control the determination of whether a route may be available.
+     * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}.
+     * @return True if a matching route may be available.
+     *
+     * @hide Future API ported from support library.  Revisit this later.
+     */
+    public boolean isRouteAvailable(int types, int flags) {
+        final int count = sStatic.mRoutes.size();
+        for (int i = 0; i < count; i++) {
+            RouteInfo route = sStatic.mRoutes.get(i);
+            if (route.matchesTypes(types)) {
+                if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) == 0
+                        || route != sStatic.mDefaultAudioVideo) {
+                    return true;
+                }
+            }
+        }
+
+        // It doesn't look like we can find a matching route right now.
+        return false;
+    }
+
+    /**
      * Add a callback to listen to events about specific kinds of media routes.
      * If the specified callback is already registered, its registration will be updated for any
      * additional route types specified.
@@ -453,9 +839,7 @@
             info = new CallbackInfo(cb, types, flags, this);
             sStatic.mCallbacks.add(info);
         }
-        if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
-            sStatic.updateActiveScan();
-        }
+        sStatic.updateDiscoveryRequest();
     }
 
     /**
@@ -466,10 +850,8 @@
     public void removeCallback(Callback cb) {
         int index = findCallbackInfo(cb);
         if (index >= 0) {
-            CallbackInfo info = sStatic.mCallbacks.remove(index);
-            if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
-                sStatic.updateActiveScan();
-            }
+            sStatic.mCallbacks.remove(index);
+            sStatic.updateDiscoveryRequest();
         } else {
             Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
         }
@@ -499,20 +881,20 @@
      * @param route Route to select
      */
     public void selectRoute(int types, RouteInfo route) {
-        selectRouteStatic(types, route);
+        selectRouteStatic(types, route, true);
     }
-    
+
     /**
      * @hide internal use
      */
-    public void selectRouteInt(int types, RouteInfo route) {
-        selectRouteStatic(types, route);
+    public void selectRouteInt(int types, RouteInfo route, boolean explicit) {
+        selectRouteStatic(types, route, explicit);
     }
 
-    static void selectRouteStatic(int types, RouteInfo route) {
+    static void selectRouteStatic(int types, RouteInfo route, boolean explicit) {
         final RouteInfo oldRoute = sStatic.mSelectedRoute;
         if (oldRoute == route) return;
-        if ((route.getSupportedTypes() & types) == 0) {
+        if (!route.matchesTypes(types)) {
             Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
                     typesToString(route.getSupportedTypes()) + " into route types " +
                     typesToString(types));
@@ -535,19 +917,44 @@
         final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null;
         if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) {
             if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) {
-                sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);
+                if (sStatic.mCanConfigureWifiDisplays) {
+                    sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);
+                } else {
+                    Log.e(TAG, "Cannot connect to wifi displays because this process "
+                            + "is not allowed to do so.");
+                }
             } else if (activeDisplay != null && !newRouteHasAddress) {
                 sStatic.mDisplayService.disconnectWifiDisplay();
             }
         }
 
+        sStatic.setSelectedRoute(route, explicit);
+
         if (oldRoute != null) {
             dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
+            if (oldRoute.resolveStatusCode()) {
+                dispatchRouteChanged(oldRoute);
+            }
         }
-        sStatic.mSelectedRoute = route;
         if (route != null) {
+            if (route.resolveStatusCode()) {
+                dispatchRouteChanged(route);
+            }
             dispatchRouteSelected(types & route.getSupportedTypes(), route);
         }
+
+        // The behavior of active scans may depend on the currently selected route.
+        sStatic.updateDiscoveryRequest();
+    }
+
+    static void selectDefaultRouteStatic() {
+        // TODO: Be smarter about the route types here; this selects for all valid.
+        if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
+                && sStatic.mBluetoothA2dpRoute != null) {
+            selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
+        } else {
+            selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
+        }
     }
 
     /**
@@ -612,7 +1019,7 @@
      * @see #addUserRoute(UserRouteInfo)
      */
     public void removeUserRoute(UserRouteInfo info) {
-        removeRoute(info);
+        removeRouteStatic(info);
     }
 
     /**
@@ -626,7 +1033,7 @@
             // TODO Right now, RouteGroups only ever contain user routes.
             // The code below will need to change if this assumption does.
             if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
-                removeRouteAt(i);
+                removeRouteStatic(info);
                 i--;
             }
         }
@@ -636,10 +1043,10 @@
      * @hide internal use only
      */
     public void removeRouteInt(RouteInfo info) {
-        removeRoute(info);
+        removeRouteStatic(info);
     }
 
-    static void removeRoute(RouteInfo info) {
+    static void removeRouteStatic(RouteInfo info) {
         if (sStatic.mRoutes.remove(info)) {
             final RouteCategory removingCat = info.getCategory();
             final int count = sStatic.mRoutes.size();
@@ -651,42 +1058,9 @@
                     break;
                 }
             }
-            if (info == sStatic.mSelectedRoute) {
+            if (info.isSelected()) {
                 // Removing the currently selected route? Select the default before we remove it.
-                // TODO: Be smarter about the route types here; this selects for all valid.
-                if (info != sStatic.mBluetoothA2dpRoute && sStatic.mBluetoothA2dpRoute != null) {
-                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER,
-                            sStatic.mBluetoothA2dpRoute);
-                } else {
-                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER,
-                            sStatic.mDefaultAudioVideo);
-                }
-            }
-            if (!found) {
-                sStatic.mCategories.remove(removingCat);
-            }
-            dispatchRouteRemoved(info);
-        }
-    }
-
-    void removeRouteAt(int routeIndex) {
-        if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) {
-            final RouteInfo info = sStatic.mRoutes.remove(routeIndex);
-            final RouteCategory removingCat = info.getCategory();
-            final int count = sStatic.mRoutes.size();
-            boolean found = false;
-            for (int i = 0; i < count; i++) {
-                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
-                if (removingCat == cat) {
-                    found = true;
-                    break;
-                }
-            }
-            if (info == sStatic.mSelectedRoute) {
-                // Removing the currently selected route? Select the default before we remove it.
-                // TODO: Be smarter about the route types here; this selects for all valid.
-                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER,
-                        sStatic.mDefaultAudioVideo);
+                selectDefaultRouteStatic();
             }
             if (!found) {
                 sStatic.mCategories.remove(removingCat);
@@ -780,6 +1154,23 @@
         return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
     }
 
+    /**
+     * Rebinds the media router to handle routes that belong to the specified user.
+     * Requires the interact across users permission to access the routes of another user.
+     * <p>
+     * This method is a complete hack to work around the singleton nature of the
+     * media router when running inside of singleton processes like QuickSettings.
+     * This mechanism should be burned to the ground when MediaRouter is redesigned.
+     * Ideally the current user would be pulled from the Context but we need to break
+     * down MediaRouter.Static before we can get there.
+     * </p>
+     *
+     * @hide
+     */
+    public void rebindAsUser(int userId) {
+        sStatic.rebindAsUser(userId);
+    }
+
     static void updateRoute(final RouteInfo info) {
         dispatchRouteChanged(info);
     }
@@ -801,10 +1192,34 @@
     }
 
     static void dispatchRouteChanged(RouteInfo info) {
+        dispatchRouteChanged(info, info.mSupportedTypes);
+    }
+
+    static void dispatchRouteChanged(RouteInfo info, int oldSupportedTypes) {
+        final int newSupportedTypes = info.mSupportedTypes;
         for (CallbackInfo cbi : sStatic.mCallbacks) {
-            if (cbi.filterRouteEvent(info)) {
+            // Reconstruct some of the history for callbacks that may not have observed
+            // all of the events needed to correctly interpret the current state.
+            // FIXME: This is a strong signal that we should deprecate route type filtering
+            // completely in the future because it can lead to inconsistencies in
+            // applications.
+            final boolean oldVisibility = cbi.filterRouteEvent(oldSupportedTypes);
+            final boolean newVisibility = cbi.filterRouteEvent(newSupportedTypes);
+            if (!oldVisibility && newVisibility) {
+                cbi.cb.onRouteAdded(cbi.router, info);
+                if (info.isSelected()) {
+                    cbi.cb.onRouteSelected(cbi.router, newSupportedTypes, info);
+                }
+            }
+            if (oldVisibility || newVisibility) {
                 cbi.cb.onRouteChanged(cbi.router, info);
             }
+            if (oldVisibility && !newVisibility) {
+                if (info.isSelected()) {
+                    cbi.cb.onRouteUnselected(cbi.router, oldSupportedTypes, info);
+                }
+                cbi.cb.onRouteRemoved(cbi.router, info);
+            }
         }
     }
 
@@ -875,65 +1290,73 @@
         }
     }
 
-    static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) {
-        final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus;
-
-        // TODO Naive implementation. Make this smarter later.
-        boolean wantScan = false;
-        boolean blockScan = false;
-        WifiDisplay[] oldDisplays = oldStatus != null ?
-                oldStatus.getDisplays() : WifiDisplay.EMPTY_ARRAY;
-        WifiDisplay[] newDisplays;
+    static void updateWifiDisplayStatus(WifiDisplayStatus status) {
+        WifiDisplay[] displays;
         WifiDisplay activeDisplay;
+        if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
+            displays = status.getDisplays();
+            activeDisplay = status.getActiveDisplay();
 
-        if (newStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
-            newDisplays = newStatus.getDisplays();
-            activeDisplay = newStatus.getActiveDisplay();
+            // Only the system is able to connect to wifi display routes.
+            // The display manager will enforce this with a permission check but it
+            // still publishes information about all available displays.
+            // Filter the list down to just the active display.
+            if (!sStatic.mCanConfigureWifiDisplays) {
+                if (activeDisplay != null) {
+                    displays = new WifiDisplay[] { activeDisplay };
+                } else {
+                    displays = WifiDisplay.EMPTY_ARRAY;
+                }
+            }
         } else {
-            newDisplays = WifiDisplay.EMPTY_ARRAY;
+            displays = WifiDisplay.EMPTY_ARRAY;
             activeDisplay = null;
         }
+        String activeDisplayAddress = activeDisplay != null ?
+                activeDisplay.getDeviceAddress() : null;
 
-        for (int i = 0; i < newDisplays.length; i++) {
-            final WifiDisplay d = newDisplays[i];
-            if (d.isRemembered()) {
+        // Add or update routes.
+        for (int i = 0; i < displays.length; i++) {
+            final WifiDisplay d = displays[i];
+            if (shouldShowWifiDisplay(d, activeDisplay)) {
                 RouteInfo route = findWifiDisplayRoute(d);
                 if (route == null) {
-                    route = makeWifiDisplayRoute(d, newStatus);
+                    route = makeWifiDisplayRoute(d, status);
                     addRouteStatic(route);
-                    wantScan = true;
                 } else {
-                    updateWifiDisplayRoute(route, d, newStatus);
+                    String address = d.getDeviceAddress();
+                    boolean disconnected = !address.equals(activeDisplayAddress)
+                            && address.equals(sStatic.mPreviousActiveWifiDisplayAddress);
+                    updateWifiDisplayRoute(route, d, status, disconnected);
                 }
                 if (d.equals(activeDisplay)) {
-                    selectRouteStatic(route.getSupportedTypes(), route);
-
-                    // Don't scan if we're already connected to a wifi display,
-                    // the scanning process can cause a hiccup with some configurations.
-                    blockScan = true;
-                }
-            }
-        }
-        for (int i = 0; i < oldDisplays.length; i++) {
-            final WifiDisplay d = oldDisplays[i];
-            if (d.isRemembered()) {
-                final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
-                if (newDisplay == null || !newDisplay.isRemembered()) {
-                    removeRoute(findWifiDisplayRoute(d));
+                    selectRouteStatic(route.getSupportedTypes(), route, false);
                 }
             }
         }
 
-        if (wantScan && !blockScan) {
-            sStatic.mDisplayService.scanWifiDisplays();
+        // Remove stale routes.
+        for (int i = sStatic.mRoutes.size(); i-- > 0; ) {
+            RouteInfo route = sStatic.mRoutes.get(i);
+            if (route.mDeviceAddress != null) {
+                WifiDisplay d = findWifiDisplay(displays, route.mDeviceAddress);
+                if (d == null || !shouldShowWifiDisplay(d, activeDisplay)) {
+                    removeRouteStatic(route);
+                }
+            }
         }
 
-        sStatic.mLastKnownWifiDisplayStatus = newStatus;
+        // Remember the current active wifi display address so that we can infer disconnections.
+        // TODO: This hack will go away once all of this is moved into the media router service.
+        sStatic.mPreviousActiveWifiDisplayAddress = activeDisplayAddress;
+    }
+
+    private static boolean shouldShowWifiDisplay(WifiDisplay d, WifiDisplay activeDisplay) {
+        return d.isRemembered() || d.equals(activeDisplay);
     }
 
     static int getWifiDisplayStatusCode(WifiDisplay d, WifiDisplayStatus wfdStatus) {
-        int newStatus = RouteInfo.STATUS_NONE;
-
+        int newStatus;
         if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) {
             newStatus = RouteInfo.STATUS_SCANNING;
         } else if (d.isAvailable()) {
@@ -947,7 +1370,7 @@
             final int activeState = wfdStatus.getActiveDisplayState();
             switch (activeState) {
                 case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
-                    newStatus = RouteInfo.STATUS_NONE;
+                    newStatus = RouteInfo.STATUS_CONNECTED;
                     break;
                 case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
                     newStatus = RouteInfo.STATUS_CONNECTING;
@@ -968,23 +1391,23 @@
     static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) {
         final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
         newRoute.mDeviceAddress = display.getDeviceAddress();
-        newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
+        newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO
+                | ROUTE_TYPE_REMOTE_DISPLAY;
         newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
         newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
 
-        newRoute.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus));
+        newRoute.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus));
         newRoute.mEnabled = isWifiDisplayEnabled(display, wfdStatus);
         newRoute.mName = display.getFriendlyDisplayName();
         newRoute.mDescription = sStatic.mResources.getText(
                 com.android.internal.R.string.wireless_display_route_description);
-
-        newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute,
-                sStatic.getAllPresentationDisplays());
+        newRoute.updatePresentationDisplay();
         return newRoute;
     }
 
     private static void updateWifiDisplayRoute(
-            RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus) {
+            RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus,
+            boolean disconnected) {
         boolean changed = false;
         final String newName = display.getFriendlyDisplayName();
         if (!route.getName().equals(newName)) {
@@ -996,24 +1419,23 @@
         changed |= route.mEnabled != enabled;
         route.mEnabled = enabled;
 
-        changed |= route.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus));
+        changed |= route.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus));
 
         if (changed) {
             dispatchRouteChanged(route);
         }
 
-        if (!enabled && route == sStatic.mSelectedRoute) {
+        if ((!enabled || disconnected) && route.isSelected()) {
             // Oops, no longer available. Reselect the default.
-            final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo;
-            selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute);
+            selectDefaultRouteStatic();
         }
     }
 
-    private static WifiDisplay findMatchingDisplay(WifiDisplay d, WifiDisplay[] displays) {
+    private static WifiDisplay findWifiDisplay(WifiDisplay[] displays, String deviceAddress) {
         for (int i = 0; i < displays.length; i++) {
-            final WifiDisplay other = displays[i];
-            if (d.hasSameAddress(other)) {
-                return other;
+            final WifiDisplay d = displays[i];
+            if (d.getDeviceAddress().equals(deviceAddress)) {
+                return d;
             }
         }
         return null;
@@ -1030,27 +1452,6 @@
         return null;
     }
 
-    private static Display choosePresentationDisplayForRoute(RouteInfo route, Display[] displays) {
-        if ((route.mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
-            if (route.mDeviceAddress != null) {
-                // Find the indicated Wifi display by its address.
-                for (Display display : displays) {
-                    if (display.getType() == Display.TYPE_WIFI
-                            && route.mDeviceAddress.equals(display.getAddress())) {
-                        return display;
-                    }
-                }
-                return null;
-            }
-
-            if (route == sStatic.mDefaultAudioVideo && displays.length > 0) {
-                // Choose the first presentation display from the list.
-                return displays[0];
-            }
-        }
-        return null;
-    }
-
     /**
      * Information about a media route.
      */
@@ -1071,12 +1472,18 @@
         int mPlaybackStream = AudioManager.STREAM_MUSIC;
         VolumeCallbackInfo mVcb;
         Display mPresentationDisplay;
+        int mPresentationDisplayId = -1;
 
         String mDeviceAddress;
         boolean mEnabled = true;
 
+        // An id by which the route is known to the media router service.
+        // Null if this route only exists as an artifact within this process.
+        String mGlobalRouteId;
+
         // A predetermined connection status that can override mStatus
-        private int mStatusCode;
+        private int mRealStatusCode;
+        private int mResolvedStatusCode;
 
         /** @hide */ public static final int STATUS_NONE = 0;
         /** @hide */ public static final int STATUS_SCANNING = 1;
@@ -1084,19 +1491,20 @@
         /** @hide */ public static final int STATUS_AVAILABLE = 3;
         /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4;
         /** @hide */ public static final int STATUS_IN_USE = 5;
+        /** @hide */ public static final int STATUS_CONNECTED = 6;
 
         private Object mTag;
 
         /**
          * The default playback type, "local", indicating the presentation of the media is happening
          * on the same device (e.g. a phone, a tablet) as where it is controlled from.
-         * @see #setPlaybackType(int)
+         * @see #getPlaybackType()
          */
         public final static int PLAYBACK_TYPE_LOCAL = 0;
         /**
          * A playback type indicating the presentation of the media is happening on
          * a different device (i.e. the remote device) than where it is controlled from.
-         * @see #setPlaybackType(int)
+         * @see #getPlaybackType()
          */
         public final static int PLAYBACK_TYPE_REMOTE = 1;
         /**
@@ -1104,12 +1512,13 @@
          * controlled from this object. An example of fixed playback volume is a remote player,
          * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
          * than attenuate at the source.
-         * @see #setVolumeHandling(int)
+         * @see #getVolumeHandling()
          */
         public final static int PLAYBACK_VOLUME_FIXED = 0;
         /**
          * Playback information indicating the playback volume is variable and can be controlled
          * from this object.
+         * @see #getVolumeHandling()
          */
         public final static int PLAYBACK_VOLUME_VARIABLE = 1;
 
@@ -1178,38 +1587,71 @@
          * Set this route's status by predetermined status code. If the caller
          * should dispatch a route changed event this call will return true;
          */
-        boolean setStatusCode(int statusCode) {
-            if (statusCode != mStatusCode) {
-                mStatusCode = statusCode;
-                int resId = 0;
-                switch (statusCode) {
-                    case STATUS_SCANNING:
-                        resId = com.android.internal.R.string.media_route_status_scanning;
-                        break;
-                    case STATUS_CONNECTING:
-                        resId = com.android.internal.R.string.media_route_status_connecting;
-                        break;
-                    case STATUS_AVAILABLE:
-                        resId = com.android.internal.R.string.media_route_status_available;
-                        break;
-                    case STATUS_NOT_AVAILABLE:
-                        resId = com.android.internal.R.string.media_route_status_not_available;
-                        break;
-                    case STATUS_IN_USE:
-                        resId = com.android.internal.R.string.media_route_status_in_use;
-                        break;
-                }
-                mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
-                return true;
+        boolean setRealStatusCode(int statusCode) {
+            if (mRealStatusCode != statusCode) {
+                mRealStatusCode = statusCode;
+                return resolveStatusCode();
             }
             return false;
         }
 
         /**
+         * Resolves the status code whenever the real status code or selection state
+         * changes.
+         */
+        boolean resolveStatusCode() {
+            int statusCode = mRealStatusCode;
+            if (isSelected()) {
+                switch (statusCode) {
+                    // If the route is selected and its status appears to be between states
+                    // then report it as connecting even though it has not yet had a chance
+                    // to officially move into the CONNECTING state.  Note that routes in
+                    // the NONE state are assumed to not require an explicit connection
+                    // lifecycle whereas those that are AVAILABLE are assumed to have
+                    // to eventually proceed to CONNECTED.
+                    case STATUS_AVAILABLE:
+                    case STATUS_SCANNING:
+                        statusCode = STATUS_CONNECTING;
+                        break;
+                }
+            }
+            if (mResolvedStatusCode == statusCode) {
+                return false;
+            }
+
+            mResolvedStatusCode = statusCode;
+            int resId;
+            switch (statusCode) {
+                case STATUS_SCANNING:
+                    resId = com.android.internal.R.string.media_route_status_scanning;
+                    break;
+                case STATUS_CONNECTING:
+                    resId = com.android.internal.R.string.media_route_status_connecting;
+                    break;
+                case STATUS_AVAILABLE:
+                    resId = com.android.internal.R.string.media_route_status_available;
+                    break;
+                case STATUS_NOT_AVAILABLE:
+                    resId = com.android.internal.R.string.media_route_status_not_available;
+                    break;
+                case STATUS_IN_USE:
+                    resId = com.android.internal.R.string.media_route_status_in_use;
+                    break;
+                case STATUS_CONNECTED:
+                case STATUS_NONE:
+                default:
+                    resId = 0;
+                    break;
+            }
+            mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
+            return true;
+        }
+
+        /**
          * @hide
          */
         public int getStatusCode() {
-            return mStatusCode;
+            return mResolvedStatusCode;
         }
 
         /**
@@ -1219,6 +1661,11 @@
             return mSupportedTypes;
         }
 
+        /** @hide */
+        public boolean matchesTypes(int types) {
+            return (mSupportedTypes & types) != 0;
+        }
+
         /**
          * @return The group that this route belongs to.
          */
@@ -1317,9 +1764,7 @@
                     Log.e(TAG, "Error setting local stream volume", e);
                 }
             } else {
-                Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " +
-                        "Non-local volume playback on system route? " +
-                        "Could not request volume change.");
+                sStatic.requestSetVolume(this, volume);
             }
         }
 
@@ -1338,9 +1783,7 @@
                     Log.e(TAG, "Error setting local stream volume", e);
                 }
             } else {
-                Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " +
-                        "Non-local volume playback on system route? " +
-                        "Could not request volume change.");
+                sStatic.requestUpdateVolume(this, direction);
             }
         }
 
@@ -1402,6 +1845,55 @@
             return mPresentationDisplay;
         }
 
+        boolean updatePresentationDisplay() {
+            Display display = choosePresentationDisplay();
+            if (mPresentationDisplay != display) {
+                mPresentationDisplay = display;
+                return true;
+            }
+            return false;
+        }
+
+        private Display choosePresentationDisplay() {
+            if ((mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
+                Display[] displays = sStatic.getAllPresentationDisplays();
+
+                // Ensure that the specified display is valid for presentations.
+                // This check will normally disallow the default display unless it was
+                // configured as a presentation display for some reason.
+                if (mPresentationDisplayId >= 0) {
+                    for (Display display : displays) {
+                        if (display.getDisplayId() == mPresentationDisplayId) {
+                            return display;
+                        }
+                    }
+                    return null;
+                }
+
+                // Find the indicated Wifi display by its address.
+                if (mDeviceAddress != null) {
+                    for (Display display : displays) {
+                        if (display.getType() == Display.TYPE_WIFI
+                                && mDeviceAddress.equals(display.getAddress())) {
+                            return display;
+                        }
+                    }
+                    return null;
+                }
+
+                // For the default route, choose the first presentation display from the list.
+                if (this == sStatic.mDefaultAudioVideo && displays.length > 0) {
+                    return displays[0];
+                }
+            }
+            return null;
+        }
+
+        /** @hide */
+        public String getDeviceAddress() {
+            return mDeviceAddress;
+        }
+
         /**
          * Returns true if this route is enabled and may be selected.
          *
@@ -1418,7 +1910,22 @@
          * @return True if this route is in the process of connecting.
          */
         public boolean isConnecting() {
-            return mStatusCode == STATUS_CONNECTING;
+            return mResolvedStatusCode == STATUS_CONNECTING;
+        }
+
+        /** @hide */
+        public boolean isSelected() {
+            return this == sStatic.mSelectedRoute;
+        }
+
+        /** @hide */
+        public boolean isDefault() {
+            return this == sStatic.mDefaultAudioVideo;
+        }
+
+        /** @hide */
+        public void select() {
+            selectRouteStatic(mSupportedTypes, this, true);
         }
 
         void setStatusInt(CharSequence status) {
@@ -1432,6 +1939,7 @@
         }
 
         final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
+            @Override
             public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
                 sStatic.mHandler.post(new Runnable() {
                     @Override
@@ -1460,7 +1968,7 @@
                     ", status=" + getStatus() +
                     ", category=" + getCategory() +
                     ", supportedTypes=" + supportedTypes +
-                    ", presentationDisplay=" + mPresentationDisplay + "}";
+                    ", presentationDisplay=" + mPresentationDisplay + " }";
         }
     }
 
@@ -1716,6 +2224,7 @@
             mVolumeHandling = PLAYBACK_VOLUME_FIXED;
         }
 
+        @Override
         CharSequence getName(Resources res) {
             if (mUpdateName) updateName();
             return super.getName(res);
@@ -1916,7 +2425,7 @@
             final int count = mRoutes.size();
             if (count == 0) {
                 // Don't keep empty groups in the router.
-                MediaRouter.removeRoute(this);
+                MediaRouter.removeRouteStatic(this);
                 return;
             }
 
@@ -2071,6 +2580,7 @@
             return mIsSystem;
         }
 
+        @Override
         public String toString() {
             return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
                     " groupable=" + mGroupable + " }";
@@ -2091,8 +2601,12 @@
         }
 
         public boolean filterRouteEvent(RouteInfo route) {
+            return filterRouteEvent(route.mSupportedTypes);
+        }
+
+        public boolean filterRouteEvent(int supportedTypes) {
             return (flags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
-                    || (type & route.mSupportedTypes) != 0;
+                    || (type & supportedTypes) != 0;
         }
     }
 
diff --git a/media/java/android/media/MediaRouterClientState.aidl b/media/java/android/media/MediaRouterClientState.aidl
new file mode 100644
index 0000000..70077119
--- /dev/null
+++ b/media/java/android/media/MediaRouterClientState.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.media;
+
+parcelable MediaRouterClientState;
diff --git a/media/java/android/media/MediaRouterClientState.java b/media/java/android/media/MediaRouterClientState.java
new file mode 100644
index 0000000..54b8276
--- /dev/null
+++ b/media/java/android/media/MediaRouterClientState.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+
+/**
+ * Information available from MediaRouterService about the state perceived by
+ * a particular client and the routes that are available to it.
+ *
+ * Clients must not modify the contents of this object.
+ * @hide
+ */
+public final class MediaRouterClientState implements Parcelable {
+    /**
+     * A list of all known routes.
+     */
+    public final ArrayList<RouteInfo> routes;
+
+    /**
+     * The id of the current globally selected route, or null if none.
+     * Globally selected routes override any other route selections that applications
+     * may have made.  Used for remote displays.
+     */
+    public String globallySelectedRouteId;
+
+    public MediaRouterClientState() {
+        routes = new ArrayList<RouteInfo>();
+    }
+
+    MediaRouterClientState(Parcel src) {
+        routes = src.createTypedArrayList(RouteInfo.CREATOR);
+        globallySelectedRouteId = src.readString();
+    }
+
+    public RouteInfo getRoute(String id) {
+        final int count = routes.size();
+        for (int i = 0; i < count; i++) {
+            final RouteInfo route = routes.get(i);
+            if (route.id.equals(id)) {
+                return route;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedList(routes);
+        dest.writeString(globallySelectedRouteId);
+    }
+
+    @Override
+    public String toString() {
+        return "MediaRouterClientState{ globallySelectedRouteId="
+                + globallySelectedRouteId + ", routes=" + routes.toString() + " }";
+    }
+
+    public static final Parcelable.Creator<MediaRouterClientState> CREATOR =
+            new Parcelable.Creator<MediaRouterClientState>() {
+        @Override
+        public MediaRouterClientState createFromParcel(Parcel in) {
+            return new MediaRouterClientState(in);
+        }
+
+        @Override
+        public MediaRouterClientState[] newArray(int size) {
+            return new MediaRouterClientState[size];
+        }
+    };
+
+    public static final class RouteInfo implements Parcelable {
+        public String id;
+        public String name;
+        public String description;
+        public int supportedTypes;
+        public boolean enabled;
+        public int statusCode;
+        public int playbackType;
+        public int playbackStream;
+        public int volume;
+        public int volumeMax;
+        public int volumeHandling;
+        public int presentationDisplayId;
+
+        public RouteInfo(String id) {
+            this.id = id;
+            enabled = true;
+            statusCode = MediaRouter.RouteInfo.STATUS_NONE;
+            playbackType = MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+            playbackStream = -1;
+            volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+            presentationDisplayId = -1;
+        }
+
+        public RouteInfo(RouteInfo other) {
+            id = other.id;
+            name = other.name;
+            description = other.description;
+            supportedTypes = other.supportedTypes;
+            enabled = other.enabled;
+            statusCode = other.statusCode;
+            playbackType = other.playbackType;
+            playbackStream = other.playbackStream;
+            volume = other.volume;
+            volumeMax = other.volumeMax;
+            volumeHandling = other.volumeHandling;
+            presentationDisplayId = other.presentationDisplayId;
+        }
+
+        RouteInfo(Parcel in) {
+            id = in.readString();
+            name = in.readString();
+            description = in.readString();
+            supportedTypes = in.readInt();
+            enabled = in.readInt() != 0;
+            statusCode = in.readInt();
+            playbackType = in.readInt();
+            playbackStream = in.readInt();
+            volume = in.readInt();
+            volumeMax = in.readInt();
+            volumeHandling = in.readInt();
+            presentationDisplayId = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(id);
+            dest.writeString(name);
+            dest.writeString(description);
+            dest.writeInt(supportedTypes);
+            dest.writeInt(enabled ? 1 : 0);
+            dest.writeInt(statusCode);
+            dest.writeInt(playbackType);
+            dest.writeInt(playbackStream);
+            dest.writeInt(volume);
+            dest.writeInt(volumeMax);
+            dest.writeInt(volumeHandling);
+            dest.writeInt(presentationDisplayId);
+        }
+
+        @Override
+        public String toString() {
+            return "RouteInfo{ id=" + id
+                    + ", name=" + name
+                    + ", description=" + description
+                    + ", supportedTypes=0x" + Integer.toHexString(supportedTypes)
+                    + ", enabled=" + enabled
+                    + ", statusCode=" + statusCode
+                    + ", playbackType=" + playbackType
+                    + ", playbackStream=" + playbackStream
+                    + ", volume=" + volume
+                    + ", volumeMax=" + volumeMax
+                    + ", volumeHandling=" + volumeHandling
+                    + ", presentationDisplayId=" + presentationDisplayId
+                    + " }";
+        }
+
+        @SuppressWarnings("hiding")
+        public static final Parcelable.Creator<RouteInfo> CREATOR =
+                new Parcelable.Creator<RouteInfo>() {
+            @Override
+            public RouteInfo createFromParcel(Parcel in) {
+                return new RouteInfo(in);
+            }
+
+            @Override
+            public RouteInfo[] newArray(int size) {
+                return new RouteInfo[size];
+            }
+        };
+    }
+}
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 6dbb3cd..910b24c 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -404,7 +404,7 @@
      * @throws IllegalArgumentException
      */
     public boolean setSynchronizationMode(int sync) throws IllegalArgumentException {
-        if ((sync != POSITION_SYNCHRONIZATION_NONE) || (sync != POSITION_SYNCHRONIZATION_CHECK)) {
+        if ((sync != POSITION_SYNCHRONIZATION_NONE) && (sync != POSITION_SYNCHRONIZATION_CHECK)) {
             throw new IllegalArgumentException("Unknown synchronization mode " + sync);
         }
         if (!mIsRegistered) {
diff --git a/media/java/android/media/RemoteDisplayState.aidl b/media/java/android/media/RemoteDisplayState.aidl
new file mode 100644
index 0000000..b3262fc
--- /dev/null
+++ b/media/java/android/media/RemoteDisplayState.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.media;
+
+parcelable RemoteDisplayState;
diff --git a/media/java/android/media/RemoteDisplayState.java b/media/java/android/media/RemoteDisplayState.java
new file mode 100644
index 0000000..1197f65
--- /dev/null
+++ b/media/java/android/media/RemoteDisplayState.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Information available from IRemoteDisplayProvider about available remote displays.
+ *
+ * Clients must not modify the contents of this object.
+ * @hide
+ */
+public final class RemoteDisplayState implements Parcelable {
+    // Note: These constants are used by the remote display provider API.
+    // Do not change them!
+    public static final String SERVICE_INTERFACE =
+            "com.android.media.remotedisplay.RemoteDisplayProvider";
+    public static final int DISCOVERY_MODE_NONE = 0;
+    public static final int DISCOVERY_MODE_PASSIVE = 1;
+    public static final int DISCOVERY_MODE_ACTIVE = 2;
+
+    /**
+     * A list of all remote displays.
+     */
+    public final ArrayList<RemoteDisplayInfo> displays;
+
+    public RemoteDisplayState() {
+        displays = new ArrayList<RemoteDisplayInfo>();
+    }
+
+    RemoteDisplayState(Parcel src) {
+        displays = src.createTypedArrayList(RemoteDisplayInfo.CREATOR);
+    }
+
+    public boolean isValid() {
+        if (displays == null) {
+            return false;
+        }
+        final int count = displays.size();
+        for (int i = 0; i < count; i++) {
+            if (!displays.get(i).isValid()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedList(displays);
+    }
+
+    public static final Parcelable.Creator<RemoteDisplayState> CREATOR =
+            new Parcelable.Creator<RemoteDisplayState>() {
+        @Override
+        public RemoteDisplayState createFromParcel(Parcel in) {
+            return new RemoteDisplayState(in);
+        }
+
+        @Override
+        public RemoteDisplayState[] newArray(int size) {
+            return new RemoteDisplayState[size];
+        }
+    };
+
+    public static final class RemoteDisplayInfo implements Parcelable {
+        // Note: These constants are used by the remote display provider API.
+        // Do not change them!
+        public static final int STATUS_NOT_AVAILABLE = 0;
+        public static final int STATUS_IN_USE = 1;
+        public static final int STATUS_AVAILABLE = 2;
+        public static final int STATUS_CONNECTING = 3;
+        public static final int STATUS_CONNECTED = 4;
+
+        public static final int PLAYBACK_VOLUME_VARIABLE =
+                MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+        public static final int PLAYBACK_VOLUME_FIXED =
+                MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+
+        public String id;
+        public String name;
+        public String description;
+        public int status;
+        public int volume;
+        public int volumeMax;
+        public int volumeHandling;
+        public int presentationDisplayId;
+
+        public RemoteDisplayInfo(String id) {
+            this.id = id;
+            status = STATUS_NOT_AVAILABLE;
+            volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+            presentationDisplayId = -1;
+        }
+
+        public RemoteDisplayInfo(RemoteDisplayInfo other) {
+            id = other.id;
+            name = other.name;
+            description = other.description;
+            status = other.status;
+            volume = other.volume;
+            volumeMax = other.volumeMax;
+            volumeHandling = other.volumeHandling;
+            presentationDisplayId = other.presentationDisplayId;
+        }
+
+        RemoteDisplayInfo(Parcel in) {
+            id = in.readString();
+            name = in.readString();
+            description = in.readString();
+            status = in.readInt();
+            volume = in.readInt();
+            volumeMax = in.readInt();
+            volumeHandling = in.readInt();
+            presentationDisplayId = in.readInt();
+        }
+
+        public boolean isValid() {
+            return !TextUtils.isEmpty(id) && !TextUtils.isEmpty(name);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(id);
+            dest.writeString(name);
+            dest.writeString(description);
+            dest.writeInt(status);
+            dest.writeInt(volume);
+            dest.writeInt(volumeMax);
+            dest.writeInt(volumeHandling);
+            dest.writeInt(presentationDisplayId);
+        }
+
+        @Override
+        public String toString() {
+            return "RemoteDisplayInfo{ id=" + id
+                    + ", name=" + name
+                    + ", description=" + description
+                    + ", status=" + status
+                    + ", volume=" + volume
+                    + ", volumeMax=" + volumeMax
+                    + ", volumeHandling=" + volumeHandling
+                    + ", presentationDisplayId=" + presentationDisplayId
+                    + " }";
+        }
+
+        @SuppressWarnings("hiding")
+        public static final Parcelable.Creator<RemoteDisplayInfo> CREATOR =
+                new Parcelable.Creator<RemoteDisplayInfo>() {
+            @Override
+            public RemoteDisplayInfo createFromParcel(Parcel in) {
+                return new RemoteDisplayInfo(in);
+            }
+
+            @Override
+            public RemoteDisplayInfo[] newArray(int size) {
+                return new RemoteDisplayInfo[size];
+            }
+        };
+    }
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 63a61e2..dea971e 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -41,13 +41,13 @@
     libstagefright_amrnb_common \
 
 LOCAL_REQUIRED_MODULES := \
-    libexif_jni
+    libjhead_jni
 
 LOCAL_STATIC_LIBRARIES := \
     libstagefright_amrnbenc
 
 LOCAL_C_INCLUDES += \
-    external/jhead \
+    external/libexif/ \
     external/tremor/Tremor \
     frameworks/base/core/jni \
     frameworks/av/media/libmedia \
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index d134667..4be9cd6 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -115,6 +115,7 @@
             nativeParcel->setData(obj->data(), obj->dataSize());
             env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
                     msg, ext1, ext2, jParcel);
+            env->DeleteLocalRef(jParcel);
         }
     } else {
         env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 77c7966..067e7a6 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -37,7 +37,10 @@
 #include "mtp.h"
 
 extern "C" {
-#include "jhead.h"
+#include "libexif/exif-content.h"
+#include "libexif/exif-data.h"
+#include "libexif/exif-tag.h"
+#include "libexif/exif-utils.h"
 }
 
 using namespace android;
@@ -750,6 +753,22 @@
     return result;
 }
 
+static void foreachentry(ExifEntry *entry, void *user) {
+    char buf[1024];
+    ALOGI("entry %x, format %d, size %d: %s",
+            entry->tag, entry->format, entry->size, exif_entry_get_value(entry, buf, sizeof(buf)));
+}
+
+static void foreachcontent(ExifContent *content, void *user) {
+    ALOGI("content %d", exif_content_get_ifd(content));
+    exif_content_foreach_entry(content, foreachentry, user);
+}
+
+static long getLongFromExifEntry(ExifEntry *e) {
+    ExifByteOrder o = exif_data_get_byte_order(e->parent->parent);
+    return exif_get_long(e->data, o);
+}
+
 MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
                                             MtpObjectInfo& info) {
     char            date[20];
@@ -792,23 +811,22 @@
 
     // read EXIF data for thumbnail information
     if (info.mFormat == MTP_FORMAT_EXIF_JPEG || info.mFormat == MTP_FORMAT_JFIF) {
-        ResetJpgfile();
-         // Start with an empty image information structure.
-        memset(&ImageInfo, 0, sizeof(ImageInfo));
-        ImageInfo.FlashUsed = -1;
-        ImageInfo.MeteringMode = -1;
-        ImageInfo.Whitebalance = -1;
-        strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX);
-        if (ReadJpegFile((const char*)path, READ_METADATA)) {
-            Section_t* section = FindSection(M_EXIF);
-            if (section) {
-                info.mThumbCompressedSize = ImageInfo.ThumbnailSize;
-                info.mThumbFormat = MTP_FORMAT_EXIF_JPEG;
-                info.mImagePixWidth = ImageInfo.Width;
-                info.mImagePixHeight = ImageInfo.Height;
-            }
+
+        ExifData *exifdata = exif_data_new_from_file(path);
+        if (exifdata) {
+            //exif_data_foreach_content(exifdata, foreachcontent, NULL);
+
+            // XXX get this from exif, or parse jpeg header instead?
+            ExifEntry *w = exif_content_get_entry(
+                    exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_X_DIMENSION);
+            ExifEntry *h = exif_content_get_entry(
+                    exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_Y_DIMENSION);
+            info.mThumbCompressedSize = exifdata->data ? exifdata->size : 0;
+            info.mThumbFormat = MTP_FORMAT_EXIF_JPEG;
+            info.mImagePixWidth = w ? getLongFromExifEntry(w) : 0;
+            info.mImagePixHeight = h ? getLongFromExifEntry(h) : 0;
+            exif_data_unref(exifdata);
         }
-        DiscardData();
     }
 
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
@@ -824,22 +842,16 @@
 
     if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK
             && (format == MTP_FORMAT_EXIF_JPEG || format == MTP_FORMAT_JFIF)) {
-        ResetJpgfile();
-         // Start with an empty image information structure.
-        memset(&ImageInfo, 0, sizeof(ImageInfo));
-        ImageInfo.FlashUsed = -1;
-        ImageInfo.MeteringMode = -1;
-        ImageInfo.Whitebalance = -1;
-        strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX);
-        if (ReadJpegFile((const char*)path, READ_METADATA)) {
-            Section_t* section = FindSection(M_EXIF);
-            if (section) {
-                outThumbSize = ImageInfo.ThumbnailSize;
-                result = malloc(outThumbSize);
-                if (result)
-                    memcpy(result, section->Data + ImageInfo.ThumbnailOffset + 8, outThumbSize);
+
+        ExifData *exifdata = exif_data_new_from_file(path);
+        if (exifdata) {
+            if (exifdata->data) {
+                result = malloc(exifdata->size);
+                if (result) {
+                    memcpy(result, exifdata->data, exifdata->size);
+                }
             }
-            DiscardData();
+            exif_data_unref(exifdata);
         }
     }
 
diff --git a/media/lib/Android.mk b/media/lib/Android.mk
new file mode 100644
index 0000000..50799a6
--- /dev/null
+++ b/media/lib/Android.mk
@@ -0,0 +1,46 @@
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+
+# the library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE:= com.android.media.remotedisplay
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+            $(call all-subdir-java-files) \
+            $(call all-aidl-files-under, java)
+
+include $(BUILD_JAVA_LIBRARY)
+
+
+# ====  com.android.media.remotedisplay.xml lib def  ========================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := com.android.media.remotedisplay.xml
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_CLASS := ETC
+
+# This will install the file in /system/etc/permissions
+#
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions
+
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+
+include $(BUILD_PREBUILT)
diff --git a/media/lib/README.txt b/media/lib/README.txt
new file mode 100644
index 0000000..cade3df
--- /dev/null
+++ b/media/lib/README.txt
@@ -0,0 +1,28 @@
+This library (com.android.media.remotedisplay.jar) is a shared java library
+containing classes required by unbundled remote display providers.
+
+--- Rules of this library ---
+o This library is effectively a PUBLIC API for unbundled remote display providers
+  that may be distributed outside the system image. So it MUST BE API STABLE.
+  You can add but not remove. The rules are the same as for the
+  public platform SDK API.
+o This library can see and instantiate internal platform classes, but it must not
+  expose them in any public method (or by extending them via inheritance). This would
+  break clients of the library because they cannot see the internal platform classes.
+
+This library is distributed in the system image, and loaded as
+a shared library. So you can change the implementation, but not
+the interface. In this way it is like framework.jar.
+
+--- Why does this library exists? ---
+
+Unbundled remote display providers (such as Cast) cannot use internal
+platform classes.
+
+This library will eventually be replaced when the media route provider
+infrastructure that is currently defined in the support library is reintegrated
+with the framework in a new API.  That API isn't ready yet so this
+library is a compromise to make new capabilities available to the system
+without exposing the full surface area of the support library media
+route provider protocol.
+
diff --git a/core/res/res/values-ja/bools.xml b/media/lib/com.android.media.remotedisplay.xml
similarity index 80%
copy from core/res/res/values-ja/bools.xml
copy to media/lib/com.android.media.remotedisplay.xml
index 59cf744..77a91d2 100644
--- a/core/res/res/values-ja/bools.xml
+++ b/media/lib/com.android.media.remotedisplay.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<resources>
-    <bool name="flip_controller_fallback_keys">true</bool>
-</resources>
-
+<permissions>
+    <library name="com.android.media.remotedisplay"
+            file="/system/framework/com.android.media.remotedisplay.jar" />
+</permissions>
diff --git a/media/lib/java/com/android/media/remotedisplay/RemoteDisplay.java b/media/lib/java/com/android/media/remotedisplay/RemoteDisplay.java
new file mode 100644
index 0000000..5e15702
--- /dev/null
+++ b/media/lib/java/com/android/media/remotedisplay/RemoteDisplay.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.remotedisplay;
+
+import com.android.internal.util.Objects;
+
+import android.media.MediaRouter;
+import android.media.RemoteDisplayState.RemoteDisplayInfo;
+import android.text.TextUtils;
+
+/**
+ * Represents a remote display that has been discovered.
+ */
+public class RemoteDisplay {
+    private final RemoteDisplayInfo mMutableInfo;
+    private RemoteDisplayInfo mImmutableInfo;
+
+    /**
+     * Status code: Indicates that the remote display is not available.
+     */
+    public static final int STATUS_NOT_AVAILABLE = RemoteDisplayInfo.STATUS_NOT_AVAILABLE;
+
+    /**
+     * Status code: Indicates that the remote display is in use by someone else.
+     */
+    public static final int STATUS_IN_USE = RemoteDisplayInfo.STATUS_IN_USE;
+
+    /**
+     * Status code: Indicates that the remote display is available for new connections.
+     */
+    public static final int STATUS_AVAILABLE = RemoteDisplayInfo.STATUS_AVAILABLE;
+
+    /**
+     * Status code: Indicates that the remote display is current connecting.
+     */
+    public static final int STATUS_CONNECTING = RemoteDisplayInfo.STATUS_CONNECTING;
+
+    /**
+     * Status code: Indicates that the remote display is connected and is mirroring
+     * display contents.
+     */
+    public static final int STATUS_CONNECTED = RemoteDisplayInfo.STATUS_CONNECTED;
+
+    /**
+     * Volume handling: Output volume can be changed.
+     */
+    public static final int PLAYBACK_VOLUME_VARIABLE =
+            RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE;
+
+    /**
+     * Volume handling: Output volume is fixed.
+     */
+    public static final int PLAYBACK_VOLUME_FIXED =
+            RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED;
+
+    /**
+     * Creates a remote display with the specified name and id.
+     */
+    public RemoteDisplay(String id, String name) {
+        if (TextUtils.isEmpty(id)) {
+            throw new IllegalArgumentException("id must not be null or empty");
+        }
+        mMutableInfo = new RemoteDisplayInfo(id);
+        setName(name);
+    }
+
+    public String getId() {
+        return mMutableInfo.id;
+    }
+
+    public String getName() {
+        return mMutableInfo.name;
+    }
+
+    public void setName(String name) {
+        if (!Objects.equal(mMutableInfo.name, name)) {
+            mMutableInfo.name = name;
+            mImmutableInfo = null;
+        }
+    }
+
+    public String getDescription() {
+        return mMutableInfo.description;
+    }
+
+    public void setDescription(String description) {
+        if (!Objects.equal(mMutableInfo.description, description)) {
+            mMutableInfo.description = description;
+            mImmutableInfo = null;
+        }
+    }
+
+    public int getStatus() {
+        return mMutableInfo.status;
+    }
+
+    public void setStatus(int status) {
+        if (mMutableInfo.status != status) {
+            mMutableInfo.status = status;
+            mImmutableInfo = null;
+        }
+    }
+
+    public int getVolume() {
+        return mMutableInfo.volume;
+    }
+
+    public void setVolume(int volume) {
+        if (mMutableInfo.volume != volume) {
+            mMutableInfo.volume = volume;
+            mImmutableInfo = null;
+        }
+    }
+
+    public int getVolumeMax() {
+        return mMutableInfo.volumeMax;
+    }
+
+    public void setVolumeMax(int volumeMax) {
+        if (mMutableInfo.volumeMax != volumeMax) {
+            mMutableInfo.volumeMax = volumeMax;
+            mImmutableInfo = null;
+        }
+    }
+
+    public int getVolumeHandling() {
+        return mMutableInfo.volumeHandling;
+    }
+
+    public void setVolumeHandling(int volumeHandling) {
+        if (mMutableInfo.volumeHandling != volumeHandling) {
+            mMutableInfo.volumeHandling = volumeHandling;
+            mImmutableInfo = null;
+        }
+    }
+
+    public int getPresentationDisplayId() {
+        return mMutableInfo.presentationDisplayId;
+    }
+
+    public void setPresentationDisplayId(int presentationDisplayId) {
+        if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
+            mMutableInfo.presentationDisplayId = presentationDisplayId;
+            mImmutableInfo = null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "RemoteDisplay{" + mMutableInfo.toString() + "}";
+    }
+
+    RemoteDisplayInfo getInfo() {
+        if (mImmutableInfo == null) {
+            mImmutableInfo = new RemoteDisplayInfo(mMutableInfo);
+        }
+        return mImmutableInfo;
+    }
+}
diff --git a/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java
new file mode 100644
index 0000000..e2df77c
--- /dev/null
+++ b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.remotedisplay;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.IRemoteDisplayCallback;
+import android.media.IRemoteDisplayProvider;
+import android.media.RemoteDisplayState;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.ArrayMap;
+
+import java.util.Collection;
+
+/**
+ * Base class for remote display providers implemented as unbundled services.
+ * <p>
+ * To implement your remote display provider service, create a subclass of
+ * {@link Service} and override the {@link Service#onBind Service.onBind()} method
+ * to return the provider's binder when the {@link #SERVICE_INTERFACE} is requested.
+ * </p>
+ * <pre>
+ *   public class SampleRemoteDisplayProviderService extends Service {
+ *       private SampleProvider mProvider;
+ *
+ *       public IBinder onBind(Intent intent) {
+ *           if (intent.getAction().equals(RemoteDisplayProvider.SERVICE_INTERFACE)) {
+ *               if (mProvider == null) {
+ *                   mProvider = new SampleProvider(this);
+ *               }
+ *               return mProvider.getBinder();
+ *           }
+ *           return null;
+ *       }
+ *
+ *       class SampleProvider extends RemoteDisplayProvider {
+ *           public SampleProvider() {
+ *               super(SampleRemoteDisplayProviderService.this);
+ *           }
+ *
+ *           // --- Implementation goes here ---
+ *       }
+ *   }
+ * </pre>
+ * <p>
+ * Declare your remote display provider service in your application manifest
+ * like this:
+ * </p>
+ * <pre>
+ *   &lt;application>
+ *       &lt;uses-library android:name="com.android.media.remotedisplay" />
+ *
+ *       &lt;service android:name=".SampleRemoteDisplayProviderService"
+ *               android:label="@string/sample_remote_display_provider_service"
+ *               android:exported="true"
+ *               android:permission="android.permission.BIND_REMOTE_DISPLAY">
+ *           &lt;intent-filter>
+ *               &lt;action android:name="com.android.media.remotedisplay.RemoteDisplayProvider" />
+ *           &lt;/intent-filter>
+ *       &lt;/service>
+ *   &lt;/application>
+ * </pre>
+ * <p>
+ * This object is not thread safe.  It is only intended to be accessed on the
+ * {@link Context#getMainLooper main looper thread} of an application.
+ * </p><p>
+ * IMPORTANT: This class is effectively a public API for unbundled applications, and
+ * must remain API stable. See README.txt in the root of this package for more information.
+ * </p>
+ */
+public abstract class RemoteDisplayProvider {
+    private static final int MSG_SET_CALLBACK = 1;
+    private static final int MSG_SET_DISCOVERY_MODE = 2;
+    private static final int MSG_CONNECT = 3;
+    private static final int MSG_DISCONNECT = 4;
+    private static final int MSG_SET_VOLUME = 5;
+    private static final int MSG_ADJUST_VOLUME = 6;
+
+    private final Context mContext;
+    private final ProviderStub mStub;
+    private final ProviderHandler mHandler;
+    private final ArrayMap<String, RemoteDisplay> mDisplays =
+            new ArrayMap<String, RemoteDisplay>();
+    private IRemoteDisplayCallback mCallback;
+    private int mDiscoveryMode = DISCOVERY_MODE_NONE;
+
+    private PendingIntent mSettingsPendingIntent;
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     * Put this in your manifest.
+     */
+    public static final String SERVICE_INTERFACE = RemoteDisplayState.SERVICE_INTERFACE;
+
+    /**
+     * Discovery mode: Do not perform any discovery.
+     */
+    public static final int DISCOVERY_MODE_NONE = RemoteDisplayState.DISCOVERY_MODE_NONE;
+
+    /**
+     * Discovery mode: Passive or low-power periodic discovery.
+     * <p>
+     * This mode indicates that an application is interested in knowing whether there
+     * are any remote displays paired or available but doesn't need the latest or
+     * most detailed information.  The provider may scan at a lower rate or rely on
+     * knowledge of previously paired devices.
+     * </p>
+     */
+    public static final int DISCOVERY_MODE_PASSIVE = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
+
+    /**
+     * Discovery mode: Active discovery.
+     * <p>
+     * This mode indicates that the user is actively trying to connect to a route
+     * and we should perform continuous scans.  This mode may use significantly more
+     * power but is intended to be short-lived.
+     * </p>
+     */
+    public static final int DISCOVERY_MODE_ACTIVE = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
+
+    /**
+     * Creates a remote display provider.
+     *
+     * @param context The application context for the remote display provider.
+     */
+    public RemoteDisplayProvider(Context context) {
+        mContext = context;
+        mStub = new ProviderStub();
+        mHandler = new ProviderHandler(context.getMainLooper());
+    }
+
+    /**
+     * Gets the context of the remote display provider.
+     */
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Gets the Binder associated with the provider.
+     * <p>
+     * This is intended to be used for the onBind() method of a service that implements
+     * a remote display provider service.
+     * </p>
+     *
+     * @return The IBinder instance associated with the provider.
+     */
+    public IBinder getBinder() {
+        return mStub;
+    }
+
+    /**
+     * Called when the current discovery mode changes.
+     *
+     * @param mode The new discovery mode.
+     */
+    public void onDiscoveryModeChanged(int mode) {
+    }
+
+    /**
+     * Called when the system would like to connect to a display.
+     *
+     * @param display The remote display.
+     */
+    public void onConnect(RemoteDisplay display) {
+    }
+
+    /**
+     * Called when the system would like to disconnect from a display.
+     *
+     * @param display The remote display.
+     */
+    public void onDisconnect(RemoteDisplay display) {
+    }
+
+    /**
+     * Called when the system would like to set the volume of a display.
+     *
+     * @param display The remote display.
+     * @param volume The desired volume.
+     */
+    public void onSetVolume(RemoteDisplay display, int volume) {
+    }
+
+    /**
+     * Called when the system would like to adjust the volume of a display.
+     *
+     * @param display The remote display.
+     * @param delta An increment to add to the current volume, such as +1 or -1.
+     */
+    public void onAdjustVolume(RemoteDisplay display, int delta) {
+    }
+
+    /**
+     * Gets the current discovery mode.
+     *
+     * @return The current discovery mode.
+     */
+    public int getDiscoveryMode() {
+        return mDiscoveryMode;
+    }
+
+    /**
+     * Gets the current collection of displays.
+     *
+     * @return The current collection of displays, which must not be modified.
+     */
+    public Collection<RemoteDisplay> getDisplays() {
+        return mDisplays.values();
+    }
+
+    /**
+     * Adds the specified remote display and notifies the system.
+     *
+     * @param display The remote display that was added.
+     * @throws IllegalStateException if there is already a display with the same id.
+     */
+    public void addDisplay(RemoteDisplay display) {
+        if (display == null || mDisplays.containsKey(display.getId())) {
+            throw new IllegalArgumentException("display");
+        }
+        mDisplays.put(display.getId(), display);
+        publishState();
+    }
+
+    /**
+     * Updates information about the specified remote display and notifies the system.
+     *
+     * @param display The remote display that was added.
+     * @throws IllegalStateException if the display was n
+     */
+    public void updateDisplay(RemoteDisplay display) {
+        if (display == null || mDisplays.get(display.getId()) != display) {
+            throw new IllegalArgumentException("display");
+        }
+        publishState();
+    }
+
+    /**
+     * Removes the specified remote display and tells the system about it.
+     *
+     * @param display The remote display that was removed.
+     */
+    public void removeDisplay(RemoteDisplay display) {
+        if (display == null || mDisplays.get(display.getId()) != display) {
+            throw new IllegalArgumentException("display");
+        }
+        mDisplays.remove(display.getId());
+        publishState();
+    }
+
+    /**
+     * Finds the remote display with the specified id, returns null if not found.
+     *
+     * @param id Id of the remote display.
+     * @return The display, or null if none.
+     */
+    public RemoteDisplay findRemoteDisplay(String id) {
+        return mDisplays.get(id);
+    }
+
+    /**
+     * Gets a pending intent to launch the remote display settings activity.
+     *
+     * @return A pending intent to launch the settings activity.
+     */
+    public PendingIntent getSettingsPendingIntent() {
+        if (mSettingsPendingIntent == null) {
+            Intent settingsIntent = new Intent(Settings.ACTION_WIFI_DISPLAY_SETTINGS);
+            settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            mSettingsPendingIntent = PendingIntent.getActivity(
+                    mContext, 0, settingsIntent, 0, null);
+        }
+        return mSettingsPendingIntent;
+    }
+
+    void setCallback(IRemoteDisplayCallback callback) {
+        mCallback = callback;
+        publishState();
+    }
+
+    void setDiscoveryMode(int mode) {
+        if (mDiscoveryMode != mode) {
+            mDiscoveryMode = mode;
+            onDiscoveryModeChanged(mode);
+        }
+    }
+
+    void publishState() {
+        if (mCallback != null) {
+            RemoteDisplayState state = new RemoteDisplayState();
+            final int count = mDisplays.size();
+            for (int i = 0; i < count; i++) {
+                final RemoteDisplay display = mDisplays.valueAt(i);
+                state.displays.add(display.getInfo());
+            }
+            try {
+                mCallback.onStateChanged(state);
+            } catch (RemoteException ex) {
+                // system server died?
+            }
+        }
+    }
+
+    final class ProviderStub extends IRemoteDisplayProvider.Stub {
+        @Override
+        public void setCallback(IRemoteDisplayCallback callback) {
+            mHandler.obtainMessage(MSG_SET_CALLBACK, callback).sendToTarget();
+        }
+
+        @Override
+        public void setDiscoveryMode(int mode) {
+            mHandler.obtainMessage(MSG_SET_DISCOVERY_MODE, mode, 0).sendToTarget();
+        }
+
+        @Override
+        public void connect(String id) {
+            mHandler.obtainMessage(MSG_CONNECT, id).sendToTarget();
+        }
+
+        @Override
+        public void disconnect(String id) {
+            mHandler.obtainMessage(MSG_DISCONNECT, id).sendToTarget();
+        }
+
+        @Override
+        public void setVolume(String id, int volume) {
+            mHandler.obtainMessage(MSG_SET_VOLUME, volume, 0, id).sendToTarget();
+        }
+
+        @Override
+        public void adjustVolume(String id, int delta) {
+            mHandler.obtainMessage(MSG_ADJUST_VOLUME, delta, 0, id).sendToTarget();
+        }
+    }
+
+    final class ProviderHandler extends Handler {
+        public ProviderHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SET_CALLBACK: {
+                    setCallback((IRemoteDisplayCallback)msg.obj);
+                    break;
+                }
+                case MSG_SET_DISCOVERY_MODE: {
+                    setDiscoveryMode(msg.arg1);
+                    break;
+                }
+                case MSG_CONNECT: {
+                    RemoteDisplay display = findRemoteDisplay((String)msg.obj);
+                    if (display != null) {
+                        onConnect(display);
+                    }
+                    break;
+                }
+                case MSG_DISCONNECT: {
+                    RemoteDisplay display = findRemoteDisplay((String)msg.obj);
+                    if (display != null) {
+                        onDisconnect(display);
+                    }
+                    break;
+                }
+                case MSG_SET_VOLUME: {
+                    RemoteDisplay display = findRemoteDisplay((String)msg.obj);
+                    if (display != null) {
+                        onSetVolume(display, msg.arg1);
+                    }
+                    break;
+                }
+                case MSG_ADJUST_VOLUME: {
+                    RemoteDisplay display = findRemoteDisplay((String)msg.obj);
+                    if (display != null) {
+                        onAdjustVolume(display, msg.arg1);
+                    }
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
index 6424744..53f04bc 100644
--- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
+++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
@@ -30,6 +30,7 @@
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/OMXClient.h>
 #include <media/stagefright/OMXCodec.h>
+#include <SkImage.h>
 #include <SkMallocPixelRef.h>
 
 #include "omx_jpeg_decoder.h"
@@ -184,8 +185,7 @@
 
 void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkBitmap::Config pref,
         int width, int height) {
-    bm->setConfig(getColorSpaceConfig(pref), width, height);
-    bm->setIsOpaque(true);
+    bm->setConfig(getColorSpaceConfig(pref), width, height, 0, kOpaque_SkAlphaType);
 }
 
 SkBitmap::Config OmxJpegImageDecoder::getColorSpaceConfig(
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
index 55d73f2..1cbc221 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
@@ -51,7 +51,7 @@
         mPosition = new int[count];
 
         cursor.moveToPosition(-1);
-        while (cursor.moveToNext()) {
+        while (cursor.moveToNext() && mCount < count) {
             final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
             final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
             if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index 3a8a3fb..34ce42d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -55,6 +55,10 @@
 public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
     private static final boolean LOGD = true;
 
+    // TODO: clean up cursor ownership so background thread doesn't traverse
+    // previously returned cursors for filtering/sorting; this currently races
+    // with the UI thread.
+
     private static final int MAX_OUTSTANDING_RECENTS = 4;
     private static final int MAX_OUTSTANDING_RECENTS_SVELTE = 2;
 
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml
index 5169fef..edd6255 100644
--- a/packages/ExternalStorageProvider/AndroidManifest.xml
+++ b/packages/ExternalStorageProvider/AndroidManifest.xml
@@ -3,6 +3,7 @@
 
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
 
     <application android:label="@string/app_label">
         <provider
diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml
index 9e296e2..66d1e75 100644
--- a/packages/Keyguard/AndroidManifest.xml
+++ b/packages/Keyguard/AndroidManifest.xml
@@ -38,6 +38,7 @@
     <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
     <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
+    <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
 
     <application android:label="@string/app_name"
         android:process="com.android.systemui"
diff --git a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml
index 8be15cb..b4847f0 100644
--- a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml
@@ -32,12 +32,10 @@
         android:id="@+id/carrier_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:singleLine="true"
         android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/kg_status_line_font_size"
-        android:textColor="?android:attr/textColorSecondary"
-        android:textAllCaps="@bool/kg_use_all_caps" />
+        android:textColor="?android:attr/textColorSecondary" />
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/packages/Keyguard/res/layout/keyguard_presentation.xml b/packages/Keyguard/res/layout/keyguard_presentation.xml
new file mode 100644
index 0000000..7df0b70
--- /dev/null
+++ b/packages/Keyguard/res/layout/keyguard_presentation.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is a view that shows general status information in Keyguard. -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res/com.android.keyguard"
+    android:id="@+id/presentation"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.android.keyguard.KeyguardStatusView
+        android:id="@+id/clock"
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:contentDescription="@string/keyguard_accessibility_status">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal|top"
+            android:orientation="vertical"
+            android:focusable="true">
+            <TextClock
+                android:id="@+id/clock_view"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal|top"
+                android:textColor="@color/clock_white"
+                android:singleLine="true"
+                style="@style/widget_big_thin"
+                android:format12Hour="@string/keyguard_widget_12_hours_format"
+                android:format24Hour="@string/keyguard_widget_24_hours_format"
+                android:baselineAligned="true"
+                android:layout_marginBottom="@dimen/bottom_text_spacing_digital" />
+
+            <include layout="@layout/keyguard_status_area" />
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dip"
+                android:layout_gravity="center_horizontal"
+                android:src="@drawable/kg_security_lock_normal" />
+        </LinearLayout>
+    </com.android.keyguard.KeyguardStatusView>
+
+</FrameLayout>
diff --git a/packages/Keyguard/res/layout/keyguard_status_view.xml b/packages/Keyguard/res/layout/keyguard_status_view.xml
index 5857fc2..a4d298a 100644
--- a/packages/Keyguard/res/layout/keyguard_status_view.xml
+++ b/packages/Keyguard/res/layout/keyguard_status_view.xml
@@ -26,7 +26,7 @@
     android:layout_height="match_parent"
     androidprv:layout_maxWidth="@dimen/keyguard_security_width"
     androidprv:layout_maxHeight="@dimen/keyguard_security_height"
-    android:gravity="center_horizontal">
+    android:gravity="center">
 
     <com.android.keyguard.KeyguardStatusView
         android:id="@+id/keyguard_status_view_face_palm"
diff --git a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java
index 7d1f24f..74e6f33 100644
--- a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java
+++ b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.Color;
-import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -41,7 +40,7 @@
     private static final String TAG = CameraWidgetFrame.class.getSimpleName();
     private static final boolean DEBUG = KeyguardHostView.DEBUG;
     private static final int WIDGET_ANIMATION_DURATION = 250; // ms
-    private static final int WIDGET_WAIT_DURATION = 650; // ms
+    private static final int WIDGET_WAIT_DURATION = 400; // ms
     private static final int RECOVERY_DELAY = 1000; // ms
 
     interface Callbacks {
@@ -68,6 +67,7 @@
     private FixedSizeFrameLayout mPreview;
     private View mFullscreenPreview;
     private View mFakeNavBar;
+    private boolean mUseFastTransition;
 
     private final Runnable mTransitionToCameraRunnable = new Runnable() {
         @Override
@@ -243,11 +243,12 @@
         final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0;
         final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0;
 
-        mPreview.setPivotX(0);
+        final boolean isRtl = mPreview.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        mPreview.setPivotX(isRtl ? mPreview.width : 0);
         mPreview.setPivotY(0);
         mPreview.setScaleX(pvScale);
         mPreview.setScaleY(pvScale);
-        mPreview.setTranslationX(pvTransX);
+        mPreview.setTranslationX((isRtl ? -1 : 1) * pvTransX);
         mPreview.setTranslationY(pvTransY);
 
         mRenderedSize.set(width, height);
@@ -417,7 +418,8 @@
     private void rescheduleTransitionToCamera() {
         if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis());
         mHandler.removeCallbacks(mTransitionToCameraRunnable);
-        mHandler.postDelayed(mTransitionToCameraRunnable, WIDGET_WAIT_DURATION);
+        final long duration = mUseFastTransition ? 0 : WIDGET_WAIT_DURATION;
+        mHandler.postDelayed(mTransitionToCameraRunnable, duration);
     }
 
     private void cancelTransitionToCamera() {
@@ -512,4 +514,8 @@
         if (DEBUG) Log.d(TAG, "setInsets: " + insets);
         mInsets.set(insets);
     }
+
+    public void setUseFastTransition(boolean useFastTransition) {
+        mUseFastTransition = useFastTransition;
+    }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/CarrierText.java b/packages/Keyguard/src/com/android/keyguard/CarrierText.java
index c33f174..88558cd 100644
--- a/packages/Keyguard/src/com/android/keyguard/CarrierText.java
+++ b/packages/Keyguard/src/com/android/keyguard/CarrierText.java
@@ -17,14 +17,18 @@
 package com.android.keyguard;
 
 import android.content.Context;
+import android.text.method.SingleLineTransformationMethod;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.TextView;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.widget.LockPatternUtils;
 
+import java.util.Locale;
+
 public class CarrierText extends TextView {
     private static CharSequence mSeparator;
 
@@ -77,6 +81,8 @@
     public CarrierText(Context context, AttributeSet attrs) {
         super(context, attrs);
         mLockPatternUtils = new LockPatternUtils(mContext);
+        boolean useAllCaps = mContext.getResources().getBoolean(R.bool.kg_use_all_caps);
+        setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
     }
 
     protected void updateCarrierText(State simState, CharSequence plmn, CharSequence spn) {
@@ -258,4 +264,25 @@
 
         return mContext.getText(carrierHelpTextId);
     }
+
+    private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
+        private final Locale mLocale;
+        private final boolean mAllCaps;
+
+        public CarrierTextTransformationMethod(Context context, boolean allCaps) {
+            mLocale = context.getResources().getConfiguration().locale;
+            mAllCaps = allCaps;
+        }
+
+        @Override
+        public CharSequence getTransformation(CharSequence source, View view) {
+            source = super.getTransformation(source, view);
+
+            if (mAllCaps && source != null) {
+                source = source.toString().toUpperCase(mLocale);
+            }
+
+            return source;
+        }
+    }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java
index 677f1f1..2ee21ac 100644
--- a/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java
+++ b/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java
@@ -39,7 +39,7 @@
      *
      * @param b true to show, false to hide
      */
-    void showChallenge(boolean b);
+    void showChallenge(boolean show);
 
     /**
      * Show the bouncer challenge. This may block access to other child views.
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java b/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java
index 9a1aa5b..0a915ea 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java
@@ -99,6 +99,11 @@
 
     public void launchCamera(Handler worker, Runnable onSecureCameraStarted) {
         LockPatternUtils lockPatternUtils = getLockPatternUtils();
+
+        // Workaround to avoid camera release/acquisition race when resuming face unlock
+        // after showing lockscreen camera (bug 11063890).
+        KeyguardUpdateMonitor.getInstance(getContext()).setAlternateUnlockEnabled(false);
+
         if (lockPatternUtils.isSecure()) {
             // Launch the secure version of the camera
             if (wouldLaunchResolverActivity(SECURE_CAMERA_INTENT)) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java
new file mode 100644
index 0000000..6bcbd6c
--- /dev/null
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.graphics.Point;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteInfo;
+import android.os.Bundle;
+import android.util.Slog;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+
+public class KeyguardDisplayManager {
+    protected static final String TAG = "KeyguardDisplayManager";
+    private static boolean DEBUG = KeyguardViewMediator.DEBUG;
+    Presentation mPresentation;
+    private MediaRouter mMediaRouter;
+    private Context mContext;
+    private boolean mShowing;
+
+    KeyguardDisplayManager(Context context) {
+        mContext = context;
+        mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+    }
+
+    void show() {
+        if (!mShowing) {
+            if (DEBUG) Slog.v(TAG, "show");
+            mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+                    mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+            updateDisplays(true);
+        }
+        mShowing = true;
+    }
+
+    void hide() {
+        if (mShowing) {
+            if (DEBUG) Slog.v(TAG, "hide");
+            mMediaRouter.removeCallback(mMediaRouterCallback);
+            updateDisplays(false);
+        }
+        mShowing = false;
+    }
+
+    private final MediaRouter.SimpleCallback mMediaRouterCallback =
+            new MediaRouter.SimpleCallback() {
+        @Override
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+            if (DEBUG) Slog.d(TAG, "onRouteSelected: type=" + type + ", info=" + info);
+            updateDisplays(mShowing);
+        }
+
+        @Override
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+            if (DEBUG) Slog.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info);
+            updateDisplays(mShowing);
+        }
+
+        @Override
+        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
+            if (DEBUG) Slog.d(TAG, "onRoutePresentationDisplayChanged: info=" + info);
+            updateDisplays(mShowing);
+        }
+    };
+
+    private OnDismissListener mOnDismissListener = new OnDismissListener() {
+
+        @Override
+        public void onDismiss(DialogInterface dialog) {
+            mPresentation = null;
+        }
+    };
+
+    protected void updateDisplays(boolean showing) {
+        if (showing) {
+            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(
+                    MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
+            boolean useDisplay = route != null
+                    && route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+            Display presentationDisplay = useDisplay ? route.getPresentationDisplay() : null;
+
+            if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
+                if (DEBUG) Slog.v(TAG, "Display gone: " + mPresentation.getDisplay());
+                mPresentation.dismiss();
+                mPresentation = null;
+            }
+
+            if (mPresentation == null && presentationDisplay != null) {
+                if (DEBUG) Slog.i(TAG, "Keyguard enabled on display: " + presentationDisplay);
+                mPresentation = new KeyguardPresentation(mContext, presentationDisplay);
+                mPresentation.setOnDismissListener(mOnDismissListener);
+                try {
+                    mPresentation.show();
+                } catch (WindowManager.InvalidDisplayException ex) {
+                    Slog.w(TAG, "Invalid display:", ex);
+                    mPresentation = null;
+                }
+            }
+        } else {
+            if (mPresentation != null) {
+                mPresentation.dismiss();
+                mPresentation = null;
+            }
+        }
+    }
+
+    private final static class KeyguardPresentation extends Presentation {
+        private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
+        private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
+        private View mClock;
+        private int mUsableWidth;
+        private int mUsableHeight;
+        private int mMarginTop;
+        private int mMarginLeft;
+        Runnable mMoveTextRunnable = new Runnable() {
+            @Override
+            public void run() {
+                int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth()));
+                int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight()));
+                mClock.setTranslationX(x);
+                mClock.setTranslationY(y);
+                mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT);
+            }
+        };
+
+        public KeyguardPresentation(Context context, Display display) {
+            super(context, display);
+            getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+        }
+
+        public void onDetachedFromWindow() {
+            mClock.removeCallbacks(mMoveTextRunnable);
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            Point p = new Point();
+            getDisplay().getSize(p);
+            mUsableWidth = VIDEO_SAFE_REGION * p.x/100;
+            mUsableHeight = VIDEO_SAFE_REGION * p.y/100;
+            mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200;
+            mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200;
+
+            setContentView(R.layout.keyguard_presentation);
+            mClock = findViewById(R.id.clock);
+
+            // Avoid screen burn in
+            mClock.post(mMoveTextRunnable);
+        }
+    }
+}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index fdc06a6..247089f 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
@@ -218,7 +218,7 @@
         mTransportState = (dcs.clearing ? TRANSPORT_GONE :
             (isMusicPlaying(dcs.playbackState) ? TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE));
 
-        if (DEBUG) Log.v(TAG, "Initial transport state: "
+        if (DEBUGXPORT) Log.v(TAG, "Initial transport state: "
                 + mTransportState + ", pbstate=" + dcs.playbackState);
     }
 
@@ -1369,7 +1369,7 @@
         }
     }
 
-    Runnable mSwitchPageRunnable = new Runnable() {
+    private final Runnable mSwitchPageRunnable = new Runnable() {
         @Override
         public void run() {
            showAppropriateWidgetPage();
@@ -1438,7 +1438,7 @@
         mAppWidgetToShow = ss.appWidgetToShow;
         setInsets(ss.insets);
         if (DEBUG) Log.d(TAG, "onRestoreInstanceState, transport=" + mTransportState);
-        post(mSwitchPageRunnable);
+        mSwitchPageRunnable.run();
     }
 
     @Override
@@ -1472,10 +1472,21 @@
     }
 
     private void showAppropriateWidgetPage() {
-        int state = mTransportState;
-        ensureTransportPresentOrRemoved(state);
-        int pageToShow = getAppropriateWidgetPage(state);
-        mAppWidgetContainer.setCurrentPage(pageToShow);
+        final int state = mTransportState;
+        final boolean transportAdded = ensureTransportPresentOrRemoved(state);
+        final int pageToShow = getAppropriateWidgetPage(state);
+        if (!transportAdded) {
+            mAppWidgetContainer.setCurrentPage(pageToShow);
+        } else if (state == TRANSPORT_VISIBLE) {
+            // If the transport was just added, we need to wait for layout to happen before
+            // we can set the current page.
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    mAppWidgetContainer.setCurrentPage(pageToShow);
+                }
+            });
+        }
     }
 
     /**
@@ -1499,12 +1510,11 @@
      *
      * @param state
      */
-    private void ensureTransportPresentOrRemoved(int state) {
+    private boolean ensureTransportPresentOrRemoved(int state) {
         final boolean showing = getWidgetPosition(R.id.keyguard_transport_control) != -1;
         final boolean visible = state == TRANSPORT_VISIBLE;
         final boolean shouldBeVisible = state == TRANSPORT_INVISIBLE && isMusicPlaying(state);
         if (!showing && (visible || shouldBeVisible)) {
-            if (DEBUGXPORT) Log.v(TAG, "add transport");
             // insert to left of camera if it exists, otherwise after right-most widget
             int lastWidget = mAppWidgetContainer.getChildCount() - 1;
             int position = 0; // handle no widget case
@@ -1512,13 +1522,16 @@
                 position = mAppWidgetContainer.isCameraPage(lastWidget) ?
                         lastWidget : lastWidget + 1;
             }
+            if (DEBUGXPORT) Log.v(TAG, "add transport at " + position);
             mAppWidgetContainer.addWidget(getOrCreateTransportControl(), position);
+            return true;
         } else if (showing && state == TRANSPORT_GONE) {
             if (DEBUGXPORT) Log.v(TAG, "remove transport");
             mAppWidgetContainer.removeWidget(getOrCreateTransportControl());
             mTransportControl = null;
             KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(null);
         }
+        return false;
     }
 
     private CameraWidgetFrame findCameraPage() {
@@ -1642,6 +1655,7 @@
             KeyguardWidgetFrame frame = mAppWidgetContainer.getWidgetPageAt(i);
             frame.removeAllViews();
         }
+        mSecurityViewContainer.onPause(); // clean up any actions in progress
     }
 
     /**
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
index 69075ec..751572c 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
@@ -177,6 +177,7 @@
 
     public KeyguardMessageArea(Context context, AttributeSet attrs) {
         super(context, attrs);
+        setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
 
         mLockPatternUtils = new LockPatternUtils(context);
 
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index e7f1259..3b72f43 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -58,8 +58,8 @@
     // how many cells the user has to cross before we poke the wakelock
     private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
 
-    private int mFailedPatternAttemptsSinceLastTimeout = 0;
-    private int mTotalFailedPatternAttempts = 0;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
     private CountDownTimer mCountdownTimer = null;
     private LockPatternUtils mLockPatternUtils;
     private LockPatternView mLockPatternView;
@@ -100,6 +100,7 @@
 
     public KeyguardPatternView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
     }
 
     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
@@ -202,7 +203,8 @@
         if (mCallback.isVerifyUnlockOnly()) {
             updateFooter(FooterMode.VerifyUnlocked);
         } else if (mEnableFallback &&
-                (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
+                (mKeyguardUpdateMonitor.getFailedUnlockAttempts()
+                        >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
             updateFooter(FooterMode.ForgotLockPattern);
         } else {
             updateFooter(FooterMode.Normal);
@@ -211,7 +213,7 @@
     }
 
     private void displayDefaultSecurityMessage() {
-        if (KeyguardUpdateMonitor.getInstance(mContext).getMaxBiometricUnlockAttemptsReached()) {
+        if (mKeyguardUpdateMonitor.getMaxBiometricUnlockAttemptsReached()) {
             mSecurityMessageDisplay.setMessage(R.string.faceunlock_multiple_failures, true);
         } else {
             mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false);
@@ -262,20 +264,20 @@
             if (mLockPatternUtils.checkPattern(pattern)) {
                 mCallback.reportSuccessfulUnlockAttempt();
                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
-                mTotalFailedPatternAttempts = 0;
                 mCallback.dismiss(true);
             } else {
                 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
                     mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
                 }
                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
-                if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
-                    mTotalFailedPatternAttempts++;
-                    mFailedPatternAttemptsSinceLastTimeout++;
+                boolean registeredAttempt =
+                        pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL;
+                if (registeredAttempt) {
                     mCallback.reportFailedUnlockAttempt();
                 }
-                if (mFailedPatternAttemptsSinceLastTimeout
-                        >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
+                int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts();
+                if (registeredAttempt &&
+                        0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
                     handleAttemptLockout(deadline);
                 } else {
@@ -363,7 +365,6 @@
                 mLockPatternView.setEnabled(true);
                 displayDefaultSecurityMessage();
                 // TODO mUnlockIcon.setVisibility(View.VISIBLE);
-                mFailedPatternAttemptsSinceLastTimeout = 0;
                 if (mEnableFallback) {
                     updateFooter(FooterMode.ForgotLockPattern);
                 } else {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java
index 8ccd6fe..36b2446 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java
@@ -68,8 +68,6 @@
     }
 
     private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {
-        private boolean mSetHiddenCalled;
-        private boolean mIsHidden;
         public boolean isShowing() {
             return mKeyguardViewMediator.isShowing();
         }
@@ -91,10 +89,7 @@
         }
         public void setHidden(boolean isHidden) {
             checkPermission();
-            if (mSetHiddenCalled && mIsHidden == isHidden) return;
             mKeyguardViewMediator.setHidden(isHidden);
-            mSetHiddenCalled = true;
-            mIsHidden = isHidden;
         }
         public void dismiss() {
             mKeyguardViewMediator.dismiss();
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
index e39622a..9accbb4 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
@@ -46,9 +46,10 @@
         implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
     private static final String LOG_TAG = "KeyguardSimPinView";
     private static final boolean DEBUG = KeyguardViewMediator.DEBUG;
+    public static final String TAG = "KeyguardSimPinView";
 
     private ProgressDialog mSimUnlockProgressDialog = null;
-    private volatile boolean mSimCheckInProgress;
+    private CheckSimPin mCheckSimPinThread;
 
     private AlertDialog mRemainingAttemptsDialog;
 
@@ -169,14 +170,17 @@
         @Override
         public void run() {
             try {
+                Log.v(TAG, "call supplyPinReportResult()");
                 final int[] result = ITelephony.Stub.asInterface(ServiceManager
                         .checkService("phone")).supplyPinReportResult(mPin);
+                Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]);
                 post(new Runnable() {
                     public void run() {
                         onSimCheckResponse(result[0], result[1]);
                     }
                 });
             } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException for supplyPinReportResult:", e);
                 post(new Runnable() {
                     public void run() {
                         onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1);
@@ -229,9 +233,8 @@
 
         getSimUnlockProgressDialog().show();
 
-        if (!mSimCheckInProgress) {
-            mSimCheckInProgress = true; // there should be only one
-            new CheckSimPin(mPasswordEntry.getText().toString()) {
+        if (mCheckSimPinThread == null) {
+            mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText().toString()) {
                 void onSimCheckResponse(final int result, final int attemptsRemaining) {
                     post(new Runnable() {
                         public void run() {
@@ -263,11 +266,12 @@
                                 mPasswordEntry.setText("");
                             }
                             mCallback.userActivity(0);
-                            mSimCheckInProgress = false;
+                            mCheckSimPinThread = null;
                         }
                     });
                 }
-            }.start();
+            };
+            mCheckSimPinThread.start();
         }
     }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
index 31518a1..6e9e83e 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
@@ -45,9 +45,10 @@
         implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
     private static final String LOG_TAG = "KeyguardSimPukView";
     private static final boolean DEBUG = KeyguardViewMediator.DEBUG;
+    public static final String TAG = "KeyguardSimPukView";
 
     private ProgressDialog mSimUnlockProgressDialog = null;
-    private volatile boolean mCheckInProgress;
+    private CheckSimPuk mCheckSimPukThread;
     private String mPukText;
     private String mPinText;
     private StateMachine mStateMachine = new StateMachine();
@@ -220,15 +221,17 @@
         @Override
         public void run() {
             try {
+                Log.v(TAG, "call supplyPukReportResult()");
                 final int[] result = ITelephony.Stub.asInterface(ServiceManager
                         .checkService("phone")).supplyPukReportResult(mPuk, mPin);
-
+                Log.v(TAG, "supplyPukReportResult returned: " + result[0] + " " + result[1]);
                 post(new Runnable() {
                     public void run() {
                         onSimLockChangedResponse(result[0], result[1]);
                     }
                 });
             } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException for supplyPukReportResult:", e);
                 post(new Runnable() {
                     public void run() {
                         onSimLockChangedResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1);
@@ -295,9 +298,8 @@
     private void updateSim() {
         getSimUnlockProgressDialog().show();
 
-        if (!mCheckInProgress) {
-            mCheckInProgress = true;
-            new CheckSimPuk(mPukText, mPinText) {
+        if (mCheckSimPukThread == null) {
+            mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText) {
                 void onSimLockChangedResponse(final int result, final int attemptsRemaining) {
                     post(new Runnable() {
                         public void run() {
@@ -326,11 +328,12 @@
                                         + " attemptsRemaining=" + attemptsRemaining);
                                 mStateMachine.reset();
                             }
-                            mCheckInProgress = false;
+                            mCheckSimPukThread = null;
                         }
                     });
                 }
-            }.start();
+            };
+            mCheckSimPukThread.start();
         }
     }
 
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index d933275..0bfee38 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -98,26 +98,13 @@
     }
 
     protected void refresh() {
-        Resources res = mContext.getResources();
-        Locale locale = Locale.getDefault();
-        final String dateFormat = DateFormat.getBestDateTimePattern(locale,
-                res.getString(R.string.abbrev_wday_month_day_no_year));
+        Patterns.update(mContext);
 
-        mDateView.setFormat24Hour(dateFormat);
-        mDateView.setFormat12Hour(dateFormat);
+        mDateView.setFormat24Hour(Patterns.dateView);
+        mDateView.setFormat12Hour(Patterns.dateView);
 
-        // 12-hour clock.
-        // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
-        // format.  The following code removes the AM/PM indicator if we didn't want it.
-        final String clock12skel = res.getString(R.string.clock_12hr_format);
-        String clock12hr = DateFormat.getBestDateTimePattern(locale, clock12skel);
-        clock12hr = clock12skel.contains("a") ? clock12hr : clock12hr.replaceAll("a", "").trim();
-        mClockView.setFormat12Hour(clock12hr);
-
-        // 24-hour clock
-        final String clock24skel = res.getString(R.string.clock_24hr_format);
-        final String clock24hr = DateFormat.getBestDateTimePattern(locale, clock24skel);
-        mClockView.setFormat24Hour(clock24hr);
+        mClockView.setFormat12Hour(Patterns.clockView12);
+        mClockView.setFormat24Hour(Patterns.clockView24);
 
         refreshAlarmStatus();
     }
@@ -149,4 +136,35 @@
         return LockPatternUtils.ID_DEFAULT_STATUS_WIDGET;
     }
 
+    // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
+    // This is an optimization to ensure we only recompute the patterns when the inputs change.
+    private static final class Patterns {
+        static String dateView;
+        static String clockView12;
+        static String clockView24;
+        static String cacheKey;
+
+        static void update(Context context) {
+            final Locale locale = Locale.getDefault();
+            final Resources res = context.getResources();
+            final String dateViewSkel = res.getString(R.string.abbrev_wday_month_day_no_year);
+            final String clockView12Skel = res.getString(R.string.clock_12hr_format);
+            final String clockView24Skel = res.getString(R.string.clock_24hr_format);
+            final String key = locale.toString() + dateViewSkel + clockView12Skel + clockView24Skel;
+            if (key.equals(cacheKey)) return;
+
+            dateView = DateFormat.getBestDateTimePattern(locale, dateViewSkel);
+
+            clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
+            // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
+            // format.  The following code removes the AM/PM indicator if we didn't want it.
+            if (!clockView12Skel.contains("a")) {
+                clockView12 = clockView12.replaceAll("a", "").trim();
+            }
+
+            clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
+
+            cacheKey = key;
+        }
+    }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
index 29ba60d..349078f 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
@@ -60,12 +60,12 @@
  */
 public class KeyguardTransportControlView extends FrameLayout {
 
-    private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s
     private static final int RESET_TO_METADATA_DELAY = 5000;
     protected static final boolean DEBUG = false;
     protected static final String TAG = "TransportControlView";
 
     private static final boolean ANIMATE_TRANSITIONS = true;
+    protected static final long QUIESCENT_PLAYBACK_FACTOR = 1000;
 
     private ViewGroup mMetadataContainer;
     private ViewGroup mInfoContainer;
@@ -89,11 +89,9 @@
     private ImageView mBadge;
 
     private boolean mSeekEnabled;
-    private boolean mUserSeeking;
     private java.text.DateFormat mFormat;
 
-    private Date mTimeElapsed;
-    private Date mTrackDuration;
+    private Date mTempDate = new Date();
 
     /**
      * The metadata which should be populated into the view once we've been attached
@@ -104,23 +102,32 @@
             new RemoteController.OnClientUpdateListener() {
         @Override
         public void onClientChange(boolean clearing) {
-            clearMetadata();
+            if (clearing) {
+                clearMetadata();
+            }
         }
 
         @Override
         public void onClientPlaybackStateUpdate(int state) {
-            setSeekBarsEnabled(false);
             updatePlayPauseState(state);
         }
 
         @Override
         public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
                 long currentPosMs, float speed) {
-            setSeekBarsEnabled(mMetadata != null && mMetadata.duration > 0);
             updatePlayPauseState(state);
             if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state +
                     ", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs +
                     ", speed=" + speed + ")");
+
+            removeCallbacks(mUpdateSeekBars);
+            // Since the music client may be responding to historical events that cause the
+            // playback state to change dramatically, wait until things become quiescent before
+            // resuming automatic scrub position update.
+            if (mTransientSeek.getVisibility() == View.VISIBLE
+                    && playbackPositionShouldMove(mCurrentPlayState)) {
+                postDelayed(mUpdateSeekBars, QUIESCENT_PLAYBACK_FACTOR);
+            }
         }
 
         @Override
@@ -134,15 +141,21 @@
         }
     };
 
-    private final Runnable mUpdateSeekBars = new Runnable() {
+    private class UpdateSeekBarRunnable implements  Runnable {
         public void run() {
-            if (updateSeekBars()) {
+            boolean seekAble = updateOnce();
+            if (seekAble) {
                 removeCallbacks(this);
                 postDelayed(this, 1000);
             }
         }
+        public boolean updateOnce() {
+            return updateSeekBars();
+        }
     };
 
+    private final UpdateSeekBarRunnable mUpdateSeekBars = new UpdateSeekBarRunnable();
+
     private final Runnable mResetToMetadata = new Runnable() {
         public void run() {
             resetToMetadata();
@@ -161,6 +174,7 @@
             }
             if (keyCode != -1) {
                 sendMediaButtonClick(keyCode);
+                delayResetToMetadata(); // if the scrub bar is showing, keep showing it.
             }
         }
     };
@@ -175,25 +189,67 @@
         }
     };
 
+    // This class is here to throttle scrub position updates to the music client
+    class FutureSeekRunnable implements Runnable {
+        private int mProgress;
+        private boolean mPending;
+
+        public void run() {
+            scrubTo(mProgress);
+            mPending = false;
+        }
+
+        void setProgress(int progress) {
+            mProgress = progress;
+            if (!mPending) {
+                mPending = true;
+                postDelayed(this, 30);
+            }
+        }
+    };
+
+    // This is here because RemoteControlClient's method isn't visible :/
+    private final static boolean playbackPositionShouldMove(int playstate) {
+        switch(playstate) {
+            case RemoteControlClient.PLAYSTATE_STOPPED:
+            case RemoteControlClient.PLAYSTATE_PAUSED:
+            case RemoteControlClient.PLAYSTATE_BUFFERING:
+            case RemoteControlClient.PLAYSTATE_ERROR:
+            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+                return false;
+            case RemoteControlClient.PLAYSTATE_PLAYING:
+            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
+            case RemoteControlClient.PLAYSTATE_REWINDING:
+            default:
+                return true;
+        }
+    }
+
+    private final FutureSeekRunnable mFutureSeekRunnable = new FutureSeekRunnable();
+
     private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
             new SeekBar.OnSeekBarChangeListener() {
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
             if (fromUser) {
-                scrubTo(progress);
+                mFutureSeekRunnable.setProgress(progress);
                 delayResetToMetadata();
+                mTempDate.setTime(progress);
+                mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
+            } else {
+                updateSeekDisplay();
             }
-            updateSeekDisplay();
         }
 
         @Override
         public void onStartTrackingTouch(SeekBar seekBar) {
-            mUserSeeking = true;
+            delayResetToMetadata();
+            removeCallbacks(mUpdateSeekBars); // don't update during user interaction
         }
 
         @Override
         public void onStopTrackingTouch(SeekBar seekBar) {
-            mUserSeeking = false;
         }
     };
 
@@ -206,10 +262,10 @@
             = new KeyguardUpdateMonitorCallback() {
         public void onScreenTurnedOff(int why) {
             setEnableMarquee(false);
-        };
+        }
         public void onScreenTurnedOn() {
             setEnableMarquee(true);
-        };
+        }
     };
 
     public KeyguardTransportControlView(Context context, AttributeSet attrs) {
@@ -245,17 +301,11 @@
         if (enabled == mSeekEnabled) return;
 
         mSeekEnabled = enabled;
-        if (mTransientSeek.getVisibility() == VISIBLE) {
+        if (mTransientSeek.getVisibility() == VISIBLE && !enabled) {
             mTransientSeek.setVisibility(INVISIBLE);
             mMetadataContainer.setVisibility(VISIBLE);
-            mUserSeeking = false;
             cancelResetToMetadata();
         }
-        if (enabled) {
-            mUpdateSeekBars.run();
-        } else {
-            removeCallbacks(mUpdateSeekBars);
-        }
     }
 
     public void setTransportControlCallback(KeyguardHostView.TransportControlCallback
@@ -292,6 +342,8 @@
         }
         final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
         setEnableMarquee(screenOn);
+        // Allow long-press anywhere else in this view to show the seek bar
+        setOnLongClickListener(mTransportShowSeekBarListener);
     }
 
     @Override
@@ -324,10 +376,36 @@
         mAudioManager.unregisterRemoteController(mRemoteController);
         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor);
         mMetadata.clear();
-        mUserSeeking = false;
         removeCallbacks(mUpdateSeekBars);
     }
 
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        SavedState ss = new SavedState(super.onSaveInstanceState());
+        ss.artist = mMetadata.artist;
+        ss.trackTitle = mMetadata.trackTitle;
+        ss.albumTitle = mMetadata.albumTitle;
+        ss.duration = mMetadata.duration;
+        ss.bitmap = mMetadata.bitmap;
+        return ss;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        mMetadata.artist = ss.artist;
+        mMetadata.trackTitle = ss.trackTitle;
+        mMetadata.albumTitle = ss.albumTitle;
+        mMetadata.duration = ss.duration;
+        mMetadata.bitmap = ss.bitmap;
+        populateMetadata();
+    }
+
     void setBadgeIcon(Drawable bmp) {
         mBadge.setImageDrawable(bmp);
 
@@ -455,18 +533,12 @@
 
     void updateSeekDisplay() {
         if (mMetadata != null && mRemoteController != null && mFormat != null) {
-            if (mTimeElapsed == null) {
-                mTimeElapsed = new Date();
-            }
-            if (mTrackDuration == null) {
-                mTrackDuration = new Date();
-            }
-            mTimeElapsed.setTime(mRemoteController.getEstimatedMediaPosition());
-            mTrackDuration.setTime(mMetadata.duration);
-            mTransientSeekTimeElapsed.setText(mFormat.format(mTimeElapsed));
-            mTransientSeekTimeTotal.setText(mFormat.format(mTrackDuration));
+            mTempDate.setTime(mRemoteController.getEstimatedMediaPosition());
+            mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
+            mTempDate.setTime(mMetadata.duration);
+            mTransientSeekTimeTotal.setText(mFormat.format(mTempDate));
 
-            if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTimeElapsed +
+            if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTempDate +
                     " duration=" + mMetadata.duration);
         }
     }
@@ -479,10 +551,16 @@
             mTransientSeek.setVisibility(INVISIBLE);
             mMetadataContainer.setVisibility(VISIBLE);
             cancelResetToMetadata();
+            removeCallbacks(mUpdateSeekBars); // don't update if scrubber isn't visible
         } else {
             mTransientSeek.setVisibility(VISIBLE);
             mMetadataContainer.setVisibility(INVISIBLE);
             delayResetToMetadata();
+            if (playbackPositionShouldMove(mCurrentPlayState)) {
+                mUpdateSeekBars.run();
+            } else {
+                mUpdateSeekBars.updateOnce();
+            }
         }
         mTransportControlCallback.userActivity();
         return true;
@@ -544,9 +622,6 @@
             case RemoteControlClient.PLAYSTATE_PLAYING:
                 imageResId = R.drawable.ic_media_pause;
                 imageDescId = R.string.keyguard_transport_pause_description;
-                if (mSeekEnabled) {
-                    mUpdateSeekBars.run();
-                }
                 break;
 
             case RemoteControlClient.PLAYSTATE_BUFFERING:
@@ -561,10 +636,9 @@
                 break;
         }
 
-        if (state != RemoteControlClient.PLAYSTATE_PLAYING) {
-            removeCallbacks(mUpdateSeekBars);
-            updateSeekBars();
-        }
+        boolean clientSupportsSeek = mMetadata != null && mMetadata.duration > 0;
+        setSeekBarsEnabled(clientSupportsSeek);
+
         mBtnPlay.setImageResource(imageResId);
         mBtnPlay.setContentDescription(getResources().getString(imageDescId));
         mCurrentPlayState = state;
@@ -572,11 +646,9 @@
 
     boolean updateSeekBars() {
         final int position = (int) mRemoteController.getEstimatedMediaPosition();
+        if (DEBUG) Log.v(TAG, "Estimated time:" + position);
         if (position >= 0) {
-            if (DEBUG) Log.v(TAG, "Seek to " + position);
-            if (!mUserSeeking) {
-                mTransientSeekBar.setProgress(position);
-            }
+            mTransientSeekBar.setProgress(position);
             return true;
         }
         Log.w(TAG, "Updating seek bars; received invalid estimated media position (" +
@@ -587,6 +659,11 @@
 
     static class SavedState extends BaseSavedState {
         boolean clientPresent;
+        String artist;
+        String trackTitle;
+        String albumTitle;
+        long duration;
+        Bitmap bitmap;
 
         SavedState(Parcelable superState) {
             super(superState);
@@ -594,13 +671,23 @@
 
         private SavedState(Parcel in) {
             super(in);
-            this.clientPresent = in.readInt() != 0;
+            clientPresent = in.readInt() != 0;
+            artist = in.readString();
+            trackTitle = in.readString();
+            albumTitle = in.readString();
+            duration = in.readLong();
+            bitmap = Bitmap.CREATOR.createFromParcel(in);
         }
 
         @Override
         public void writeToParcel(Parcel out, int flags) {
             super.writeToParcel(out, flags);
-            out.writeInt(this.clientPresent ? 1 : 0);
+            out.writeInt(clientPresent ? 1 : 0);
+            out.writeString(artist);
+            out.writeString(trackTitle);
+            out.writeString(albumTitle);
+            out.writeLong(duration);
+            bitmap.writeToParcel(out, flags);
         }
 
         public static final Parcelable.Creator<SavedState> CREATOR
@@ -627,34 +714,4 @@
     public boolean providesClock() {
         return false;
     }
-
-    private boolean wasPlayingRecently(int state, long stateChangeTimeMs) {
-        switch (state) {
-            case RemoteControlClient.PLAYSTATE_PLAYING:
-            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
-            case RemoteControlClient.PLAYSTATE_REWINDING:
-            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
-            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
-            case RemoteControlClient.PLAYSTATE_BUFFERING:
-                // actively playing or about to play
-                return true;
-            case RemoteControlClient.PLAYSTATE_NONE:
-                return false;
-            case RemoteControlClient.PLAYSTATE_STOPPED:
-            case RemoteControlClient.PLAYSTATE_PAUSED:
-            case RemoteControlClient.PLAYSTATE_ERROR:
-                // we have stopped playing, check how long ago
-                if (DEBUG) {
-                    if ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS) {
-                        Log.v(TAG, "wasPlayingRecently: time < TIMEOUT was playing recently");
-                    } else {
-                        Log.v(TAG, "wasPlayingRecently: time > TIMEOUT");
-                    }
-                }
-                return ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS);
-            default:
-                Log.e(TAG, "Unknown playback state " + state + " in wasPlayingRecently()");
-                return false;
-        }
-    }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 520cea3..a849316 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -815,7 +815,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onKeyguardVisibilityChanged(isShowing);
+                cb.onKeyguardVisibilityChangedRaw(isShowing);
             }
         }
     }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 76f9637..c08880d 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -19,6 +19,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.graphics.Bitmap;
 import android.media.AudioManager;
+import android.os.SystemClock;
 import android.view.WindowManagerPolicy;
 
 import com.android.internal.telephony.IccCardConstants;
@@ -27,6 +28,11 @@
  * Callback for general information relevant to lock screen.
  */
 class KeyguardUpdateMonitorCallback {
+
+    private static final long VISIBILITY_CHANGED_COLLAPSE_MS = 1000;
+    private long mVisibilityChangedCalled;
+    private boolean mShowing;
+
     /**
      * Called when the battery status changes, e.g. when plugged in or unplugged, charge
      * level, etc. changes.
@@ -70,6 +76,15 @@
      */
     void onKeyguardVisibilityChanged(boolean showing) { }
 
+    void onKeyguardVisibilityChangedRaw(boolean showing) {
+        final long now = SystemClock.elapsedRealtime();
+        if (showing == mShowing
+                && (now - mVisibilityChangedCalled) < VISIBILITY_CHANGED_COLLAPSE_MS) return;
+        onKeyguardVisibilityChanged(showing);
+        mVisibilityChangedCalled = now;
+        mShowing = showing;
+    }
+
     /**
      * Called when visibility of lockscreen clock changes, such as when
      * obscured by a widget.
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
index fd7cae6..6aa0a4b 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
@@ -61,9 +61,11 @@
 public class KeyguardViewManager {
     private final static boolean DEBUG = KeyguardViewMediator.DEBUG;
     private static String TAG = "KeyguardViewManager";
-    public static boolean USE_UPPER_CASE = true;
     public final static String IS_SWITCHING_USER = "is_switching_user";
 
+    // Delay dismissing keyguard to allow animations to complete.
+    private static final int HIDE_KEYGUARD_DELAY = 500;
+
     // Timeout used for keypresses
     static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
 
@@ -509,9 +511,10 @@
                             mKeyguardHost.setCustomBackground(null);
                             updateShowWallpaper(true);
                             mKeyguardHost.removeView(lastView);
+                            mViewMediatorCallback.keyguardGone();
                         }
                     }
-                }, 500);
+                }, HIDE_KEYGUARD_DELAY);
             }
         }
     }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java
index b92ae90..4086f84 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java
@@ -200,7 +200,7 @@
 
     // cached value of whether we are showing (need to know this to quickly
     // answer whether the input should be restricted)
-    private boolean mShowing = false;
+    private boolean mShowing;
 
     // true if the keyguard is hidden by another window
     private boolean mHidden = false;
@@ -253,6 +253,11 @@
     private final float mLockSoundVolume;
 
     /**
+     * For managing external displays
+     */
+    private KeyguardDisplayManager mKeyguardDisplayManager;
+
+    /**
      * Cache of avatar drawables, for use by KeyguardMultiUserAvatar.
      */
     private static MultiUserAvatarCache sMultiUserAvatarCache = new MultiUserAvatarCache();
@@ -304,6 +309,11 @@
          * Report that the keyguard is dismissable, pending the next keyguardDone call.
          */
         void keyguardDonePending();
+
+        /**
+         * Report when keyguard is actually gone
+         */
+        void keyguardGone();
     }
 
     KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@@ -457,6 +467,11 @@
         public void keyguardDonePending() {
             mKeyguardDonePending = true;
         }
+
+        @Override
+        public void keyguardGone() {
+            mKeyguardDisplayManager.hide();
+        }
     };
 
     private void userActivity() {
@@ -483,6 +498,8 @@
 
         mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION));
 
+        mKeyguardDisplayManager = new KeyguardDisplayManager(context);
+
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
 
         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
@@ -491,6 +508,10 @@
                 ? lockPatternUtils : new LockPatternUtils(mContext);
         mLockPatternUtils.setCurrentUser(UserHandle.USER_OWNER);
 
+        // Assume keyguard is showing (unless it's disabled) until we know for sure...
+        mShowing = (mUpdateMonitor.isDeviceProvisioned() || mLockPatternUtils.isSecure())
+                && !mLockPatternUtils.isLockScreenDisabled();
+
         WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
 
         mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback,
@@ -1218,6 +1239,7 @@
 
             mShowKeyguardWakeLock.release();
         }
+        mKeyguardDisplayManager.show();
     }
 
     /**
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java
index 8e39628..169899f 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java
@@ -15,6 +15,9 @@
  */
 package com.android.keyguard;
 
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
@@ -46,6 +49,20 @@
 
     int mChallengeTop = 0;
 
+    private final AnimatorListener mPauseListener = new AnimatorListenerAdapter() {
+        public void onAnimationEnd(Animator animation) {
+            mKeyguardSecurityContainer.onPause();
+        }
+    };
+
+    private final AnimatorListener mResumeListener = new AnimatorListenerAdapter() {
+        public void onAnimationEnd(Animator animation) {
+            if (((View)mKeyguardSecurityContainer).isShown()) {
+                mKeyguardSecurityContainer.onResume(0);
+            }
+        }
+    };
+
     public KeyguardViewStateManager(KeyguardHostView hostView) {
         mKeyguardHostView = hostView;
     }
@@ -102,20 +119,20 @@
     }
 
     public void fadeOutSecurity(int duration) {
-        ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration).start();
+        ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration)
+                .setListener(mPauseListener);
     }
 
     public void fadeInSecurity(int duration) {
-        ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration).start();
+        ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration)
+                .setListener(mResumeListener);
     }
 
     public void onPageBeginMoving() {
         if (mChallengeLayout.isChallengeOverlapping() &&
                 mChallengeLayout instanceof SlidingChallengeLayout) {
             SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout;
-            if (!mKeyguardWidgetPager.isWarping()) {
-                scl.fadeOutChallenge();
-            }
+            scl.fadeOutChallenge();
             mPageIndexOnPageBeginMoving = mKeyguardWidgetPager.getCurrentPage();
         }
         // We use mAppWidgetToShow to show a particular widget after you add it--
@@ -137,11 +154,12 @@
     public void onPageSwitching(View newPage, int newPageIndex) {
         if (mKeyguardWidgetPager != null && mChallengeLayout instanceof SlidingChallengeLayout) {
             boolean isCameraPage = newPage instanceof CameraWidgetFrame;
+            if (isCameraPage) {
+                CameraWidgetFrame camera = (CameraWidgetFrame) newPage;
+                camera.setUseFastTransition(mKeyguardWidgetPager.isWarping());
+            }
             SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout;
             scl.setChallengeInteractive(!isCameraPage);
-            if (isCameraPage) {
-                scl.fadeOutChallenge();
-            }
             final int currentFlags = mKeyguardWidgetPager.getSystemUiVisibility();
             final int newFlags = isCameraPage ? (currentFlags | View.STATUS_BAR_DISABLE_SEARCH)
                     : (currentFlags & ~View.STATUS_BAR_DISABLE_SEARCH);
@@ -178,7 +196,7 @@
             boolean challengeOverlapping = mChallengeLayout.isChallengeOverlapping();
             if (challengeOverlapping && !newCurPage.isSmall()
                     && mPageListeningToSlider != newPageIndex) {
-                newCurPage.shrinkWidget();
+                newCurPage.shrinkWidget(true);
             }
         }
 
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java
index ab8a759..8ee9b61 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java
@@ -375,10 +375,6 @@
         return mSmallFrameHeight;
     }
 
-    public void shrinkWidget() {
-        shrinkWidget(true);
-    }
-
     public void setWidgetLockedSmall(boolean locked) {
         if (locked) {
             setWidgetHeight(mSmallWidgetHeight);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java
index 704af6e..99f7757 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java
@@ -194,7 +194,9 @@
 
     @Override
     public void onPageEndWarp() {
-        hideOutlinesAndSidePages();
+        // if we're moving to the warp page, then immediately hide the other widgets.
+        int duration = getPageWarpIndex() == getNextPage() ? 0 : -1;
+        animateOutlinesAndSidePages(false, duration);
         mViewStateManager.onPageEndWarp();
     }
 
@@ -669,7 +671,7 @@
                 // On the very first measure pass, if the challenge is showing, we need to make sure
                 // that the widget on the current page is small.
                 if (challengeShowing && i == mCurrentPage && !mHasMeasure) {
-                    frame.shrinkWidget();
+                    frame.shrinkWidget(true);
                 }
             }
         }
diff --git a/packages/Keyguard/src/com/android/keyguard/PagedView.java b/packages/Keyguard/src/com/android/keyguard/PagedView.java
index 9d237dc..53c17a5 100644
--- a/packages/Keyguard/src/com/android/keyguard/PagedView.java
+++ b/packages/Keyguard/src/com/android/keyguard/PagedView.java
@@ -82,13 +82,13 @@
 
     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
     // The page is moved more than halfway, automatically move to the next page on touch up.
-    private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
+    private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.5f;
 
     // The following constants need to be scaled based on density. The scaled versions will be
     // assigned to the corresponding member variables below.
-    private static final int FLING_THRESHOLD_VELOCITY = 500;
+    private static final int FLING_THRESHOLD_VELOCITY = 1500;
     private static final int MIN_SNAP_VELOCITY = 1500;
-    private static final int MIN_FLING_VELOCITY = 250;
+    private static final int MIN_FLING_VELOCITY = 500;
 
     // We are disabling touch interaction of the widget region for factory ROM.
     private static final boolean DISABLE_TOUCH_INTERACTION = false;
@@ -267,6 +267,8 @@
 
     private boolean mIsCameraEvent;
     private float mWarpPeekAmount;
+    private boolean mOnPageEndWarpCalled;
+    private boolean mOnPageBeginWarpCalled;
 
     public interface PageSwitchListener {
         void onPageSwitching(View newPage, int newPageIndex);
@@ -491,7 +493,7 @@
         if (!mIsPageMoving) {
             mIsPageMoving = true;
             if (isWarping()) {
-                onPageBeginWarp();
+                dispatchOnPageBeginWarp();
                 if (mPageSwapIndex != -1) {
                     swapPages(mPageSwapIndex, mPageWarpIndex);
                 }
@@ -500,6 +502,22 @@
         }
     }
 
+    private void dispatchOnPageBeginWarp() {
+        if (!mOnPageBeginWarpCalled) {
+            onPageBeginWarp();
+            mOnPageBeginWarpCalled = true;
+        }
+        mOnPageEndWarpCalled = false;
+    }
+
+    private void dispatchOnPageEndWarp() {
+        if (!mOnPageEndWarpCalled) {
+            onPageEndWarp();
+            mOnPageEndWarpCalled = true;
+        }
+        mOnPageBeginWarpCalled = false;
+    }
+
     protected void pageEndMoving() {
         if (DEBUG_WARP) Log.v(TAG, "pageEndMoving(" + mIsPageMoving + ")");
         if (mIsPageMoving) {
@@ -508,7 +526,7 @@
                 if (mPageSwapIndex != -1) {
                     swapPages(mPageSwapIndex, mPageWarpIndex);
                 }
-                onPageEndWarp();
+                dispatchOnPageEndWarp();
                 resetPageWarp();
             }
             onPageEndMoving();
@@ -1919,11 +1937,13 @@
         }
 
         if (isWarping()) {
-            onPageEndWarp();
+            dispatchOnPageEndWarp();
+            notifyPageSwitching(whichPage);
             resetPageWarp();
+        } else {
+            notifyPageSwitching(whichPage);
         }
 
-        notifyPageSwitching(whichPage);
         View focusedChild = getFocusedChild();
         if (focusedChild != null && whichPage != mCurrentPage &&
                 focusedChild == getPageAt(mCurrentPage)) {
@@ -2260,11 +2280,11 @@
         mTempVisiblePagesRange[0] = 0;
         mTempVisiblePagesRange[1] = getPageCount() - 1;
         boundByReorderablePages(true, mTempVisiblePagesRange);
-        mReorderingStarted = true;
 
         // Check if we are within the reordering range
         if (mTempVisiblePagesRange[0] <= dragViewIndex &&
                 dragViewIndex <= mTempVisiblePagesRange[1]) {
+            mReorderingStarted = true;
             if (zoomOut()) {
                 // Find the drag view under the pointer
                 mDragView = getChildAt(dragViewIndex);
@@ -2702,12 +2722,12 @@
         @Override
         public void onAnimationEnd(Animator animation) {
             mWarpAnimation = null;
-            mWarpPageExposed = true;
+            mWarpPageExposed = false;
         }
     };
 
     private void cancelWarpAnimation(String msg, boolean abortAnimation) {
-        if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ")");
+        if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ",abort=" + abortAnimation + ")");
         if (abortAnimation) {
             // We're done with the animation and moving to a new page.  Let the scroller
             // take over the animation.
@@ -2727,9 +2747,9 @@
 
     private void animateWarpPageOnScreen(String reason) {
         if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOnScreen(" + reason + ")");
-        if (isWarping()) {
+        if (isWarping() && !mWarpPageExposed) {
             mWarpPageExposed = true;
-            onPageBeginWarp();
+            dispatchOnPageBeginWarp();
             KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
             if (DEBUG_WARP) Log.v(TAG, "moving page on screen: Tx=" + v.getTranslationX());
             DecelerateInterpolator interp = new DecelerateInterpolator(1.5f);
@@ -2744,7 +2764,7 @@
     private void animateWarpPageOffScreen(String reason, boolean animate) {
         if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOffScreen(" + reason + " anim:" + animate + ")");
         if (isWarping()) {
-            onPageEndWarp();
+            dispatchOnPageEndWarp();
             KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
             if (DEBUG_WARP) Log.v(TAG, "moving page off screen: Tx=" + v.getTranslationX());
             AccelerateInterpolator interp = new AccelerateInterpolator(1.5f);
diff --git a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
index 7a9a1c8..3d515ce 100644
--- a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
+++ b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
@@ -1003,6 +1003,16 @@
         }
     }
 
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        // Focus security fileds before widgets.
+        if (mChallengeView != null &&
+                mChallengeView.requestFocus(direction, previouslyFocusedRect)) {
+            return true;
+        }
+        return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+    }
+
     public void computeScroll() {
         super.computeScroll();
 
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml
index f573d9d..d9f0a9a 100644
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml
@@ -35,7 +35,6 @@
             android:layout_marginBottom="32dip"
             android:layout_gravity="center"
             style="?android:attr/buttonBarButtonStyle"
-            android:singleLine="true"
             android:ellipsize="end"
             android:text="@string/print_error_default_message"
             android:textColor="@color/important_text"
diff --git a/packages/PrintSpooler/res/values-ja/arrays.xml b/packages/PrintSpooler/res/values-ja/arrays.xml
index 57088c8..460bdb2 100644
--- a/packages/PrintSpooler/res/values-ja/arrays.xml
+++ b/packages/PrintSpooler/res/values-ja/arrays.xml
@@ -20,13 +20,13 @@
         <item>JIS_B9</item>
         <item>JIS_B8</item>
         <item>JIS_B7</item>
-        <item>JIS_b6</item>
-        <item>JIS_b5</item>
-        <item>JIS_b4</item>
-        <item>JIS_b3</item>
-        <item>JIS_b2</item>
-        <item>JIS_b1</item>
-        <item>JIS_b0</item>
+        <item>JIS_B6</item>
+        <item>JIS_B5</item>
+        <item>JIS_B4</item>
+        <item>JIS_B3</item>
+        <item>JIS_B2</item>
+        <item>JIS_B1</item>
+        <item>JIS_B0</item>
         <item>JIS_EXEC</item>
         <item>JPN_CHOU4</item>
         <item>JPN_CHOU3</item>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index f2e768a..d2613d0 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -93,6 +93,12 @@
     <!-- Title of the action bar button to got to add a printer. [CHAR LIMIT=25] -->
     <string name="print_add_printer">Add printer</string>
 
+    <!-- Title of the menu item to select a printer. [CHAR LIMIT=25] -->
+    <string name="print_select_printer">Select printer</string>
+
+    <!-- Title of the menu item to forget a printer. [CHAR LIMIT=25] -->
+    <string name="print_forget_printer">Forget printer</string>
+
     <!-- Utterance to announce a change in the number of matches during a search. This is spoken to a blind user. [CHAR LIMIT=none] -->
     <plurals name="print_search_result_count_utterance">
         <item quantity="one"><xliff:g id="count" example="1">%1$s</xliff:g> printer found</item>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
index 0601467..9831839 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
@@ -79,6 +79,8 @@
 
     private PrinterId mTrackedPrinter;
 
+    private boolean mPrintersUpdatedBefore;
+
     public FusedPrintersProvider(Context context) {
         super(context);
         mPersistenceManager = new PersistenceManager(context);
@@ -88,13 +90,14 @@
         mPersistenceManager.addPrinterAndWritePrinterHistory(printer);
     }
 
-    private void computeAndDeliverResult(Map<PrinterId, PrinterInfo> discoveredPrinters) {
+    private void computeAndDeliverResult(ArrayMap<PrinterId, PrinterInfo> discoveredPrinters,
+            ArrayMap<PrinterId, PrinterInfo> favoritePrinters) {
         List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
 
         // Add the updated favorite printers.
-        final int favoritePrinterCount = mFavoritePrinters.size();
+        final int favoritePrinterCount = favoritePrinters.size();
         for (int i = 0; i < favoritePrinterCount; i++) {
-            PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
+            PrinterInfo favoritePrinter = favoritePrinters.valueAt(i);
             PrinterInfo updatedPrinter = discoveredPrinters.remove(
                     favoritePrinter.getId());
             if (updatedPrinter != null) {
@@ -123,8 +126,11 @@
         mPrinters.addAll(printers);
 
         if (isStarted()) {
-            // Deliver the printers.
+            // If stated deliver the new printers.
             deliverResult(printers);
+        } else {
+            // Otherwise, take a note for the change.
+            onContentChanged();
         }
     }
 
@@ -165,6 +171,8 @@
                     .getSystemService(Context.PRINT_SERVICE);
             mDiscoverySession = printManager.createPrinterDiscoverySession();
             mPersistenceManager.readPrinterHistory();
+        } else if (mPersistenceManager.isHistoryChanged()) {
+            mPersistenceManager.readPrinterHistory();
         }
         if (mPersistenceManager.isReadHistoryCompleted()
                 && !mDiscoverySession.isPrinterDiscoveryStarted()) {
@@ -176,7 +184,7 @@
                                 + mDiscoverySession.getPrinters().size()
                                 + " " + FusedPrintersProvider.this.hashCode());
                     }
-                    updatePrinters(mDiscoverySession.getPrinters());
+                    updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
                 }
             });
             final int favoriteCount = mFavoritePrinters.size();
@@ -187,15 +195,19 @@
             mDiscoverySession.startPrinterDisovery(printerIds);
             List<PrinterInfo> printers = mDiscoverySession.getPrinters();
             if (!printers.isEmpty()) {
-                updatePrinters(printers);
+                updatePrinters(printers, mFavoritePrinters);
             }
         }
     }
 
-    private void updatePrinters(List<PrinterInfo> printers) {
-        if (mPrinters.equals(printers)) {
+    private void updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters) {
+        if (mPrintersUpdatedBefore && mPrinters.equals(printers)
+                && mFavoritePrinters.equals(favoritePrinters)) {
             return;
         }
+
+        mPrintersUpdatedBefore = true;
+
         ArrayMap<PrinterId, PrinterInfo> printersMap =
                 new ArrayMap<PrinterId, PrinterInfo>();
         final int printerCount = printers.size();
@@ -203,7 +215,16 @@
             PrinterInfo printer = printers.get(i);
             printersMap.put(printer.getId(), printer);
         }
-        computeAndDeliverResult(printersMap);
+
+        ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap =
+                new ArrayMap<PrinterId, PrinterInfo>();
+        final int favoritePrinterCount = favoritePrinters.size();
+        for (int i = 0; i < favoritePrinterCount; i++) {
+            PrinterInfo favoritePrinter = favoritePrinters.get(i);
+            favoritePrintersMap.put(favoritePrinter.getId(), favoritePrinter);
+        }
+
+        computeAndDeliverResult(printersMap, favoritePrintersMap);
     }
 
     @Override
@@ -264,6 +285,42 @@
         }
     }
 
+    public boolean isFavoritePrinter(PrinterId printerId) {
+        final int printerCount = mFavoritePrinters.size();
+        for (int i = 0; i < printerCount; i++) {
+            PrinterInfo favoritePritner = mFavoritePrinters.get(i);
+            if (favoritePritner.getId().equals(printerId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void forgetFavoritePrinter(PrinterId printerId) {
+        List<PrinterInfo> newFavoritePrinters = null;
+
+        // Remove the printer from the favorites.
+        final int favoritePrinterCount = mFavoritePrinters.size();
+        for (int i = 0; i < favoritePrinterCount; i++) {
+            PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
+            if (favoritePrinter.getId().equals(printerId)) {
+                newFavoritePrinters = new ArrayList<PrinterInfo>();
+                newFavoritePrinters.addAll(mPrinters);
+                newFavoritePrinters.remove(i);
+                break;
+            }
+        }
+
+        // If we removed a favorite printer, we have work to do.
+        if (newFavoritePrinters != null) {
+            // Remove the printer from history and persist the latter.
+            mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId);
+
+            // Recompute and deliver the printers.
+            updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters);
+        }
+    }
+
     private final class PersistenceManager {
         private static final String PERSIST_FILE_NAME = "printer_history.xml";
 
@@ -281,13 +338,15 @@
 
         private final AtomicFile mStatePersistFile;
 
-        private List<PrinterInfo> mHistoricalPrinters;
+        private List<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>();
 
         private boolean mReadHistoryCompleted;
         private boolean mReadHistoryInProgress;
 
         private ReadTask mReadTask;
 
+        private volatile long mLastReadHistoryTimestamp;
+
         private PersistenceManager(Context context) {
             mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
                     PERSIST_FILE_NAME));
@@ -327,6 +386,27 @@
                     new ArrayList<PrinterInfo>(mHistoricalPrinters));
         }
 
+        @SuppressWarnings("unchecked")
+        public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) {
+            boolean writeHistory = false;
+            final int printerCount = mHistoricalPrinters.size();
+            for (int i = printerCount - 1; i >= 0; i--) {
+                PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
+                if (historicalPrinter.getId().equals(printerId)) {
+                    mHistoricalPrinters.remove(i);
+                    writeHistory = true;
+                }
+            }
+            if (writeHistory) {
+                new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
+                        new ArrayList<PrinterInfo>(mHistoricalPrinters));
+            }
+        }
+
+        public boolean isHistoryChanged() {
+            return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
+        }
+
         private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
             Map<PrinterId, PrinterRecord> recordMap =
                     new ArrayMap<PrinterId, PrinterRecord>();
@@ -423,11 +503,10 @@
                 mReadHistoryInProgress = false;
                 mReadHistoryCompleted = true;
 
-                // Deliver the favorites.
-                Map<PrinterId, PrinterInfo> discoveredPrinters = Collections.emptyMap();
-                computeAndDeliverResult(discoveredPrinters);
+                // Deliver the printers.
+                updatePrinters(mDiscoverySession.getPrinters(), mHistoricalPrinters);
 
-                // Start loading the available printers.
+                // Loading the available printers if needed.
                 loadInternal();
 
                 // We are done.
@@ -450,6 +529,8 @@
                     XmlPullParser parser = Xml.newPullParser();
                     parser.setInput(in, null);
                     parseState(parser, printers);
+                    // Take a note which version of the history was read.
+                    mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified();
                     return printers;
                 } catch (IllegalStateException ise) {
                     Slog.w(LOG_TAG, "Failed parsing ", ise);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index 8f26361..f6008d4 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -357,6 +357,9 @@
         }
 
         public void cancel() {
+            if (isWorking()) {
+                mRemotePrintAdapter.cancel();
+            }
             mControllerState = CONTROLLER_STATE_CANCELLED;
         }
 
@@ -934,6 +937,7 @@
                             mPrintJobId, mCurrentPrinter);
 
                     if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
+                        mCapabilitiesTimeout.post();
                         updateUi();
                         return;
                     }
@@ -1525,9 +1529,13 @@
                                 builder.append(',');
                             }
                             PageRange pageRange = pageRanges[i];
-                            builder.append(pageRange.getStart());
-                            builder.append('-');
-                            builder.append(pageRange.getEnd());
+                            final int shownStartPage = pageRange.getStart() + 1;
+                            final int shownEndPage = pageRange.getEnd() + 1;
+                            builder.append(shownStartPage);
+                            if (shownStartPage != shownEndPage) {
+                                builder.append('-');
+                                builder.append(shownEndPage);
+                            }
                         }
                         mPageRangeEditText.setText(builder.toString());
                     }
@@ -2182,12 +2190,16 @@
                         // Select the old color mode - nothing really changed.
                         setColorModeSpinnerSelectionNoCallback(oldColorModeNewIndex);
                     } else {
-                        final int selectedColorModeIndex = Integer.numberOfTrailingZeros(
-                                    (colorModes & defaultAttributes.getColorMode()));
-                        setColorModeSpinnerSelectionNoCallback(selectedColorModeIndex);
-                        mCurrPrintAttributes.setColorMode(mColorModeSpinnerAdapter
-                                .getItem(selectedColorModeIndex).value);
-                        someAttributeSelectionChanged = true;
+                        final int selectedColorMode = colorModes & defaultAttributes.getColorMode();
+                        final int itemCount = mColorModeSpinnerAdapter.getCount();
+                        for (int i = 0; i < itemCount; i++) {
+                            SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i);
+                            if (selectedColorMode == item.value) {
+                                setColorModeSpinnerSelectionNoCallback(i);
+                                mCurrPrintAttributes.setColorMode(selectedColorMode);
+                                someAttributeSelectionChanged = true;
+                            }
+                        }
                     }
                 }
                 mColorModeSpinner.setEnabled(true);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index 609ae64..615d667 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -441,6 +441,7 @@
 
     private void removeObsoletePrintJobs() {
         synchronized (mLock) {
+            boolean persistState = false;
             final int printJobCount = mPrintJobs.size();
             for (int i = printJobCount - 1; i >= 0; i--) {
                 PrintJobInfo printJob = mPrintJobs.get(i);
@@ -450,9 +451,12 @@
                         Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
                     }
                     removePrintJobFileLocked(printJob.getId());
+                    persistState = true;
                 }
             }
-            mPersistanceManager.writeStateLocked();
+            if (persistState) {
+                mPersistanceManager.writeStateLocked();
+            }
         }
     }
 
@@ -544,7 +548,7 @@
         final int printJobCount = mPrintJobs.size();
         for (int i = 0; i < printJobCount; i++) {
             PrintJobInfo printJob = mPrintJobs.get(i);
-            if (isActiveState(printJob.getState())
+            if (isActiveState(printJob.getState()) && printJob.getPrinterId() != null
                     && printJob.getPrinterId().getServiceName().equals(service)) {
                 return true;
             }
@@ -799,6 +803,10 @@
                 for (int j = 0; j < printJobCount; j++) {
                     PrintJobInfo printJob = printJobs.get(j);
 
+                    if (!shouldPersistPrintJob(printJob)) {
+                        continue;
+                    }
+
                     serializer.startTag(null, TAG_JOB);
 
                     serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString());
diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
index fd14af9..d9ccb5d 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
@@ -137,4 +137,15 @@
             Log.e(LOG_TAG, "Error calling finish()", re);
         }
     }
+
+    public void cancel() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "cancel()");
+        }
+        try {
+            mRemoteInterface.cancel();
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error calling cancel()", re);
+        }
+    }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
index 204c152..fe5920c 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
@@ -46,6 +46,8 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -54,6 +56,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
 import android.widget.ArrayAdapter;
 import android.widget.BaseAdapter;
 import android.widget.Filter;
@@ -81,6 +84,8 @@
     private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS =
             "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS";
 
+    private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
+
     private final ArrayList<PrintServiceInfo> mAddPrinterServices =
             new ArrayList<PrintServiceInfo>();
 
@@ -127,6 +132,9 @@
         mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
             @Override
             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) {
+                    return;
+                }
                 PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
                 Activity activity = getActivity();
                 if (activity instanceof OnPrinterSelectedListener) {
@@ -138,6 +146,8 @@
             }
         });
 
+        registerForContextMenu(mListView);
+
         return content;
     }
 
@@ -185,6 +195,62 @@
     }
 
     @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        if (view == mListView) {
+            final int position = ((AdapterContextMenuInfo) menuInfo).position;
+            PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
+
+            menu.setHeaderTitle(printer.getName());
+
+            // Add the select menu item if applicable.
+            if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) {
+                MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer,
+                        Menu.NONE, R.string.print_select_printer);
+                Intent intent = new Intent();
+                intent.putExtra(EXTRA_PRINTER_ID, printer.getId());
+                selectItem.setIntent(intent);
+            }
+
+            // Add the forget menu item if applicable.
+            FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>)
+                    getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
+            if (provider.isFavoritePrinter(printer.getId())) {
+                MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer,
+                        Menu.NONE, R.string.print_forget_printer);
+                Intent intent = new Intent();
+                intent.putExtra(EXTRA_PRINTER_ID, printer.getId());
+                forgetItem.setIntent(intent);
+            }
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.string.print_select_printer: {
+                PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra(
+                        EXTRA_PRINTER_ID);
+                Activity activity = getActivity();
+                if (activity instanceof OnPrinterSelectedListener) {
+                    ((OnPrinterSelectedListener) activity).onPrinterSelected(printerId);
+                } else {
+                    throw new IllegalStateException("the host activity must implement"
+                            + " OnPrinterSelectedListener");
+                }
+            } return true;
+
+            case R.string.print_forget_printer: {
+                PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra(
+                        EXTRA_PRINTER_ID);
+                FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>)
+                        getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
+                provider.forgetFavoritePrinter(printerId);
+            } return true;
+        }
+        return false;
+    }
+
+    @Override
     public void onResume() {
         updateAddPrintersAdapter();
         getActivity().invalidateOptionsMenu();
@@ -464,7 +530,7 @@
                         R.layout.printer_list_item, parent, false);
             }
 
-            convertView.setEnabled(isEnabled(position));
+            convertView.setEnabled(isActionable(position));
 
             CharSequence title = null;
             CharSequence subtitle = null;
@@ -506,8 +572,7 @@
             return convertView;
         }
 
-        @Override
-        public boolean isEnabled(int position) {
+        public boolean isActionable(int position) {
             PrinterInfo printer =  (PrinterInfo) getItem(position);
             return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
         }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ffb4c20..29e8d1d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -5,7 +5,6 @@
         >
 
     <!-- Standard permissions granted to the shell. -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.SEND_SMS" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
@@ -64,6 +63,7 @@
     <uses-permission android:name="android.permission.SET_SCREEN_COMPATIBILITY" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 09ac2da..8d6fe41 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -71,6 +71,9 @@
     <!-- Keyguard -->
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
 
+    <!-- Wifi Display -->
+    <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
+
     <application
         android:persistent="true"
         android:allowClearUserData="false"
diff --git a/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png
new file mode 100644
index 0000000..5bbfa4f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png
index 2d8d074..693abf5 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png
new file mode 100644
index 0000000..f256fbb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png
new file mode 100644
index 0000000..b946ec9
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png
new file mode 100644
index 0000000..48606a8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png
new file mode 100644
index 0000000..d006f13
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png
new file mode 100644
index 0000000..867947b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png
index 7220968..e3b3eeb 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png
deleted file mode 100644
index 02d7fda..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png
deleted file mode 100644
index 263f07c..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png
index 8f4cb64..c6f03c4 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/search_light.png b/packages/SystemUI/res/drawable-hdpi/search_light.png
index 116b1f0..3c0dc4e 100644
--- a/packages/SystemUI/res/drawable-hdpi/search_light.png
+++ b/packages/SystemUI/res/drawable-hdpi/search_light.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/search_light_land.png b/packages/SystemUI/res/drawable-hdpi/search_light_land.png
new file mode 100644
index 0000000..731f19b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/search_light_land.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png
new file mode 100644
index 0000000..1a58144
--- /dev/null
+++ b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png
new file mode 100644
index 0000000..a12519e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png
new file mode 100644
index 0000000..ce41454
--- /dev/null
+++ b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png
new file mode 100644
index 0000000..b0b4561
--- /dev/null
+++ b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png
new file mode 100644
index 0000000..2856e09
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png
index 399db00..15340d3 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png
new file mode 100644
index 0000000..b1e984c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png
new file mode 100644
index 0000000..ceda1bb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png
new file mode 100644
index 0000000..25fc759
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png
new file mode 100644
index 0000000..9dfc3c6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png
new file mode 100644
index 0000000..82f4113
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png
index 8c2dc68..cc81794 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png
deleted file mode 100644
index 09ae409..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png
deleted file mode 100644
index 780cfc8..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png
index 2142147..1c2d7aa 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/search_light.png b/packages/SystemUI/res/drawable-mdpi/search_light.png
index 7a70984..8010ce7 100644
--- a/packages/SystemUI/res/drawable-mdpi/search_light.png
+++ b/packages/SystemUI/res/drawable-mdpi/search_light.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/search_light_land.png b/packages/SystemUI/res/drawable-mdpi/search_light_land.png
new file mode 100644
index 0000000..a4d82f0
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/search_light_land.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png
new file mode 100644
index 0000000..72269f2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png
index c0032e2..e3cc9b0 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png
new file mode 100644
index 0000000..47be502
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png
new file mode 100644
index 0000000..4b12809
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png
new file mode 100644
index 0000000..945c606
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png
new file mode 100644
index 0000000..0a3f73e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png
new file mode 100644
index 0000000..398cbef
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png
index bffbf55..65d15b5 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png
deleted file mode 100644
index 48f90ac..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png
deleted file mode 100644
index 621c045..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png
index b0ea8e0..fbd4d6b 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light.png b/packages/SystemUI/res/drawable-xhdpi/search_light.png
index e2aed09..6d46fdd 100644
--- a/packages/SystemUI/res/drawable-xhdpi/search_light.png
+++ b/packages/SystemUI/res/drawable-xhdpi/search_light.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png
new file mode 100644
index 0000000..b62c74e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png
new file mode 100644
index 0000000..efc9b04
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png
index a3cc08d..e15981a 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png
new file mode 100644
index 0000000..8e225af
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png
new file mode 100644
index 0000000..937202b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png
new file mode 100644
index 0000000..4621d18
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png
new file mode 100644
index 0000000..a1ab61b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png
new file mode 100644
index 0000000..ea42a7f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png
index ab841d2..1a5d26a 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png
deleted file mode 100644
index b07be828..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png
deleted file mode 100644
index f02d0ab..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png
index aac3428..86df881 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light.png b/packages/SystemUI/res/drawable-xxhdpi/search_light.png
index e5ef85d..7742207 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/search_light.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/search_light.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png
new file mode 100644
index 0000000..f364577
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml b/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml
new file mode 100644
index 0000000..70db2a9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="false">
+    <item android:drawable="@drawable/ic_qs_cast_connecting_0" android:duration="500" />
+    <item android:drawable="@drawable/ic_qs_cast_connecting_1" android:duration="500" />
+    <item android:drawable="@drawable/ic_qs_cast_connecting_2" android:duration="500" />
+    <item android:drawable="@drawable/ic_qs_cast_connecting_1" android:duration="500" />
+</animation-list>
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
index b2ba25a..0c0be29 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
@@ -24,6 +24,7 @@
     android:id="@+id/recents_root"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
+    android:foreground="@drawable/bg_protect"
     systemui:recentItemLayout="@layout/status_bar_recent_item"
     >
     <FrameLayout
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index aa365ae..5488a87 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -311,7 +311,7 @@
             android:layout_height="80dp"
             android:layout_width="match_parent"
             android:layout_gravity="center_vertical"
-            android:src="@drawable/search_light"
+            android:src="@drawable/search_light_land"
             android:scaleType="center"
             android:visibility="gone"
             android:contentDescription="@string/accessibility_search_light"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index d7312df..eb66908 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -95,12 +95,12 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     />
-                <!-- battery must be padded below by 2px to match assets -->
+                <!-- battery must be padded below to match assets -->
                 <com.android.systemui.BatteryMeterView
                     android:id="@+id/battery"
                     android:layout_height="16dp"
                     android:layout_width="10.5dp"
-                    android:layout_marginBottom="2px"
+                    android:layout_marginBottom="0.33dp"
                     android:layout_marginStart="4dip"
                     />
             </LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_recent_panel.xml b/packages/SystemUI/res/layout/status_bar_recent_panel.xml
index e41475b..2f3968d 100644
--- a/packages/SystemUI/res/layout/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout/status_bar_recent_panel.xml
@@ -24,6 +24,7 @@
     android:id="@+id/recents_root"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
+    android:foreground="@drawable/bg_protect"
     systemui:recentItemLayout="@layout/status_bar_recent_item"
     >
     <FrameLayout
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e6fcdff..e36ca8e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -490,10 +490,8 @@
     <string name="quick_settings_wifi_no_network">No Network</string>
     <!-- QuickSettings: Wifi (Off) [CHAR LIMIT=NONE] -->
     <string name="quick_settings_wifi_off_label">Wi-Fi Off</string>
-    <!-- QuickSettings: Wifi display [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_wifi_display_label">Wi-Fi Display</string>
-    <!-- QuickSettings: Wifi display [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_wifi_display_no_connection_label">Wireless Display</string>
+    <!-- QuickSettings: Remote display [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_remote_display_no_connection_label">Cast Screen</string>
     <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
     <string name="quick_settings_brightness_dialog_title">Brightness</string>
     <!-- QuickSettings: Brightness dialog auto brightness button [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index b6e03e1..13aafb2 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -66,7 +66,7 @@
     private final RectF mFrame = new RectF();
     private final RectF mButtonFrame = new RectF();
     private final RectF mClipFrame = new RectF();
-    private final Rect mBoltFrame = new Rect();
+    private final RectF mBoltFrame = new RectF();
 
     private class BatteryTracker extends BroadcastReceiver {
         public static final int UNKNOWN_LEVEL = -1;
@@ -319,10 +319,10 @@
 
         if (tracker.plugged) {
             // draw the bolt
-            final int bl = (int)(mFrame.left + mFrame.width() / 4.5f);
-            final int bt = (int)(mFrame.top + mFrame.height() / 6f);
-            final int br = (int)(mFrame.right - mFrame.width() / 7f);
-            final int bb = (int)(mFrame.bottom - mFrame.height() / 10f);
+            final float bl = mFrame.left + mFrame.width() / 4.5f;
+            final float bt = mFrame.top + mFrame.height() / 6f;
+            final float br = mFrame.right - mFrame.width() / 7f;
+            final float bb = mFrame.bottom - mFrame.height() / 10f;
             if (mBoltFrame.left != bl || mBoltFrame.top != bt
                     || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
                 mBoltFrame.set(bl, bt, br, bb);
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCase.java b/packages/SystemUI/src/com/android/systemui/DessertCase.java
index dd4c018..d797e38 100644
--- a/packages/SystemUI/src/com/android/systemui/DessertCase.java
+++ b/packages/SystemUI/src/com/android/systemui/DessertCase.java
@@ -36,7 +36,8 @@
         if (pm.getComponentEnabledSetting(cn) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
             Slog.v("DessertCase", "ACHIEVEMENT UNLOCKED");
             pm.setComponentEnabledSetting(cn,
-                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                    PackageManager.DONT_KILL_APP);
         }
 
         mView = new DessertCaseView(this);
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
index 6fce732..4147155 100644
--- a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
@@ -496,7 +496,6 @@
     }
 
     public static class RescalingContainer extends FrameLayout {
-        private static final int SYSTEM_UI_MODE_800 = 0x00000800;
         private DessertCaseView mView;
         private float mDarkness;
 
@@ -509,7 +508,7 @@
                     | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                     | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                     | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                    | SYSTEM_UI_MODE_800
+                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
             );
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 4b0c2cb..8092ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -27,13 +27,16 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Region.Op;
 import android.opengl.GLUtils;
 import android.os.SystemProperties;
 import android.renderscript.Matrix4f;
 import android.service.wallpaper.WallpaperService;
 import android.util.Log;
+import android.view.Display;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
@@ -107,10 +110,12 @@
         private WallpaperObserver mReceiver;
 
         Bitmap mBackground;
+        int mBackgroundWidth = -1, mBackgroundHeight = -1;
         int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
         int mLastRotation = -1;
-        float mXOffset;
-        float mYOffset;
+        float mXOffset = 0.5f;
+        float mYOffset = 0.5f;
+        float mScale = 1f;
 
         boolean mVisible = true;
         boolean mRedrawNeeded;
@@ -155,6 +160,8 @@
 
                 mLastSurfaceWidth = mLastSurfaceHeight = -1;
                 mBackground = null;
+                mBackgroundWidth = -1;
+                mBackgroundHeight = -1;
                 mRedrawNeeded = true;
                 drawFrame();
             }
@@ -173,6 +180,8 @@
                 }
                 mBackground.recycle();
                 mBackground = null;
+                mBackgroundWidth = -1;
+                mBackgroundHeight = -1;
                 mWallpaperManager.forgetLoadedWallpaper();
             }
         }
@@ -204,21 +213,40 @@
             }
         }
 
-        @Override
-        public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
-            super.onDesiredSizeChanged(desiredWidth, desiredHeight);
-            SurfaceHolder surfaceHolder = getSurfaceHolder();
-            if (surfaceHolder != null) {
-                updateSurfaceSize(surfaceHolder);
-            }
-        }
-
         void updateSurfaceSize(SurfaceHolder surfaceHolder) {
+            Point p = getDefaultDisplaySize();
+
+            // Load background image dimensions, if we haven't saved them yet
+            if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
+                // Need to load the image to get dimensions
+                mWallpaperManager.forgetLoadedWallpaper();
+                updateWallpaperLocked();
+                if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
+                    // Default to the display size if we can't find the dimensions
+                    mBackgroundWidth = p.x;
+                    mBackgroundHeight = p.y;
+                }
+            }
+
+            // Force the wallpaper to cover the screen in both dimensions
+            int surfaceWidth = Math.max(p.x, mBackgroundWidth);
+            int surfaceHeight = Math.max(p.y, mBackgroundHeight);
+
+            // If the surface dimensions haven't changed, then just return
+            final Rect frame = surfaceHolder.getSurfaceFrame();
+            if (frame != null) {
+                final int dw = frame.width();
+                final int dh = frame.height();
+                if (surfaceWidth == dw && surfaceHeight == dh) {
+                    return;
+                }
+            }
+
             if (FIXED_SIZED_SURFACE) {
                 // Used a fixed size surface, because we are special.  We can do
                 // this because we know the current design of window animations doesn't
                 // cause this to break.
-                surfaceHolder.setFixedSize(getDesiredMinimumWidth(), getDesiredMinimumHeight());
+                surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
             } else {
                 surfaceHolder.setSizeFromLayout();
             }
@@ -298,13 +326,30 @@
             drawFrame();
         }
 
+        private Point getDefaultDisplaySize() {
+            Point p = new Point();
+            Context c = ImageWallpaper.this.getApplicationContext();
+            WindowManager wm = (WindowManager)c.getSystemService(Context.WINDOW_SERVICE);
+            Display d = wm.getDefaultDisplay();
+            d.getRealSize(p);
+            return p;
+        }
+
         void drawFrame() {
+            int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)).
+                    getDefaultDisplay().getRotation();
+
+            // Sometimes a wallpaper is not large enough to cover the screen in one dimension.
+            // Call updateSurfaceSize -- it will only actually do the update if the dimensions
+            // should change
+            if (newRotation != mLastRotation) {
+                // Update surface size (if necessary)
+                updateSurfaceSize(getSurfaceHolder());
+            }
             SurfaceHolder sh = getSurfaceHolder();
             final Rect frame = sh.getSurfaceFrame();
             final int dw = frame.width();
             final int dh = frame.height();
-            int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)).
-                    getDefaultDisplay().getRotation();
             boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth || dh != mLastSurfaceHeight;
 
             boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
@@ -343,10 +388,21 @@
                 }
             }
 
-            final int availw = dw - mBackground.getWidth();
-            final int availh = dh - mBackground.getHeight();
-            int xPixels = availw < 0 ? (int)(availw * mXOffset + .5f) : (availw / 2);
-            int yPixels = availh < 0 ? (int)(availh * mYOffset + .5f) : (availh / 2);
+            // Center the scaled image
+            mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
+                    dh / (float) mBackground.getHeight()));
+            final int availw = dw - (int) (mBackground.getWidth() * mScale);
+            final int availh = dh - (int) (mBackground.getHeight() * mScale);
+            int xPixels = availw / 2;
+            int yPixels = availh / 2;
+
+            // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
+            // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
+            // will remain unchanged
+            final int availwUnscaled = dw - mBackground.getWidth();
+            final int availhUnscaled = dh - mBackground.getHeight();
+            if (availwUnscaled < 0) xPixels += (int)(availwUnscaled * (mXOffset - .5f) + .5f);
+            if (availhUnscaled < 0) yPixels += (int)(availhUnscaled * (mYOffset - .5f) + .5f);
 
             mOffsetsChanged = false;
             mRedrawNeeded = false;
@@ -354,8 +410,6 @@
                 mLastSurfaceWidth = dw;
                 mLastSurfaceHeight = dh;
             }
-            mLastXTranslation = xPixels;
-            mLastYTranslation = yPixels;
             if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {
                 if (DEBUG) {
                     Log.d(TAG, "Suppressed drawFrame since the image has not "
@@ -363,6 +417,8 @@
                 }
                 return;
             }
+            mLastXTranslation = xPixels;
+            mLastYTranslation = yPixels;
 
             if (DEBUG) {
                 Log.d(TAG, "Redrawing wallpaper");
@@ -391,7 +447,11 @@
             Throwable exception = null;
             try {
                 mBackground = null;
+                mBackgroundWidth = -1;
+                mBackgroundHeight = -1;
                 mBackground = mWallpaperManager.getBitmap();
+                mBackgroundWidth = mBackground.getWidth();
+                mBackgroundHeight = mBackground.getHeight();
             } catch (RuntimeException e) {
                 exception = e;
             } catch (OutOfMemoryError e) {
@@ -400,6 +460,8 @@
 
             if (exception != null) {
                 mBackground = null;
+                mBackgroundWidth = -1;
+                mBackgroundHeight = -1;
                 // Note that if we do fail at this, and the default wallpaper can't
                 // be loaded, we will go into a cycle.  Don't do a build where the
                 // default wallpaper can't be loaded.
@@ -413,24 +475,27 @@
             }
         }
 
-        private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int x, int y) {
+        private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
             Canvas c = sh.lockCanvas();
             if (c != null) {
                 try {
                     if (DEBUG) {
-                        Log.d(TAG, "Redrawing: x=" + x + ", y=" + y);
+                        Log.d(TAG, "Redrawing: left=" + left + ", top=" + top);
                     }
 
-                    c.translate(x, y);
+                    final float right = left + mBackground.getWidth() * mScale;
+                    final float bottom = top + mBackground.getHeight() * mScale;
                     if (w < 0 || h < 0) {
                         c.save(Canvas.CLIP_SAVE_FLAG);
-                        c.clipRect(0, 0, mBackground.getWidth(), mBackground.getHeight(),
+                        c.clipRect(left, top, right, bottom,
                                 Op.DIFFERENCE);
                         c.drawColor(0xff000000);
                         c.restore();
                     }
                     if (mBackground != null) {
-                        c.drawBitmap(mBackground, 0, 0, null);
+                        RectF dest = new RectF(left, top, right, bottom);
+                        // add a filter bitmap?
+                        c.drawBitmap(mBackground, null, dest, null);
                     }
                 } finally {
                     sh.unlockCanvasAndPost(c);
@@ -441,8 +506,8 @@
         private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) {
             if (!initGL(sh)) return false;
 
-            final float right = left + mBackground.getWidth();
-            final float bottom = top + mBackground.getHeight();
+            final float right = left + mBackground.getWidth() * mScale;
+            final float bottom = top + mBackground.getHeight() * mScale;
 
             final Rect frame = sh.getSurfaceFrame();
             final Matrix4f ortho = new Matrix4f();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
index 9839fe9..7d3e870 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
@@ -38,7 +38,7 @@
     }
 
     private void updateAnim() {
-        Drawable drawable = getDrawable();
+        Drawable drawable = mAttached ? getDrawable() : null;
         if (mAttached && mAnim != null) {
             mAnim.stop();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 6a2bc5f..ed00398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
-import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.TaskStackBuilder;
@@ -70,6 +69,7 @@
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SearchPanelView;
 import com.android.systemui.SystemUI;
+import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
 import com.android.systemui.statusbar.policy.NotificationRowLayout;
 
 import java.util.ArrayList;
@@ -128,7 +128,6 @@
     protected boolean mUseHeadsUp = false;
 
     protected IDreamManager mDreamManager;
-    KeyguardManager mKeyguardManager;
     PowerManager mPowerManager;
     protected int mRowHeight;
 
@@ -221,7 +220,6 @@
 
         mDreamManager = IDreamManager.Stub.asInterface(
                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
-        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
         mProvisioningObserver.onChange(false); // set up
@@ -749,9 +747,7 @@
                     Log.w(TAG, "Sending contentIntent failed: " + e);
                 }
 
-                KeyguardManager kgm =
-                    (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-                if (kgm != null) kgm.exitKeyguardSecurely(null);
+                KeyguardTouchDelegate.getInstance(mContext).dismiss();
             }
 
             try {
@@ -1056,10 +1052,12 @@
         boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
                 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
 
+        final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
         boolean interrupt = (isFullscreen || (isHighPriority && isNoisy))
                 && isAllowed
                 && mPowerManager.isScreenOn()
-                && !mKeyguardManager.isKeyguardLocked();
+                && !keyguard.isShowingAndNotHidden()
+                && !keyguard.isInputRestricted();
         try {
             interrupt = interrupt && !mDreamManager.isDreaming();
         } catch (RemoteException e) {
@@ -1087,8 +1085,7 @@
     }
 
     public boolean inKeyguardRestrictedInputMode() {
-        KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        return km.inKeyguardRestrictedInputMode();
+        return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted();
     }
 
     public void setInteracting(int barWindow, boolean interacting) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index e8173b7..39333d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -55,8 +55,7 @@
     private static final int MSG_TOGGLE_RECENT_APPS         = 13 << MSG_SHIFT;
     private static final int MSG_PRELOAD_RECENT_APPS        = 14 << MSG_SHIFT;
     private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 15 << MSG_SHIFT;
-    private static final int MSG_SET_NAVIGATION_ICON_HINTS  = 16 << MSG_SHIFT;
-    private static final int MSG_SET_WINDOW_STATE           = 17 << MSG_SHIFT;
+    private static final int MSG_SET_WINDOW_STATE           = 16 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -98,7 +97,6 @@
         public void showSearchPanel();
         public void hideSearchPanel();
         public void cancelPreloadRecentApps();
-        public void setNavigationIconHints(int hints);
         public void setWindowState(int window, int state);
     }
 
@@ -227,13 +225,6 @@
         }
     }
 
-    public void setNavigationIconHints(int hints) {
-        synchronized (mList) {
-            mHandler.removeMessages(MSG_SET_NAVIGATION_ICON_HINTS);
-            mHandler.obtainMessage(MSG_SET_NAVIGATION_ICON_HINTS, hints, 0, null).sendToTarget();
-        }
-    }
-
     public void setWindowState(int window, int state) {
         synchronized (mList) {
             // don't coalesce these
@@ -318,9 +309,6 @@
                 case MSG_CANCEL_PRELOAD_RECENT_APPS:
                     mCallbacks.cancelPreloadRecentApps();
                     break;
-                case MSG_SET_NAVIGATION_ICON_HINTS:
-                    mCallbacks.setNavigationIconHints(msg.arg1);
-                    break;
                 case MSG_SET_WINDOW_STATE:
                     mCallbacks.setWindowState(msg.arg1, msg.arg2);
                     break;
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 8ad538b..eb63a54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -37,6 +37,8 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_COLORS = false;
 
+    public static final boolean HIGH_END = ActivityManager.isHighEndGfx();
+
     public static final int MODE_OPAQUE = 0;
     public static final int MODE_SEMI_TRANSPARENT = 1;
     public static final int MODE_TRANSLUCENT = 2;
@@ -48,7 +50,6 @@
 
     private final String mTag;
     private final View mView;
-    private final boolean mSupportsTransitions = ActivityManager.isHighEndGfx();
     private final BarBackgroundDrawable mBarBackground;
 
     private int mMode;
@@ -57,7 +58,7 @@
         mTag = "BarTransitions." + view.getClass().getSimpleName();
         mView = view;
         mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId);
-        if (mSupportsTransitions) {
+        if (HIGH_END) {
             mView.setBackground(mBarBackground);
         }
     }
@@ -67,18 +68,22 @@
     }
 
     public void transitionTo(int mode, boolean animate) {
+        // low-end devices do not support translucent modes, fallback to opaque
+        if (!HIGH_END && (mode == MODE_SEMI_TRANSPARENT || mode == MODE_TRANSLUCENT)) {
+            mode = MODE_OPAQUE;
+        }
         if (mMode == mode) return;
         int oldMode = mMode;
         mMode = mode;
         if (DEBUG) Log.d(mTag, String.format("%s -> %s animate=%s",
                 modeToString(oldMode), modeToString(mode),  animate));
-        if (mSupportsTransitions) {
-            onTransition(oldMode, mMode, animate);
-        }
+        onTransition(oldMode, mMode, animate);
     }
 
     protected void onTransition(int oldMode, int newMode, boolean animate) {
-        applyModeBackground(oldMode, newMode, animate);
+        if (HIGH_END) {
+            applyModeBackground(oldMode, newMode, animate);
+        }
     }
 
     protected void applyModeBackground(int oldMode, int newMode, boolean animate) {
@@ -99,6 +104,10 @@
         mBarBackground.finishAnimation();
     }
 
+    public void setContentVisible(boolean visible) {
+        // for subclasses
+    }
+
     private static class BarBackgroundDrawable extends Drawable {
         private final int mOpaque;
         private final int mSemiTransparent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java
index 5c55f0d..c1646ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java
@@ -77,10 +77,11 @@
     }
 
     public static KeyguardTouchDelegate getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new KeyguardTouchDelegate(context);
+        KeyguardTouchDelegate instance = sInstance;
+        if (instance == null) {
+            instance = sInstance = new KeyguardTouchDelegate(context);
         }
-        return sInstance;
+        return instance;
     }
 
     public boolean isSecure() {
@@ -165,7 +166,21 @@
                 Slog.e(TAG, "RemoteException launching camera!", e);
             }
         } else {
-            Slog.w(TAG, "dispatch(event): NO SERVICE!");
+            Slog.w(TAG, "launchCamera(): NO SERVICE!");
+        }
+    }
+
+    public void dismiss() {
+        final IKeyguardService service = mService;
+        if (service != null) {
+            try {
+                service.dismiss();
+            } catch (RemoteException e) {
+                // What to do?
+                Slog.e(TAG, "RemoteException dismissing keyguard!", e);
+            }
+        } else {
+            Slog.w(TAG, "dismiss(): NO SERVICE!");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 5d4b995..a74230b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -30,6 +30,9 @@
 
 public final class NavigationBarTransitions extends BarTransitions {
 
+    private static final float KEYGUARD_QUIESCENT_ALPHA = 0.5f;
+    private static final int CONTENT_FADE_DURATION = 200;
+
     private final NavigationBarView mView;
     private final IStatusBarService mBarService;
 
@@ -73,18 +76,57 @@
 
     private void applyMode(int mode, boolean animate, boolean force) {
         // apply to key buttons
-        final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT;
-        final float alpha = isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f;
-        setKeyButtonViewQuiescentAlpha(mView.getBackButton(), alpha, animate);
+        final float alpha = alphaForMode(mode);
         setKeyButtonViewQuiescentAlpha(mView.getHomeButton(), alpha, animate);
         setKeyButtonViewQuiescentAlpha(mView.getRecentsButton(), alpha, animate);
         setKeyButtonViewQuiescentAlpha(mView.getMenuButton(), alpha, animate);
-        setKeyButtonViewQuiescentAlpha(mView.getCameraButton(), alpha, animate);
+
+        setKeyButtonViewQuiescentAlpha(mView.getSearchLight(), KEYGUARD_QUIESCENT_ALPHA, animate);
+        setKeyButtonViewQuiescentAlpha(mView.getCameraButton(), KEYGUARD_QUIESCENT_ALPHA, animate);
+
+        applyBackButtonQuiescentAlpha(mode, animate);
 
         // apply to lights out
         applyLightsOut(mode == MODE_LIGHTS_OUT, animate, force);
     }
 
+    private float alphaForMode(int mode) {
+        final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT;
+        return isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f;
+    }
+
+    public void applyBackButtonQuiescentAlpha(int mode, boolean animate) {
+        float backAlpha = 0;
+        backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getSearchLight());
+        backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getCameraButton());
+        backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getHomeButton());
+        backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getRecentsButton());
+        backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getMenuButton());
+        if (backAlpha > 0) {
+            setKeyButtonViewQuiescentAlpha(mView.getBackButton(), backAlpha, animate);
+        }
+    }
+
+    private static float maxVisibleQuiescentAlpha(float max, View v) {
+        if ((v instanceof KeyButtonView) && v.isShown()) {
+            return Math.max(max, ((KeyButtonView)v).getQuiescentAlpha());
+        }
+        return max;
+    }
+
+    @Override
+    public void setContentVisible(boolean visible) {
+        final float alpha = visible ? 1 : 0;
+        fadeContent(mView.getCameraButton(), alpha);
+        fadeContent(mView.getSearchLight(), alpha);
+    }
+
+    private void fadeContent(View v, float alpha) {
+        if (v != null) {
+            v.animate().alpha(alpha).setDuration(CONTENT_FADE_DURATION);
+        }
+    }
+
     private void setKeyButtonViewQuiescentAlpha(View button, float alpha, boolean animate) {
         if (button instanceof KeyButtonView) {
             ((KeyButtonView) button).setQuiescentAlpha(alpha, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index d1c4109..839016d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -17,6 +17,10 @@
 package com.android.systemui.statusbar.phone;
 
 import android.animation.LayoutTransition;
+import android.animation.LayoutTransition.TransitionListener;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
 import android.app.ActivityManagerNative;
 import android.app.StatusBarManager;
 import android.app.admin.DevicePolicyManager;
@@ -48,12 +52,12 @@
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.DelegateViewHelper;
 import com.android.systemui.statusbar.policy.DeadZone;
+import com.android.systemui.statusbar.policy.KeyButtonView;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 public class NavigationBarView extends LinearLayout {
-    private static final int CAMERA_BUTTON_FADE_DURATION = 200;
     final static boolean DEBUG = false;
     final static String TAG = "PhoneStatusBar/NavigationBarView";
 
@@ -89,6 +93,54 @@
     // used to disable the camera icon in navbar when disabled by DPM
     private boolean mCameraDisabledByDpm;
 
+    // performs manual animation in sync with layout transitions
+    private final NavTransitionListener mTransitionListener = new NavTransitionListener();
+
+    private class NavTransitionListener implements TransitionListener {
+        private boolean mBackTransitioning;
+        private boolean mHomeAppearing;
+        private long mStartDelay;
+        private long mDuration;
+        private TimeInterpolator mInterpolator;
+
+        @Override
+        public void startTransition(LayoutTransition transition, ViewGroup container,
+                View view, int transitionType) {
+            if (view.getId() == R.id.back) {
+                mBackTransitioning = true;
+            } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
+                mHomeAppearing = true;
+                mStartDelay = transition.getStartDelay(transitionType);
+                mDuration = transition.getDuration(transitionType);
+                mInterpolator = transition.getInterpolator(transitionType);
+            }
+        }
+
+        @Override
+        public void endTransition(LayoutTransition transition, ViewGroup container,
+                View view, int transitionType) {
+            if (view.getId() == R.id.back) {
+                mBackTransitioning = false;
+            } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
+                mHomeAppearing = false;
+            }
+        }
+
+        public void onBackAltCleared() {
+            // When dismissing ime during unlock, force the back button to run the same appearance
+            // animation as home (if we catch this condition early enough).
+            if (!mBackTransitioning && getBackButton().getVisibility() == VISIBLE
+                    && mHomeAppearing && getHomeButton().getAlpha() == 0) {
+                getBackButton().setAlpha(0);
+                ValueAnimator a = ObjectAnimator.ofFloat(getBackButton(), "alpha", 0, 1);
+                a.setStartDelay(mStartDelay);
+                a.setDuration(mDuration);
+                a.setInterpolator(mInterpolator);
+                a.start();
+            }
+        }
+    }
+
     // simplified click handler to be used when device is in accessibility mode
     private final OnClickListener mAccessibilityClickListener = new OnClickListener() {
         @Override
@@ -108,12 +160,12 @@
                 case MotionEvent.ACTION_DOWN:
                     // disable search gesture while interacting with camera
                     mDelegateHelper.setDisabled(true);
-                    transitionCameraAndSearchButtonAlpha(0.0f);
+                    mBarTransitions.setContentVisible(false);
                     break;
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL:
                     mDelegateHelper.setDisabled(false);
-                    transitionCameraAndSearchButtonAlpha(1.0f);
+                    mBarTransitions.setContentVisible(true);
                     break;
             }
             return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event);
@@ -163,17 +215,6 @@
         watchForDevicePolicyChanges();
     }
 
-    protected void transitionCameraAndSearchButtonAlpha(float alpha) {
-        View cameraButtonView = getCameraButton();
-        if (cameraButtonView != null) {
-            cameraButtonView.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION);
-        }
-        View searchLight = getSearchLight();
-        if (searchLight != null) {
-            searchLight.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION);
-        }
-    }
-
     private void watchForDevicePolicyChanges() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
@@ -277,7 +318,10 @@
 
     public void setNavigationIconHints(int hints, boolean force) {
         if (!force && hints == mNavigationIconHints) return;
-
+        final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+        if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) {
+            mTransitionListener.onBackAltCleared();
+        }
         if (DEBUG) {
             android.widget.Toast.makeText(mContext,
                 "Navigation icon hints = " + hints,
@@ -286,15 +330,7 @@
 
         mNavigationIconHints = hints;
 
-        getBackButton().setAlpha(
-            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f);
-        getHomeButton().setAlpha(
-            (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f);
-        getRecentsButton().setAlpha(
-            (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f);
-
-        ((ImageView)getBackButton()).setImageDrawable(
-            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT))
+        ((ImageView)getBackButton()).setImageDrawable(backAlt
                 ? (mVertical ? mBackAltLandIcon : mBackAltIcon)
                 : (mVertical ? mBackLandIcon : mBackIcon));
 
@@ -322,13 +358,20 @@
             setSlippery(disableHome && disableRecent && disableBack && disableSearch);
         }
 
-        if (!mScreenOn && mCurrentView != null) {
-            ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons);
-            LayoutTransition lt = navButtons == null ? null : navButtons.getLayoutTransition();
+        ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons);
+        if (navButtons != null) {
+            LayoutTransition lt = navButtons.getLayoutTransition();
             if (lt != null) {
-                lt.disableTransitionType(
-                        LayoutTransition.CHANGE_APPEARING | LayoutTransition.CHANGE_DISAPPEARING |
-                        LayoutTransition.APPEARING | LayoutTransition.DISAPPEARING);
+                if (!lt.getTransitionListeners().contains(mTransitionListener)) {
+                    lt.addTransitionListener(mTransitionListener);
+                }
+                if (!mScreenOn && mCurrentView != null) {
+                    lt.disableTransitionType(
+                            LayoutTransition.CHANGE_APPEARING |
+                            LayoutTransition.CHANGE_DISAPPEARING |
+                            LayoutTransition.APPEARING |
+                            LayoutTransition.DISAPPEARING);
+                }
             }
         }
 
@@ -336,12 +379,17 @@
         getHomeButton()   .setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
         getRecentsButton().setVisibility(disableRecent     ? View.INVISIBLE : View.VISIBLE);
 
-        final boolean shouldShowSearch = disableHome && !disableSearch;
-        getSearchLight().setVisibility(shouldShowSearch ? View.VISIBLE : View.GONE);
-        final View cameraButton = getCameraButton();
-        if (cameraButton != null) {
-            cameraButton.setVisibility(
-                    shouldShowSearch && !mCameraDisabledByDpm ? View.VISIBLE : View.GONE);
+        final boolean showSearch = disableHome && !disableSearch;
+        final boolean showCamera = showSearch && !mCameraDisabledByDpm;
+        setVisibleOrGone(getSearchLight(), showSearch);
+        setVisibleOrGone(getCameraButton(), showCamera);
+
+        mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/);
+    }
+
+    private void setVisibleOrGone(View view, boolean visible) {
+        if (view != null) {
+            view.setVisibility(visible ? VISIBLE : GONE);
         }
     }
 
@@ -574,28 +622,31 @@
                         mVertical ? "true" : "false",
                         mShowMenu ? "true" : "false"));
 
-        final View back = getBackButton();
-        final View home = getHomeButton();
-        final View recent = getRecentsButton();
-        final View menu = getMenuButton();
+        dumpButton(pw, "back", getBackButton());
+        dumpButton(pw, "home", getHomeButton());
+        dumpButton(pw, "rcnt", getRecentsButton());
+        dumpButton(pw, "menu", getMenuButton());
+        dumpButton(pw, "srch", getSearchLight());
+        dumpButton(pw, "cmra", getCameraButton());
 
-        pw.println("      back: "
-                + PhoneStatusBar.viewInfo(back)
-                + " " + visibilityToString(back.getVisibility())
-                );
-        pw.println("      home: "
-                + PhoneStatusBar.viewInfo(home)
-                + " " + visibilityToString(home.getVisibility())
-                );
-        pw.println("      rcnt: "
-                + PhoneStatusBar.viewInfo(recent)
-                + " " + visibilityToString(recent.getVisibility())
-                );
-        pw.println("      menu: "
-                + PhoneStatusBar.viewInfo(menu)
-                + " " + visibilityToString(menu.getVisibility())
-                );
         pw.println("    }");
     }
 
+    private static void dumpButton(PrintWriter pw, String caption, View button) {
+        pw.print("      " + caption + ": ");
+        if (button == null) {
+            pw.print("null");
+        } else {
+            pw.print(PhoneStatusBar.viewInfo(button)
+                    + " " + visibilityToString(button.getVisibility())
+                    + " alpha=" + button.getAlpha()
+                    );
+            if (button instanceof KeyButtonView) {
+                pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha());
+                pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha());
+            }
+        }
+        pw.println();
+    }
+
 }
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 39a9ba7..bbac4ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -53,6 +53,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -632,6 +633,10 @@
             }
         }
 
+        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF));
+
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -649,14 +654,14 @@
     @Override
     protected void onShowSearchPanel() {
         if (mNavigationBarView != null) {
-            mNavigationBarView.transitionCameraAndSearchButtonAlpha(0.0f);
+            mNavigationBarView.getBarTransitions().setContentVisible(false);
         }
     }
 
     @Override
     protected void onHideSearchPanel() {
         if (mNavigationBarView != null) {
-            mNavigationBarView.transitionCameraAndSearchButtonAlpha(1.0f);
+            mNavigationBarView.getBarTransitions().setContentVisible(true);
         }
     }
 
@@ -802,7 +807,7 @@
     }
 
     private void repositionNavigationBar() {
-        if (mNavigationBarView == null) return;
+        if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
 
         prepareNavigationBarView();
 
@@ -1807,8 +1812,7 @@
         return mGestureRec;
     }
 
-    @Override // CommandQueue
-    public void setNavigationIconHints(int hints) {
+    private void setNavigationIconHints(int hints) {
         if (hints == mNavigationIconHints) return;
 
         mNavigationIconHints = hints;
@@ -2045,7 +2049,7 @@
         boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS)
             || ((vis & InputMethodService.IME_VISIBLE) != 0);
 
-        mCommandQueue.setNavigationIconHints(
+        setNavigationIconHints(
                 altBack ? (mNavigationIconHints | NAVIGATION_HINT_BACK_ALT)
                         : (mNavigationIconHints & ~NAVIGATION_HINT_BACK_ALT));
         if (mQS != null) mQS.setImeWindowStatus(vis > 0);
@@ -2101,9 +2105,12 @@
         }
 
         public void tickerHalting() {
-            mStatusBarContents.setVisibility(View.VISIBLE);
+            if (mStatusBarContents.getVisibility() != View.VISIBLE) {
+                mStatusBarContents.setVisibility(View.VISIBLE);
+                mStatusBarContents
+                        .startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
+            }
             mTickerView.setVisibility(View.GONE);
-            mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
             // we do not animate the ticker away at this point, just get rid of it (b/6992707)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index 37504fb..e7b8fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -37,9 +37,8 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LevelListDrawable;
 import android.hardware.display.DisplayManager;
-import android.hardware.display.WifiDisplayStatus;
+import android.media.MediaRouter;
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -62,6 +61,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.internal.app.MediaRouteDialogPresenter;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.QuickSettingsModel.ActivityState;
 import com.android.systemui.statusbar.phone.QuickSettingsModel.BluetoothState;
@@ -92,9 +92,7 @@
     private QuickSettingsModel mModel;
     private ViewGroup mContainerView;
 
-    private DisplayManager mDisplayManager;
     private DevicePolicyManager mDevicePolicyManager;
-    private WifiDisplayStatus mWifiDisplayStatus;
     private PhoneStatusBar mStatusBarService;
     private BluetoothState mBluetoothState;
     private BluetoothAdapter mBluetoothAdapter;
@@ -118,13 +116,11 @@
             new ArrayList<QuickSettingsTileView>();
 
     public QuickSettings(Context context, QuickSettingsContainerView container) {
-        mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
         mDevicePolicyManager
             = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
         mContext = context;
         mContainerView = container;
         mModel = new QuickSettingsModel(context);
-        mWifiDisplayStatus = new WifiDisplayStatus();
         mBluetoothState = new QuickSettingsModel.BluetoothState();
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
@@ -171,7 +167,6 @@
         mLocationController = locationController;
 
         setupQuickSettings();
-        updateWifiDisplayStatus();
         updateResources();
         applyLocationEnabledStatus();
 
@@ -314,11 +309,19 @@
                 collapsePanels();
                 final UserManager um = UserManager.get(mContext);
                 if (um.getUsers(true).size() > 1) {
-                    try {
-                        WindowManagerGlobal.getWindowManagerService().lockNow(null);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Couldn't show user switcher", e);
-                    }
+                    // Since keyguard and systemui were merged into the same process to save
+                    // memory, they share the same Looper and graphics context.  As a result,
+                    // there's no way to allow concurrent animation while keyguard inflates.
+                    // The workaround is to add a slight delay to allow the animation to finish.
+                    mHandler.postDelayed(new Runnable() {
+                        public void run() {
+                            try {
+                                WindowManagerGlobal.getWindowManagerService().lockNow(null);
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Couldn't show user switcher", e);
+                            }
+                        }
+                    }, 400); // TODO: ideally this would be tied to the collapse of the panel
                 } else {
                     Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
                             mContext, v, ContactsContract.Profile.CONTENT_URI,
@@ -668,20 +671,33 @@
         });
         parent.addView(alarmTile);
 
-        // Wifi Display
-        QuickSettingsBasicTile wifiDisplayTile
+        // Remote Display
+        QuickSettingsBasicTile remoteDisplayTile
                 = new QuickSettingsBasicTile(mContext);
-        wifiDisplayTile.setImageResource(R.drawable.ic_qs_remote_display);
-        wifiDisplayTile.setOnClickListener(new View.OnClickListener() {
+        remoteDisplayTile.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                startSettingsActivity(android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS);
+                collapsePanels();
+
+                final Dialog[] dialog = new Dialog[1];
+                dialog[0] = MediaRouteDialogPresenter.createDialog(mContext,
+                        MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+                        new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        dialog[0].dismiss();
+                        startSettingsActivity(
+                                android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS);
+                    }
+                });
+                dialog[0].getWindow().setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+                dialog[0].show();
             }
         });
-        mModel.addWifiDisplayTile(wifiDisplayTile,
-                new QuickSettingsModel.BasicRefreshCallback(wifiDisplayTile)
+        mModel.addRemoteDisplayTile(remoteDisplayTile,
+                new QuickSettingsModel.BasicRefreshCallback(remoteDisplayTile)
                         .setShowWhenEnabled(true));
-        parent.addView(wifiDisplayTile);
+        parent.addView(remoteDisplayTile);
 
         if (SHOW_IME_TILE || DEBUG_GONE_TILES) {
             // IME
@@ -816,15 +832,6 @@
         dialog.show();
     }
 
-    private void updateWifiDisplayStatus() {
-        mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus();
-        applyWifiDisplayStatus();
-    }
-
-    private void applyWifiDisplayStatus() {
-        mModel.onWifiDisplayStateChanged(mWifiDisplayStatus);
-    }
-
     private void applyBluetoothStatus() {
         mModel.onBluetoothStateChange(mBluetoothState);
     }
@@ -848,12 +855,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
-            if (DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED.equals(action)) {
-                WifiDisplayStatus status = (WifiDisplayStatus)intent.getParcelableExtra(
-                        DisplayManager.EXTRA_WIFI_DISPLAY_STATUS);
-                mWifiDisplayStatus = status;
-                applyWifiDisplayStatus();
-            } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
+            if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
                 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                         BluetoothAdapter.ERROR);
                 mBluetoothState.enabled = (state == BluetoothAdapter.STATE_ON);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
index 15d655c..42201c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
@@ -27,7 +27,8 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.drawable.Drawable;
-import android.hardware.display.WifiDisplayStatus;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteInfo;
 import android.net.ConnectivityManager;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -57,7 +58,6 @@
         BrightnessStateChangeCallback,
         RotationLockControllerCallback,
         LocationSettingsChangeCallback {
-
     // Sett InputMethoManagerService
     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
 
@@ -199,6 +199,30 @@
         }
     }
 
+    /** Callback for changes to remote display routes. */
+    private class RemoteDisplayRouteCallback extends MediaRouter.SimpleCallback {
+        @Override
+        public void onRouteAdded(MediaRouter router, RouteInfo route) {
+            updateRemoteDisplays();
+        }
+        @Override
+        public void onRouteChanged(MediaRouter router, RouteInfo route) {
+            updateRemoteDisplays();
+        }
+        @Override
+        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
+            updateRemoteDisplays();
+        }
+        @Override
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo route) {
+            updateRemoteDisplays();
+        }
+        @Override
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
+            updateRemoteDisplays();
+        }
+    }
+
     private final Context mContext;
     private final Handler mHandler;
     private final CurrentUserTracker mUserTracker;
@@ -206,6 +230,9 @@
     private final BugreportObserver mBugreportObserver;
     private final BrightnessObserver mBrightnessObserver;
 
+    private final MediaRouter mMediaRouter;
+    private final RemoteDisplayRouteCallback mRemoteDisplayRouteCallback;
+
     private final boolean mHasMobileData;
 
     private QuickSettingsTileView mUserTile;
@@ -228,9 +255,9 @@
     private RefreshCallback mWifiCallback;
     private WifiState mWifiState = new WifiState();
 
-    private QuickSettingsTileView mWifiDisplayTile;
-    private RefreshCallback mWifiDisplayCallback;
-    private State mWifiDisplayState = new State();
+    private QuickSettingsTileView mRemoteDisplayTile;
+    private RefreshCallback mRemoteDisplayCallback;
+    private State mRemoteDisplayState = new State();
 
     private QuickSettingsTileView mRSSITile;
     private RefreshCallback mRSSICallback;
@@ -278,12 +305,14 @@
         mContext = context;
         mHandler = new Handler();
         mUserTracker = new CurrentUserTracker(mContext) {
+            @Override
             public void onUserSwitched(int newUserId) {
                 mBrightnessObserver.startObserving();
                 refreshRotationLockTile();
                 onBrightnessLevelChanged();
                 onNextAlarmChanged();
                 onBugreportChanged();
+                rebindMediaRouterAsCurrentUser();
             }
         };
 
@@ -294,6 +323,11 @@
         mBrightnessObserver = new BrightnessObserver(mHandler);
         mBrightnessObserver.startObserving();
 
+        mMediaRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        rebindMediaRouterAsCurrentUser();
+
+        mRemoteDisplayRouteCallback = new RemoteDisplayRouteCallback();
+
         ConnectivityManager cm = (ConnectivityManager)
                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
         mHasMobileData = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
@@ -621,24 +655,58 @@
         mBugreportCallback.refreshView(mBugreportTile, mBugreportState);
     }
 
-    // Wifi Display
-    void addWifiDisplayTile(QuickSettingsTileView view, RefreshCallback cb) {
-        mWifiDisplayTile = view;
-        mWifiDisplayCallback = cb;
-    }
-    public void onWifiDisplayStateChanged(WifiDisplayStatus status) {
-        mWifiDisplayState.enabled =
-                (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON);
-        if (status.getActiveDisplay() != null) {
-            mWifiDisplayState.label = status.getActiveDisplay().getFriendlyDisplayName();
-            mWifiDisplayState.iconId = R.drawable.ic_qs_remote_display_connected;
-        } else {
-            mWifiDisplayState.label = mContext.getString(
-                    R.string.quick_settings_wifi_display_no_connection_label);
-            mWifiDisplayState.iconId = R.drawable.ic_qs_remote_display;
-        }
-        mWifiDisplayCallback.refreshView(mWifiDisplayTile, mWifiDisplayState);
+    // Remote Display
+    void addRemoteDisplayTile(QuickSettingsTileView view, RefreshCallback cb) {
+        mRemoteDisplayTile = view;
+        mRemoteDisplayCallback = cb;
+        final int[] count = new int[1];
+        mRemoteDisplayTile.setOnPrepareListener(new QuickSettingsTileView.OnPrepareListener() {
+            @Override
+            public void onPrepare() {
+                mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+                        mRemoteDisplayRouteCallback,
+                        MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+                updateRemoteDisplays();
+            }
+            @Override
+            public void onUnprepare() {
+                mMediaRouter.removeCallback(mRemoteDisplayRouteCallback);
+            }
+        });
 
+        updateRemoteDisplays();
+    }
+
+    private void rebindMediaRouterAsCurrentUser() {
+        mMediaRouter.rebindAsUser(mUserTracker.getCurrentUserId());
+    }
+
+    private void updateRemoteDisplays() {
+        MediaRouter.RouteInfo connectedRoute = mMediaRouter.getSelectedRoute(
+                MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
+        boolean enabled = connectedRoute != null
+                && connectedRoute.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
+        boolean connecting;
+        if (enabled) {
+            connecting = connectedRoute.isConnecting();
+        } else {
+            connectedRoute = null;
+            connecting = false;
+            enabled = mMediaRouter.isRouteAvailable(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+                    MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
+        }
+
+        mRemoteDisplayState.enabled = enabled;
+        if (connectedRoute != null) {
+            mRemoteDisplayState.label = connectedRoute.getName().toString();
+            mRemoteDisplayState.iconId = connecting ?
+                    R.drawable.ic_qs_cast_connecting : R.drawable.ic_qs_cast_connected;
+        } else {
+            mRemoteDisplayState.label = mContext.getString(
+                    R.string.quick_settings_remote_display_no_connection_label);
+            mRemoteDisplayState.iconId = R.drawable.ic_qs_cast_available;
+        }
+        mRemoteDisplayCallback.refreshView(mRemoteDisplayTile, mRemoteDisplayState);
     }
 
     // IME
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java
index 3d520f7..ad18294 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java
@@ -21,6 +21,7 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewParent;
 import android.widget.FrameLayout;
 
 /**
@@ -31,14 +32,14 @@
 
     private int mContentLayoutId;
     private int mColSpan;
-    private int mRowSpan;
+    private boolean mPrepared;
+    private OnPrepareListener mOnPrepareListener;
 
     public QuickSettingsTileView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         mContentLayoutId = -1;
         mColSpan = 1;
-        mRowSpan = 1;
     }
 
     void setColumnSpan(int span) {
@@ -77,4 +78,72 @@
         }
         super.setVisibility(vis);
     }
+
+    public void setOnPrepareListener(OnPrepareListener listener) {
+        if (mOnPrepareListener != listener) {
+            mOnPrepareListener = listener;
+            mPrepared = false;
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    updatePreparedState();
+                }
+            });
+        }
+    }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        updatePreparedState();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        updatePreparedState();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        updatePreparedState();
+    }
+
+    private void updatePreparedState() {
+        if (mOnPrepareListener != null) {
+            if (isParentVisible()) {
+                if (!mPrepared) {
+                    mPrepared = true;
+                    mOnPrepareListener.onPrepare();
+                }
+            } else if (mPrepared) {
+                mPrepared = false;
+                mOnPrepareListener.onUnprepare();
+            }
+        }
+    }
+
+    private boolean isParentVisible() {
+        if (!isAttachedToWindow()) {
+            return false;
+        }
+        for (ViewParent current = getParent(); current instanceof View;
+                current = current.getParent()) {
+            View view = (View)current;
+            if (view.getVisibility() != VISIBLE) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Called when the view's parent becomes visible or invisible to provide
+     * an opportunity for the client to provide new content.
+     */
+    public interface OnPrepareListener {
+        void onPrepare();
+        void onUnprepare();
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index e77b420..4901823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -113,7 +113,7 @@
             handled = super.onTouchEvent(ev);
         }
         final int action = ev.getAction();
-        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+        if (!handled && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) {
             mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         }
         return handled;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
index dca5e41..6eb88be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
@@ -22,7 +22,7 @@
 import android.graphics.Canvas;
 import android.os.SystemClock;
 import android.util.AttributeSet;
-import android.util.Log;
+import android.util.Slog;
 import android.view.MotionEvent;
 import android.view.View;
 
@@ -75,7 +75,7 @@
         mVertical = (index == VERTICAL);
 
         if (DEBUG)
-            Log.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold
+            Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold
                     + (mVertical ? " vertical" : " horizontal"));
 
         setFlashOnTouchCapture(context.getResources().getBoolean(R.bool.config_dead_zone_flash));
@@ -106,7 +106,7 @@
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (DEBUG) {
-            Log.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction()));
+            Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction()));
         }
 
         final int action = event.getAction();
@@ -114,12 +114,12 @@
             poke(event);
         } else if (action == MotionEvent.ACTION_DOWN) {
             if (DEBUG) {
-                Log.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY());
+                Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY());
             }
             int size = (int) getSize(event.getEventTime());
             if ((mVertical && event.getX() < size) || event.getY() < size) {
                 if (CHATTY) {
-                    Log.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")");
+                    Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")");
                 }
                 if (mShouldFlash) {
                     post(mDebugFlash);
@@ -134,7 +134,7 @@
     public void poke(MotionEvent event) {
         mLastPokeTime = event.getEventTime();
         if (DEBUG)
-            Log.v(TAG, "poked! size=" + getSize(mLastPokeTime));
+            Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime));
         if (mShouldFlash) postInvalidate();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 55fb95d..718acc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -36,6 +36,7 @@
 import android.view.SoundEffectConstants;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewDebug;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.ImageView;
 
@@ -53,10 +54,13 @@
     int mTouchSlop;
     Drawable mGlowBG;
     int mGlowWidth, mGlowHeight;
-    float mGlowAlpha = 0f, mGlowScale = 1f, mDrawingAlpha = 1f;
+    float mGlowAlpha = 0f, mGlowScale = 1f;
+    @ViewDebug.ExportedProperty(category = "drawing")
+    float mDrawingAlpha = 1f;
+    @ViewDebug.ExportedProperty(category = "drawing")
     float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA;
     boolean mSupportsLongpress = true;
-    RectF mRect = new RectF(0f,0f,0f,0f);
+    RectF mRect = new RectF();
     AnimatorSet mPressedAnim;
     Animator mAnimateToQuiescent = new ObjectAnimator();
 
@@ -90,8 +94,8 @@
         mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
 
         mGlowBG = a.getDrawable(R.styleable.KeyButtonView_glowBackground);
+        setDrawingAlpha(mQuiescentAlpha);
         if (mGlowBG != null) {
-            setDrawingAlpha(mQuiescentAlpha);
             mGlowWidth = mGlowBG.getIntrinsicWidth();
             mGlowHeight = mGlowBG.getIntrinsicHeight();
         }
@@ -126,16 +130,14 @@
     public void setQuiescentAlpha(float alpha, boolean animate) {
         mAnimateToQuiescent.cancel();
         alpha = Math.min(Math.max(alpha, 0), 1);
-        if (alpha == mQuiescentAlpha) return;
+        if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return;
         mQuiescentAlpha = alpha;
         if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha);
-        if (mGlowBG != null) {
-            if (animate) {
-                mAnimateToQuiescent = animateToQuiescent();
-                mAnimateToQuiescent.start();
-            } else {
-                setDrawingAlpha(mQuiescentAlpha);
-            }
+        if (mGlowBG != null && animate) {
+            mAnimateToQuiescent = animateToQuiescent();
+            mAnimateToQuiescent.start();
+        } else {
+            setDrawingAlpha(mQuiescentAlpha);
         }
     }
 
@@ -143,13 +145,15 @@
         return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha);
     }
 
+    public float getQuiescentAlpha() {
+        return mQuiescentAlpha;
+    }
+
     public float getDrawingAlpha() {
-        if (mGlowBG == null) return 0;
         return mDrawingAlpha;
     }
 
     public void setDrawingAlpha(float x) {
-        if (mGlowBG == null) return;
         // Calling setAlpha(int), which is an ImageView-specific
         // method that's different from setAlpha(float). This sets
         // the alpha on this ImageView's drawable directly
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index a53b25a..dd13e31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -89,10 +89,6 @@
     }
 
     @Override // CommandQueue
-    public void setNavigationIconHints(int hints) {
-    }
-
-    @Override // CommandQueue
     public void setWindowState(int window, int state) {
     }
 
diff --git a/packages/WallpaperCropper/res/layout/actionbar_set_wallpaper.xml b/packages/WallpaperCropper/res/layout/actionbar_set_wallpaper.xml
index 1622742..2a0188a 100644
--- a/packages/WallpaperCropper/res/layout/actionbar_set_wallpaper.xml
+++ b/packages/WallpaperCropper/res/layout/actionbar_set_wallpaper.xml
@@ -20,6 +20,7 @@
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     style="?android:actionButtonStyle"
+    android:id="@+id/set_wallpaper_button"
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
     <TextView style="?android:actionBarTabTextStyle"
diff --git a/packages/WallpaperCropper/res/values/config.xml b/packages/WallpaperCropper/res/values/config.xml
index 1b24190..2e2fa6f 100644
--- a/packages/WallpaperCropper/res/values/config.xml
+++ b/packages/WallpaperCropper/res/values/config.xml
@@ -15,4 +15,7 @@
 -->
 <resources>
     <bool name="allow_rotation">false</bool>
+    <!-- Specifies whether to expand the cropped area on both sides (rather
+         than just to one side) -->
+    <bool name="center_crop">true</bool>
 </resources>
diff --git a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java
index e14f89a..8511de2 100644
--- a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java
+++ b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java
@@ -24,6 +24,9 @@
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Build;
@@ -53,7 +56,8 @@
     private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) {
         mDecoder = decoder;
     }
-    public static SimpleBitmapRegionDecoderWrapper newInstance(String pathName, boolean isShareable) {
+    public static SimpleBitmapRegionDecoderWrapper newInstance(
+            String pathName, boolean isShareable) {
         try {
             BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(pathName, isShareable);
             if (d != null) {
@@ -65,7 +69,8 @@
         }
         return null;
     }
-    public static SimpleBitmapRegionDecoderWrapper newInstance(InputStream is, boolean isShareable) {
+    public static SimpleBitmapRegionDecoderWrapper newInstance(
+            InputStream is, boolean isShareable) {
         try {
             BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(is, isShareable);
             if (d != null) {
@@ -89,8 +94,9 @@
 }
 
 class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder {
-    //byte[] streamCopy;
     Bitmap mBuffer;
+    Canvas mTempCanvas;
+    Paint mTempPaint;
     private DumbBitmapRegionDecoder(Bitmap b) {
         mBuffer = b;
     }
@@ -115,9 +121,23 @@
         return mBuffer.getHeight();
     }
     public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
-        System.out.println("DECODING WITH SAMPLE LEVEL OF " + options.inSampleSize);
-        return Bitmap.createBitmap(
-                mBuffer, wantRegion.left, wantRegion.top, wantRegion.width(), wantRegion.height());
+        if (mTempCanvas == null) {
+            mTempCanvas = new Canvas();
+            mTempPaint = new Paint();
+            mTempPaint.setFilterBitmap(true);
+        }
+        int sampleSize = Math.max(options.inSampleSize, 1);
+        Bitmap newBitmap = Bitmap.createBitmap(
+                wantRegion.width() / sampleSize,
+                wantRegion.height() / sampleSize,
+                Bitmap.Config.ARGB_8888);
+        mTempCanvas.setBitmap(newBitmap);
+        mTempCanvas.save();
+        mTempCanvas.scale(1f / sampleSize, 1f / sampleSize);
+        mTempCanvas.drawBitmap(mBuffer, -wantRegion.left, -wantRegion.top, mTempPaint);
+        mTempCanvas.restore();
+        mTempCanvas.setBitmap(null);
+        return newBitmap;
     }
 }
 
@@ -256,6 +276,7 @@
                 if (regionDecoder == null) {
                     is = regenerateInputStream();
                     regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
+                    Utils.closeSilently(is);
                 }
                 return regionDecoder;
             } catch (FileNotFoundException e) {
@@ -280,8 +301,9 @@
         }
         @Override
         public boolean readExif(ExifInterface ei) {
+            InputStream is = null;
             try {
-                InputStream is = regenerateInputStream();
+                is = regenerateInputStream();
                 ei.readExif(is);
                 Utils.closeSilently(is);
                 return true;
@@ -291,6 +313,8 @@
             } catch (IOException e) {
                 Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                 return false;
+            } finally {
+                Utils.closeSilently(is);
             }
         }
     }
@@ -316,6 +340,7 @@
             if (regionDecoder == null) {
                 is = regenerateInputStream();
                 regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
+                Utils.closeSilently(is);
             }
             return regionDecoder;
         }
diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java
index 14f7c1d..c3ef302 100644
--- a/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java
+++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java
@@ -165,7 +165,8 @@
                 final float imageWidth = imageDims[0];
                 final float imageHeight = imageDims[1];
                 mMinScale = Math.max(w / imageWidth, h / imageHeight);
-                mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
+                mRenderer.scale =
+                        Math.max(mMinScale, resetScale ? Float.MIN_VALUE : mRenderer.scale);
             }
         }
     }
diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java
index d12140d..a070e5e 100644
--- a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java
+++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java
@@ -73,6 +73,7 @@
 
     protected CropView mCropView;
     protected Uri mUri;
+    private View mSetWallpaperButton;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -109,10 +110,12 @@
                         cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
                     }
                 });
+        mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
 
         // Load image in background
         final BitmapRegionTileSource.UriBitmapSource bitmapSource =
                 new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024);
+        mSetWallpaperButton.setVisibility(View.INVISIBLE);
         Runnable onLoad = new Runnable() {
             public void run() {
                 if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
@@ -120,6 +123,8 @@
                             getString(R.string.wallpaper_load_fail),
                             Toast.LENGTH_LONG).show();
                     finish();
+                } else {
+                    mSetWallpaperButton.setVisibility(View.VISIBLE);
                 }
             }
         };
@@ -247,19 +252,19 @@
     private static int getRotationFromExifHelper(
             String path, Resources res, int resId, Context context, Uri uri) {
         ExifInterface ei = new ExifInterface();
+        InputStream is = null;
+        BufferedInputStream bis = null;
         try {
             if (path != null) {
                 ei.readExif(path);
             } else if (uri != null) {
-                InputStream is = context.getContentResolver().openInputStream(uri);
-                BufferedInputStream bis = new BufferedInputStream(is);
+                is = context.getContentResolver().openInputStream(uri);
+                bis = new BufferedInputStream(is);
                 ei.readExif(bis);
-                bis.close();
             } else {
-                InputStream is = res.openRawResource(resId);
-                BufferedInputStream bis = new BufferedInputStream(is);
+                is = res.openRawResource(resId);
+                bis = new BufferedInputStream(is);
                 ei.readExif(bis);
-                bis.close();
             }
             Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
             if (ori != null) {
@@ -267,6 +272,9 @@
             }
         } catch (IOException e) {
             Log.w(LOGTAG, "Getting exif data failed", e);
+        } finally {
+            Utils.closeSilently(bis);
+            Utils.closeSilently(is);
         }
         return 0;
     }
@@ -323,49 +331,26 @@
 
     protected void cropImageAndSetWallpaper(Uri uri,
             OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
+        boolean centerCrop = getResources().getBoolean(R.bool.center_crop);
         // Get the crop
         boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
 
-        Point minDims = new Point();
-        Point maxDims = new Point();
         Display d = getWindowManager().getDefaultDisplay();
-        d.getCurrentSizeRange(minDims, maxDims);
 
         Point displaySize = new Point();
         d.getSize(displaySize);
-
-        int maxDim = Math.max(maxDims.x, maxDims.y);
-        final int minDim = Math.min(minDims.x, minDims.y);
-        int defaultWallpaperWidth;
-        if (isScreenLarge(getResources())) {
-            defaultWallpaperWidth = (int) (maxDim *
-                    wallpaperTravelToScreenWidthRatio(maxDim, minDim));
-        } else {
-            defaultWallpaperWidth = Math.max((int)
-                    (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
-        }
-
         boolean isPortrait = displaySize.x < displaySize.y;
-        int portraitHeight;
-        if (isPortrait) {
-            portraitHeight = mCropView.getHeight();
-        } else {
-            // TODO: how to actually get the proper portrait height?
-            // This is not quite right:
-            portraitHeight = Math.max(maxDims.x, maxDims.y);
-        }
-        if (android.os.Build.VERSION.SDK_INT >=
-                android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            Point realSize = new Point();
-            d.getRealSize(realSize);
-            portraitHeight = Math.max(realSize.x, realSize.y);
-        }
+
+        Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(),
+                getWindowManager());
         // Get the crop
         RectF cropRect = mCropView.getCrop();
+
+        Point inSize = mCropView.getSourceDimensions();
+
         int cropRotation = mCropView.getImageRotation();
         float cropScale = mCropView.getWidth() / (float) cropRect.width();
 
-        Point inSize = mCropView.getSourceDimensions();
         Matrix rotateMatrix = new Matrix();
         rotateMatrix.setRotate(cropRotation);
         float[] rotatedInSize = new float[] { inSize.x, inSize.y };
@@ -373,26 +358,43 @@
         rotatedInSize[0] = Math.abs(rotatedInSize[0]);
         rotatedInSize[1] = Math.abs(rotatedInSize[1]);
 
+        // Due to rounding errors in the cropview renderer the edges can be slightly offset
+        // therefore we ensure that the boundaries are sanely defined
+        cropRect.left = Math.max(0, cropRect.left);
+        cropRect.right = Math.min(rotatedInSize[0], cropRect.right);
+        cropRect.top = Math.max(0, cropRect.top);
+        cropRect.bottom = Math.min(rotatedInSize[1], cropRect.bottom);
+
         // ADJUST CROP WIDTH
         // Extend the crop all the way to the right, for parallax
         // (or all the way to the left, in RTL)
-        float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
+        float extraSpace;
+        if (centerCrop) {
+            extraSpace = 2f * Math.min(rotatedInSize[0] - cropRect.right, cropRect.left);
+        } else {
+            extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
+        }
         // Cap the amount of extra width
-        float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width();
+        float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width();
         extraSpace = Math.min(extraSpace, maxExtraSpace);
 
-        if (ltr) {
-            cropRect.right += extraSpace;
+        if (centerCrop) {
+            cropRect.left -= extraSpace / 2f;
+            cropRect.right += extraSpace / 2f;
         } else {
-            cropRect.left -= extraSpace;
+            if (ltr) {
+                cropRect.right += extraSpace;
+            } else {
+                cropRect.left -= extraSpace;
+            }
         }
 
         // ADJUST CROP HEIGHT
         if (isPortrait) {
-            cropRect.bottom = cropRect.top + portraitHeight / cropScale;
+            cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale;
         } else { // LANDSCAPE
             float extraPortraitHeight =
-                    portraitHeight / cropScale - cropRect.height();
+                    defaultWallpaperSize.y / cropScale - cropRect.height();
             float expandHeight =
                     Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
                             extraPortraitHeight / 2);
@@ -572,6 +574,8 @@
                 Rect roundedTrueCrop = new Rect();
                 Matrix rotateMatrix = new Matrix();
                 Matrix inverseRotateMatrix = new Matrix();
+
+                Point bounds = getImageBounds();
                 if (mRotation > 0) {
                     rotateMatrix.setRotate(mRotation);
                     inverseRotateMatrix.setRotate(-mRotation);
@@ -579,7 +583,6 @@
                     mCropBounds.roundOut(roundedTrueCrop);
                     mCropBounds = new RectF(roundedTrueCrop);
 
-                    Point bounds = getImageBounds();
                     if (bounds == null) {
                         Log.w(LOGTAG, "cannot get bounds for image");
                         failure = true;
@@ -606,13 +609,13 @@
                 }
 
                 // See how much we're reducing the size of the image
-                int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth,
-                        roundedTrueCrop.height() / mOutHeight);
-
+                int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
+                        roundedTrueCrop.height() / mOutHeight));
                 // Attempt to open a region decoder
                 BitmapRegionDecoder decoder = null;
+                InputStream is = null;
                 try {
-                    InputStream is = regenerateInputStream();
+                    is = regenerateInputStream();
                     if (is == null) {
                         Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
                         failure = true;
@@ -622,6 +625,9 @@
                     Utils.closeSilently(is);
                 } catch (IOException e) {
                     Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+                } finally {
+                   Utils.closeSilently(is);
+                   is = null;
                 }
 
                 Bitmap crop = null;
@@ -637,7 +643,7 @@
 
                 if (crop == null) {
                     // BitmapRegionDecoder has failed, try to crop in-memory
-                    InputStream is = regenerateInputStream();
+                    is = regenerateInputStream();
                     Bitmap fullSize = null;
                     if (is != null) {
                         BitmapFactory.Options options = new BitmapFactory.Options();
@@ -648,12 +654,38 @@
                         Utils.closeSilently(is);
                     }
                     if (fullSize != null) {
+                        // Find out the true sample size that was used by the decoder
+                        scaleDownSampleSize = bounds.x / fullSize.getWidth();
                         mCropBounds.left /= scaleDownSampleSize;
                         mCropBounds.top /= scaleDownSampleSize;
                         mCropBounds.bottom /= scaleDownSampleSize;
                         mCropBounds.right /= scaleDownSampleSize;
                         mCropBounds.roundOut(roundedTrueCrop);
 
+                        // Adjust values to account for issues related to rounding
+                        if (roundedTrueCrop.width() > fullSize.getWidth()) {
+                            // Adjust the width
+                            roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
+                        }
+                        if (roundedTrueCrop.right > fullSize.getWidth()) {
+                            // Adjust the left value
+                            int adjustment = roundedTrueCrop.left -
+                                    Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width());
+                            roundedTrueCrop.left -= adjustment;
+                            roundedTrueCrop.right -= adjustment;
+                        }
+                        if (roundedTrueCrop.height() > fullSize.getHeight()) {
+                            // Adjust the height
+                            roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
+                        }
+                        if (roundedTrueCrop.bottom > fullSize.getHeight()) {
+                            // Adjust the top value
+                            int adjustment = roundedTrueCrop.top -
+                                    Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height());
+                            roundedTrueCrop.top -= adjustment;
+                            roundedTrueCrop.bottom -= adjustment;
+                        }
+
                         crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
                                 roundedTrueCrop.top, roundedTrueCrop.width(),
                                 roundedTrueCrop.height());
@@ -757,7 +789,7 @@
 
     protected void updateWallpaperDimensions(int width, int height) {
         String spKey = getSharedPreferencesKey();
-        SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
+        SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
         SharedPreferences.Editor editor = sp.edit();
         if (width != 0 && height != 0) {
             editor.putInt(WALLPAPER_WIDTH_KEY, width);
@@ -778,14 +810,15 @@
             final WallpaperManager wallpaperManager) {
         final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
 
-        new Thread("suggestWallpaperDimension") {
-            public void run() {
+        new AsyncTask<Void, Void, Void>() {
+            public Void doInBackground(Void ... args) {
                 // If we have saved a wallpaper width/height, use that instead
                 int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
                 int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
                 wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
+                return null;
             }
-        }.start();
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
     }
 
     protected static RectF getMaxCropRect(
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
index 596435a..10bcdad 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
@@ -117,8 +117,8 @@
                         if (!proxy.equals(Proxy.NO_PROXY)) {
                             // Only Inets created by PacProxySelector.
                             InetSocketAddress inetSocketAddress =
-                                    (InetSocketAddress)list.get(0).address();
-                            server = new Socket(inetSocketAddress.getAddress(),
+                                    (InetSocketAddress)proxy.address();
+                            server = new Socket(inetSocketAddress.getHostName(),
                                     inetSocketAddress.getPort());
                             sendLine(server, requestLine);
                         } else {
diff --git a/policy/src/com/android/internal/policy/impl/BarController.java b/policy/src/com/android/internal/policy/impl/BarController.java
index 8d97fc8..0ce4b12 100644
--- a/policy/src/com/android/internal/policy/impl/BarController.java
+++ b/policy/src/com/android/internal/policy/impl/BarController.java
@@ -37,8 +37,9 @@
     private static final boolean DEBUG = false;
 
     private static final int TRANSIENT_BAR_NONE = 0;
-    private static final int TRANSIENT_BAR_SHOWING = 1;
-    private static final int TRANSIENT_BAR_HIDING = 2;
+    private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1;
+    private static final int TRANSIENT_BAR_SHOWING = 2;
+    private static final int TRANSIENT_BAR_HIDING = 3;
 
     private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000;
 
@@ -73,13 +74,9 @@
         mWin = win;
     }
 
-    public boolean isHidden() {
-        return mState == StatusBarManager.WINDOW_STATE_HIDDEN;
-    }
-
     public void showTransient() {
         if (mWin != null) {
-            setTransientBarState(TRANSIENT_BAR_SHOWING);
+            setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED);
         }
     }
 
@@ -87,6 +84,10 @@
         return mTransientBarState == TRANSIENT_BAR_SHOWING;
     }
 
+    public boolean isTransientShowRequested() {
+        return mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED;
+    }
+
     public boolean wasRecentlyTranslucent() {
         return (SystemClock.uptimeMillis() - mLastTranslucent) < TRANSLUCENT_ANIMATION_DELAY_MS;
     }
@@ -129,8 +130,8 @@
         final boolean wasAnim = mWin.isAnimatingLw();
         final boolean change = show ? mWin.showLw(true) : mWin.hideLw(true);
         final int state = computeStateLw(wasVis, wasAnim, mWin, change);
-        updateStateLw(state);
-        return change;
+        final boolean stateChanged = updateStateLw(state);
+        return change || stateChanged;
     }
 
     private int computeStateLw(boolean wasVis, boolean wasAnim, WindowState win, boolean change) {
@@ -139,6 +140,8 @@
             final boolean anim = win.isAnimatingLw();
             if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) {
                 return StatusBarManager.WINDOW_STATE_HIDDEN;
+            } else if (mState == StatusBarManager.WINDOW_STATE_HIDDEN && vis) {
+                return StatusBarManager.WINDOW_STATE_SHOWING;
             } else if (change) {
                 if (wasVis && vis && !wasAnim && anim) {
                     return StatusBarManager.WINDOW_STATE_HIDING;
@@ -150,7 +153,7 @@
         return mState;
     }
 
-    private void updateStateLw(final int state) {
+    private boolean updateStateLw(final int state) {
         if (state != mState) {
             mState = state;
             if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state));
@@ -169,7 +172,9 @@
                     }
                 }
             });
+            return true;
         }
+        return false;
     }
 
     public boolean checkHiddenLw() {
@@ -194,6 +199,9 @@
         if (mTransientBarState == TRANSIENT_BAR_SHOWING) {
             if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown");
             return false;
+        } else if (mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED) {
+            if (DEBUG) Slog.d(mTag, "Not showing transient bar, already requested");
+            return false;
         } else if (mWin == null) {
             if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist");
             return false;
@@ -207,12 +215,13 @@
 
     public int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) {
         if (mWin == null) return vis;
-        if (mTransientBarState == TRANSIENT_BAR_SHOWING) { // transient bar requested
+        if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested
             if (transientAllowed) {
                 vis |= mTransientFlag;
                 if ((oldVis & mTransientFlag) == 0) {
                     vis |= mUnhideFlag;  // tell sysui we're ready to unhide
                 }
+                setTransientBarState(TRANSIENT_BAR_SHOWING);  // request accepted
             } else {
                 setTransientBarState(TRANSIENT_BAR_NONE);  // request denied
             }
@@ -250,6 +259,7 @@
     private static String transientBarStateToString(int state) {
         if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING";
         if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING";
+        if (state == TRANSIENT_BAR_SHOW_REQUESTED) return "TRANSIENT_BAR_SHOW_REQUESTED";
         if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE";
         throw new IllegalArgumentException("Unknown state " + state);
     }
diff --git a/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java b/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java
index 3e57a77..b734c41 100644
--- a/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java
+++ b/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java
@@ -178,6 +178,7 @@
                         | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                 ,
                 PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("ImmersiveModeConfirmation");
         lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
         lp.gravity = Gravity.FILL;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index faca949..d36b8f1 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -105,6 +105,7 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.HashSet;
 
 import static android.view.WindowManager.LayoutParams.*;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
@@ -381,6 +382,7 @@
     static final Rect mTmpNavigationFrame = new Rect();
 
     WindowState mTopFullscreenOpaqueWindowState;
+    HashSet<IApplicationToken> mAppsToBeHidden = new HashSet<IApplicationToken>();
     boolean mTopIsFullscreen;
     boolean mForceStatusBar;
     boolean mForceStatusBarFromKeyguard;
@@ -3277,8 +3279,9 @@
                             + mRestrictedScreenWidth;
                     pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
                             + mRestrictedScreenHeight;
-                } else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT) {
-                    // Toasts are stable to interim decor changes.
+                } else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT
+                        || attrs.type == TYPE_VOLUME_OVERLAY) {
+                    // These dialogs are stable to interim decor changes.
                     pf.left = df.left = of.left = cf.left = mStableLeft;
                     pf.top = df.top = of.top = cf.top = mStableTop;
                     pf.right = df.right = of.right = cf.right = mStableRight;
@@ -3364,6 +3367,7 @@
     @Override
     public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) {
         mTopFullscreenOpaqueWindowState = null;
+        mAppsToBeHidden.clear();
         mForceStatusBar = false;
         mForceStatusBarFromKeyguard = false;
         mForcingShowNavBar = false;
@@ -3382,13 +3386,10 @@
                                 WindowManager.LayoutParams attrs) {
         if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": isVisibleOrBehindKeyguardLw="
                 + win.isVisibleOrBehindKeyguardLw());
-        if (mTopFullscreenOpaqueWindowState == null && (win.getAttrs().privateFlags
-                &WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_NAV_BAR) != 0
-                || (win.isVisibleLw() && attrs.type == TYPE_INPUT_METHOD)) {
-            if (mForcingShowNavBarLayer < 0) {
-                mForcingShowNavBar = true;
-                mForcingShowNavBarLayer = win.getSurfaceLayer();
-            }
+        if (mTopFullscreenOpaqueWindowState == null
+                && win.isVisibleLw() && attrs.type == TYPE_INPUT_METHOD) {
+            mForcingShowNavBar = true;
+            mForcingShowNavBarLayer = win.getSurfaceLayer();
         }
         if (mTopFullscreenOpaqueWindowState == null &&
                 win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw()) {
@@ -3402,7 +3403,7 @@
             if (attrs.type == TYPE_KEYGUARD) {
                 mShowingLockscreen = true;
             }
-            boolean applyWindow = attrs.type >= FIRST_APPLICATION_WINDOW
+            boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
                     && attrs.type <= LAST_APPLICATION_WINDOW;
             if (attrs.type == TYPE_DREAM) {
                 // If the lockscreen was showing when the dream started then wait
@@ -3410,30 +3411,42 @@
                 if (!mDreamingLockscreen
                         || (win.isVisibleLw() && win.hasDrawnLw())) {
                     mShowingDream = true;
-                    applyWindow = true;
+                    appWindow = true;
                 }
             }
-            if (applyWindow
-                    && attrs.x == 0 && attrs.y == 0
-                    && attrs.width == WindowManager.LayoutParams.MATCH_PARENT
-                    && attrs.height == WindowManager.LayoutParams.MATCH_PARENT) {
-                if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win);
-                mTopFullscreenOpaqueWindowState = win;
-                if ((attrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0) {
-                    if (DEBUG_LAYOUT) Slog.v(TAG, "Setting mHideLockScreen to true by win " + win);
-                    mHideLockScreen = true;
-                    mForceStatusBarFromKeyguard = false;
+
+            final boolean showWhenLocked = (attrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
+            if (appWindow) {
+                if (showWhenLocked) {
+                    mAppsToBeHidden.remove(win.getAppToken());
+                } else {
+                    mAppsToBeHidden.add(win.getAppToken());
                 }
-                if ((attrs.flags & FLAG_DISMISS_KEYGUARD) != 0
-                        && mDismissKeyguard == DISMISS_KEYGUARD_NONE) {
-                    if (DEBUG_LAYOUT) Slog.v(TAG, "Setting mDismissKeyguard true by win " + win);
-                    mDismissKeyguard = mWinDismissingKeyguard == win ?
-                            DISMISS_KEYGUARD_CONTINUE : DISMISS_KEYGUARD_START;
-                    mWinDismissingKeyguard = win;
-                    mForceStatusBarFromKeyguard = mShowingLockscreen && isKeyguardSecure();
-                }
-                if ((attrs.flags & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
-                    mAllowLockscreenWhenOn = true;
+                if (attrs.x == 0 && attrs.y == 0
+                        && attrs.width == WindowManager.LayoutParams.MATCH_PARENT
+                        && attrs.height == WindowManager.LayoutParams.MATCH_PARENT) {
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win);
+                    mTopFullscreenOpaqueWindowState = win;
+                    if (mAppsToBeHidden.isEmpty()) {
+                        if (showWhenLocked) {
+                            if (DEBUG_LAYOUT) Slog.v(TAG,
+                                    "Setting mHideLockScreen to true by win " + win);
+                            mHideLockScreen = true;
+                            mForceStatusBarFromKeyguard = false;
+                        }
+                        if ((attrs.flags & FLAG_DISMISS_KEYGUARD) != 0
+                                && mDismissKeyguard == DISMISS_KEYGUARD_NONE) {
+                            if (DEBUG_LAYOUT) Slog.v(TAG,
+                                    "Setting mDismissKeyguard true by win " + win);
+                            mDismissKeyguard = mWinDismissingKeyguard == win ?
+                                    DISMISS_KEYGUARD_CONTINUE : DISMISS_KEYGUARD_START;
+                            mWinDismissingKeyguard = win;
+                            mForceStatusBarFromKeyguard = mShowingLockscreen && isKeyguardSecure();
+                        }
+                    }
+                    if ((attrs.flags & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
+                        mAllowLockscreenWhenOn = true;
+                    }
                 }
             }
         }
@@ -3468,6 +3481,11 @@
                 }
                 // Maintain fullscreen layout until incoming animation is complete.
                 topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw();
+                // Transient status bar on the lockscreen is not allowed
+                if (mForceStatusBarFromKeyguard && mStatusBarController.isTransientShowing()) {
+                    mStatusBarController.updateVisibilityLw(false /*transientAllowed*/,
+                            mLastSystemUiFlags, mLastSystemUiFlags);
+                }
             } else if (mTopFullscreenOpaqueWindowState != null) {
                 if (localLOGV) {
                     Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
@@ -3514,7 +3532,7 @@
         if (mKeyguard != null) {
             if (localLOGV) Slog.v(TAG, "finishPostLayoutPolicyLw: mHideKeyguard="
                     + mHideLockScreen);
-            if (mDismissKeyguard != DISMISS_KEYGUARD_NONE && !mKeyguardDelegate.isSecure()) {
+            if (mDismissKeyguard != DISMISS_KEYGUARD_NONE && !isKeyguardSecure()) {
                 if (mKeyguard.hideLw(true)) {
                     changes |= FINISH_LAYOUT_REDO_LAYOUT
                             | FINISH_LAYOUT_REDO_CONFIG
@@ -5158,9 +5176,9 @@
                 mNavigationBar != null &&
                 hideNavBarSysui && immersiveSticky;
 
-        boolean denyTransientStatus = mStatusBarController.isTransientShowing()
+        boolean denyTransientStatus = mStatusBarController.isTransientShowRequested()
                 && !transientStatusBarAllowed && hideStatusBarSysui;
-        boolean denyTransientNav = mNavigationBarController.isTransientShowing()
+        boolean denyTransientNav = mNavigationBarController.isTransientShowRequested()
                 && !transientNavBarAllowed;
         if (denyTransientStatus || denyTransientNav) {
             // clear the clearable flags instead
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
index bf22e2f..1357462 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
@@ -40,6 +40,14 @@
     private KeyguardState mKeyguardState = new KeyguardState();
 
     /* package */ static final class KeyguardState {
+        KeyguardState() {
+            // Assume keyguard is showing and secure until we know for sure. This is here in
+            // the event something checks before the service is actually started.
+            // KeyguardService itself should default to this state until the real state is known.
+            showing = true;
+            showingAndNotHidden = true;
+            secure = true;
+        }
         boolean showing;
         boolean showingAndNotHidden;
         boolean inputRestricted;
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index a04ee14..6d65a70 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -46,6 +46,8 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.ContentObserver;
@@ -80,7 +82,7 @@
 import com.android.internal.backup.BackupConstants;
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.backup.IObbBackupService;
-import com.android.internal.backup.LocalTransport;
+import com.android.server.EventLogTags;
 import com.android.server.PackageManagerBackupAgent.Metadata;
 
 import java.io.BufferedInputStream;
@@ -138,14 +140,20 @@
     private static final boolean DEBUG = true;
     private static final boolean MORE_DEBUG = false;
 
+    // Historical and current algorithm names
+    static final String PBKDF_CURRENT = "PBKDF2WithHmacSHA1";
+    static final String PBKDF_FALLBACK = "PBKDF2WithHmacSHA1And8bit";
+
     // Name and current contents version of the full-backup manifest file
     static final String BACKUP_MANIFEST_FILENAME = "_manifest";
     static final int BACKUP_MANIFEST_VERSION = 1;
     static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
-    static final int BACKUP_FILE_VERSION = 1;
+    static final int BACKUP_FILE_VERSION = 2;
+    static final int BACKUP_PW_FILE_VERSION = 2;
     static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
 
     static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
+    static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
 
     // How often we perform a backup pass.  Privileged external callers can
     // trigger an immediate pass.
@@ -158,6 +166,9 @@
     // the first backup pass.
     private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR;
 
+    // Retry interval for clear/init when the transport is unavailable
+    private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
+
     private static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
     private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
     private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR";
@@ -171,6 +182,8 @@
     private static final int MSG_RESTORE_TIMEOUT = 8;
     private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
     private static final int MSG_RUN_FULL_RESTORE = 10;
+    private static final int MSG_RETRY_INIT = 11;
+    private static final int MSG_RETRY_CLEAR = 12;
 
     // backup task state machine tick
     static final int MSG_BACKUP_RESTORE_STEP = 20;
@@ -251,10 +264,13 @@
     volatile boolean mClearingData;
 
     // Transport bookkeeping
+    final HashMap<String,String> mTransportNames
+            = new HashMap<String,String>();             // component name -> registration name
     final HashMap<String,IBackupTransport> mTransports
-            = new HashMap<String,IBackupTransport>();
+            = new HashMap<String,IBackupTransport>();   // registration name -> binder
+    final ArrayList<TransportConnection> mTransportConnections
+            = new ArrayList<TransportConnection>();
     String mCurrentTransport;
-    IBackupTransport mLocalTransport, mGoogleTransport;
     ActiveRestoreSession mActiveRestoreSession;
 
     // Watch the device provisioning operation during setup
@@ -300,6 +316,7 @@
 
     class RestoreParams {
         public IBackupTransport transport;
+        public String dirName;
         public IRestoreObserver observer;
         public long token;
         public PackageInfo pkgInfo;
@@ -307,9 +324,10 @@
         public boolean needFullBackup;
         public String[] filterSet;
 
-        RestoreParams(IBackupTransport _transport, IRestoreObserver _obs,
+        RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
                 long _token, PackageInfo _pkg, int _pmToken, boolean _needFullBackup) {
             transport = _transport;
+            dirName = _dirName;
             observer = _obs;
             token = _token;
             pkgInfo = _pkg;
@@ -318,9 +336,10 @@
             filterSet = null;
         }
 
-        RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token,
-                boolean _needFullBackup) {
+        RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+                long _token, boolean _needFullBackup) {
             transport = _transport;
+            dirName = _dirName;
             observer = _obs;
             token = _token;
             pkgInfo = null;
@@ -329,9 +348,10 @@
             filterSet = null;
         }
 
-        RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token,
-                String[] _filterSet, boolean _needFullBackup) {
+        RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+                long _token, String[] _filterSet, boolean _needFullBackup) {
             transport = _transport;
+            dirName = _dirName;
             observer = _obs;
             token = _token;
             pkgInfo = null;
@@ -351,6 +371,16 @@
         }
     }
 
+    class ClearRetryParams {
+        public String transportName;
+        public String packageName;
+
+        ClearRetryParams(String transport, String pkg) {
+            transportName = transport;
+            packageName = pkg;
+        }
+    }
+
     class FullParams {
         public ParcelFileDescriptor fd;
         public final AtomicBoolean latch;
@@ -425,6 +455,8 @@
     private final SecureRandom mRng = new SecureRandom();
     private String mPasswordHash;
     private File mPasswordHashFile;
+    private int mPasswordVersion;
+    private File mPasswordVersionFile;
     private byte[] mPasswordSalt;
 
     // Configuration of PBKDF2 that we use for generating pw hashes and intermediate keys
@@ -510,13 +542,28 @@
                 // When it completes successfully, that old journal file will be
                 // deleted.  If we crash prior to that, the old journal is parsed
                 // at next boot and the journaled requests fulfilled.
+                boolean staged = true;
                 if (queue.size() > 0) {
                     // Spin up a backup state sequence and set it running
-                    PerformBackupTask pbt = new PerformBackupTask(transport, queue, oldJournal);
-                    Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
-                    sendMessage(pbtMessage);
+                    try {
+                        String dirName = transport.transportDirName();
+                        PerformBackupTask pbt = new PerformBackupTask(transport, dirName,
+                                queue, oldJournal);
+                        Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+                        sendMessage(pbtMessage);
+                    } catch (RemoteException e) {
+                        // unable to ask the transport its dir name -- transient failure, since
+                        // the above check succeeded.  Try again next time.
+                        Slog.e(TAG, "Transport became unavailable attempting backup");
+                        staged = false;
+                    }
                 } else {
                     Slog.v(TAG, "Backup requested but nothing pending");
+                    staged = false;
+                }
+
+                if (!staged) {
+                    // if we didn't actually hand off the wakelock, rewind until next time
                     synchronized (mQueueLock) {
                         mBackupRunning = false;
                     }
@@ -566,7 +613,7 @@
                 RestoreParams params = (RestoreParams)msg.obj;
                 Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
                 PerformRestoreTask task = new PerformRestoreTask(
-                        params.transport, params.observer,
+                        params.transport, params.dirName, params.observer,
                         params.token, params.pkgInfo, params.pmToken,
                         params.needFullBackup, params.filterSet);
                 Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
@@ -593,6 +640,14 @@
                 break;
             }
 
+            case MSG_RETRY_CLEAR:
+            {
+                // reenqueues if the transport remains unavailable
+                ClearRetryParams params = (ClearRetryParams)msg.obj;
+                clearBackupData(params.transportName, params.packageName);
+                break;
+            }
+
             case MSG_RUN_INITIALIZE:
             {
                 HashSet<String> queue;
@@ -607,6 +662,16 @@
                 break;
             }
 
+            case MSG_RETRY_INIT:
+            {
+                synchronized (mQueueLock) {
+                    recordInitPendingLocked(msg.arg1 != 0, (String)msg.obj);
+                    mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
+                            mRunInitIntent);
+                }
+                break;
+            }
+
             case MSG_RUN_GET_RESTORE_SETS:
             {
                 // Like other async operations, this is entered with the wakelock held
@@ -752,6 +817,27 @@
         }
         mDataDir = Environment.getDownloadCacheDirectory();
 
+        mPasswordVersion = 1;       // unless we hear otherwise
+        mPasswordVersionFile = new File(mBaseStateDir, "pwversion");
+        if (mPasswordVersionFile.exists()) {
+            FileInputStream fin = null;
+            DataInputStream in = null;
+            try {
+                fin = new FileInputStream(mPasswordVersionFile);
+                in = new DataInputStream(fin);
+                mPasswordVersion = in.readInt();
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to read backup pw version");
+            } finally {
+                try {
+                    if (in != null) in.close();
+                    if (fin != null) fin.close();
+                } catch (IOException e) {
+                    Slog.w(TAG, "Error closing pw version files");
+                }
+            }
+        }
+
         mPasswordHashFile = new File(mBaseStateDir, "pwhash");
         if (mPasswordHashFile.exists()) {
             FileInputStream fin = null;
@@ -815,13 +901,7 @@
         }
 
         // Set up our transport options and initialize the default transport
-        // TODO: Have transports register themselves somehow?
         // TODO: Don't create transports that we don't need to?
-        mLocalTransport = new LocalTransport(context);  // This is actually pretty cheap
-        ComponentName localName = new ComponentName(context, LocalTransport.class);
-        registerTransport(localName.flattenToShortString(), mLocalTransport);
-
-        mGoogleTransport = null;
         mCurrentTransport = Settings.Secure.getString(context.getContentResolver(),
                 Settings.Secure.BACKUP_TRANSPORT);
         if ("".equals(mCurrentTransport)) {
@@ -829,28 +909,43 @@
         }
         if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport);
 
-        // Attach to the Google backup transport.  When this comes up, it will set
-        // itself as the current transport because we explicitly reset mCurrentTransport
-        // to null.
-        ComponentName transportComponent = new ComponentName("com.google.android.backup",
-                "com.google.android.backup.BackupTransportService");
-        try {
-            // If there's something out there that is supposed to be the Google
-            // backup transport, make sure it's legitimately part of the OS build
-            // and not an app lying about its package name.
-            ApplicationInfo info = mPackageManager.getApplicationInfo(
-                    transportComponent.getPackageName(), 0);
-            if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                if (DEBUG) Slog.v(TAG, "Binding to Google transport");
-                Intent intent = new Intent().setComponent(transportComponent);
-                context.bindServiceAsUser(intent, mGoogleConnection, Context.BIND_AUTO_CREATE,
-                        UserHandle.OWNER);
-            } else {
-                Slog.w(TAG, "Possible Google transport spoof: ignoring " + info);
+        // Find transport hosts and bind to their services
+        Intent transportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
+        List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
+                transportServiceIntent, 0, UserHandle.USER_OWNER);
+        if (DEBUG) {
+            Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size()));
+        }
+        if (hosts != null) {
+            if (MORE_DEBUG) {
+                for (int i = 0; i < hosts.size(); i++) {
+                    ServiceInfo info = hosts.get(i).serviceInfo;
+                    Slog.v(TAG, "   " + info.packageName + "/" + info.name);
+                }
             }
-        } catch (PackageManager.NameNotFoundException nnf) {
-            // No such package?  No binding.
-            if (DEBUG) Slog.v(TAG, "Google transport not present");
+            for (int i = 0; i < hosts.size(); i++) {
+                try {
+                    ServiceInfo info = hosts.get(i).serviceInfo;
+                    PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0);
+                    if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+                        ComponentName svcName = new ComponentName(info.packageName, info.name);
+                        if (DEBUG) {
+                            Slog.i(TAG, "Binding to transport host " + svcName);
+                        }
+                        Intent intent = new Intent(transportServiceIntent);
+                        intent.setComponent(svcName);
+                        TransportConnection connection = new TransportConnection();
+                        mTransportConnections.add(connection);
+                        context.bindServiceAsUser(intent,
+                                connection, Context.BIND_AUTO_CREATE,
+                                UserHandle.OWNER);
+                    } else {
+                        Slog.w(TAG, "Transport package not privileged: " + info.packageName);
+                    }
+                } catch (Exception e) {
+                    Slog.e(TAG, "Problem resolving transport service: " + e.getMessage());
+                }
+            }
         }
 
         // Now that we know about valid backup participants, parse any
@@ -1043,13 +1138,13 @@
         }
     }
 
-    private SecretKey buildPasswordKey(String pw, byte[] salt, int rounds) {
-        return buildCharArrayKey(pw.toCharArray(), salt, rounds);
+    private SecretKey buildPasswordKey(String algorithm, String pw, byte[] salt, int rounds) {
+        return buildCharArrayKey(algorithm, pw.toCharArray(), salt, rounds);
     }
 
-    private SecretKey buildCharArrayKey(char[] pwArray, byte[] salt, int rounds) {
+    private SecretKey buildCharArrayKey(String algorithm, char[] pwArray, byte[] salt, int rounds) {
         try {
-            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
             KeySpec ks = new PBEKeySpec(pwArray, salt, rounds, PBKDF2_KEY_SIZE);
             return keyFactory.generateSecret(ks);
         } catch (InvalidKeySpecException e) {
@@ -1060,8 +1155,8 @@
         return null;
     }
 
-    private String buildPasswordHash(String pw, byte[] salt, int rounds) {
-        SecretKey key = buildPasswordKey(pw, salt, rounds);
+    private String buildPasswordHash(String algorithm, String pw, byte[] salt, int rounds) {
+        SecretKey key = buildPasswordKey(algorithm, pw, salt, rounds);
         if (key != null) {
             return byteArrayToHex(key.getEncoded());
         }
@@ -1089,13 +1184,13 @@
         return result;
     }
 
-    private byte[] makeKeyChecksum(byte[] pwBytes, byte[] salt, int rounds) {
+    private byte[] makeKeyChecksum(String algorithm, byte[] pwBytes, byte[] salt, int rounds) {
         char[] mkAsChar = new char[pwBytes.length];
         for (int i = 0; i < pwBytes.length; i++) {
             mkAsChar[i] = (char) pwBytes[i];
         }
 
-        Key checksum = buildCharArrayKey(mkAsChar, salt, rounds);
+        Key checksum = buildCharArrayKey(algorithm, mkAsChar, salt, rounds);
         return checksum.getEncoded();
     }
 
@@ -1107,7 +1202,7 @@
     }
 
     // Backup password management
-    boolean passwordMatchesSaved(String candidatePw, int rounds) {
+    boolean passwordMatchesSaved(String algorithm, String candidatePw, int rounds) {
         // First, on an encrypted device we require matching the device pw
         final boolean isEncrypted;
         try {
@@ -1150,7 +1245,7 @@
         } else {
             // hash the stated current pw and compare to the stored one
             if (candidatePw != null && candidatePw.length() > 0) {
-                String currentPwHash = buildPasswordHash(candidatePw, mPasswordSalt, rounds);
+                String currentPwHash = buildPasswordHash(algorithm, candidatePw, mPasswordSalt, rounds);
                 if (mPasswordHash.equalsIgnoreCase(currentPwHash)) {
                     // candidate hash matches the stored hash -- the password matches
                     return true;
@@ -1165,11 +1260,37 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "setBackupPassword");
 
-        // If the supplied pw doesn't hash to the the saved one, fail
-        if (!passwordMatchesSaved(currentPw, PBKDF2_HASH_ROUNDS)) {
+        // When processing v1 passwords we may need to try two different PBKDF2 checksum regimes
+        final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
+
+        // If the supplied pw doesn't hash to the the saved one, fail.  The password
+        // might be caught in the legacy crypto mismatch; verify that too.
+        if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS)
+                && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
+                        currentPw, PBKDF2_HASH_ROUNDS))) {
             return false;
         }
 
+        // Snap up to current on the pw file version
+        mPasswordVersion = BACKUP_PW_FILE_VERSION;
+        FileOutputStream pwFout = null;
+        DataOutputStream pwOut = null;
+        try {
+            pwFout = new FileOutputStream(mPasswordVersionFile);
+            pwOut = new DataOutputStream(pwFout);
+            pwOut.writeInt(mPasswordVersion);
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to write backup pw version; password not changed");
+            return false;
+        } finally {
+            try {
+                if (pwOut != null) pwOut.close();
+                if (pwFout != null) pwFout.close();
+            } catch (IOException e) {
+                Slog.w(TAG, "Unable to close pw version record");
+            }
+        }
+
         // Clearing the password is okay
         if (newPw == null || newPw.isEmpty()) {
             if (mPasswordHashFile.exists()) {
@@ -1187,7 +1308,7 @@
         try {
             // Okay, build the hash of the new backup password
             byte[] salt = randomBytes(PBKDF2_SALT_SIZE);
-            String newPwHash = buildPasswordHash(newPw, salt, PBKDF2_HASH_ROUNDS);
+            String newPwHash = buildPasswordHash(PBKDF_CURRENT, newPw, salt, PBKDF2_HASH_ROUNDS);
 
             OutputStream pwf = null, buffer = null;
             DataOutputStream out = null;
@@ -1230,34 +1351,65 @@
         }
     }
 
+    private boolean backupPasswordMatches(String currentPw) {
+        if (hasBackupPassword()) {
+            final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
+            if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS)
+                    && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
+                            currentPw, PBKDF2_HASH_ROUNDS))) {
+                if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
+                return false;
+            }
+        }
+        return true;
+    }
+
     // Maintain persistent state around whether need to do an initialize operation.
     // Must be called with the queue lock held.
     void recordInitPendingLocked(boolean isPending, String transportName) {
         if (DEBUG) Slog.i(TAG, "recordInitPendingLocked: " + isPending
                 + " on transport " + transportName);
+        mBackupHandler.removeMessages(MSG_RETRY_INIT);
+
         try {
             IBackupTransport transport = getTransport(transportName);
-            String transportDirName = transport.transportDirName();
-            File stateDir = new File(mBaseStateDir, transportDirName);
-            File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+            if (transport != null) {
+                String transportDirName = transport.transportDirName();
+                File stateDir = new File(mBaseStateDir, transportDirName);
+                File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
 
-            if (isPending) {
-                // We need an init before we can proceed with sending backup data.
-                // Record that with an entry in our set of pending inits, as well as
-                // journaling it via creation of a sentinel file.
-                mPendingInits.add(transportName);
-                try {
-                    (new FileOutputStream(initPendingFile)).close();
-                } catch (IOException ioe) {
-                    // Something is badly wrong with our permissions; just try to move on
+                if (isPending) {
+                    // We need an init before we can proceed with sending backup data.
+                    // Record that with an entry in our set of pending inits, as well as
+                    // journaling it via creation of a sentinel file.
+                    mPendingInits.add(transportName);
+                    try {
+                        (new FileOutputStream(initPendingFile)).close();
+                    } catch (IOException ioe) {
+                        // Something is badly wrong with our permissions; just try to move on
+                    }
+                } else {
+                    // No more initialization needed; wipe the journal and reset our state.
+                    initPendingFile.delete();
+                    mPendingInits.remove(transportName);
                 }
-            } else {
-                // No more initialization needed; wipe the journal and reset our state.
-                initPendingFile.delete();
-                mPendingInits.remove(transportName);
+                return; // done; don't fall through to the error case
             }
         } catch (RemoteException e) {
-            // can't happen; the transport is local
+            // transport threw when asked its name; fall through to the lookup-failed case
+        }
+
+        // The named transport doesn't exist or threw.  This operation is
+        // important, so we record the need for a an init and post a message
+        // to retry the init later.
+        if (isPending) {
+            mPendingInits.add(transportName);
+            mBackupHandler.sendMessageDelayed(
+                    mBackupHandler.obtainMessage(MSG_RETRY_INIT,
+                            (isPending ? 1 : 0),
+                            0,
+                            transportName),
+                    TRANSPORT_RETRY_INTERVAL);
         }
     }
 
@@ -1298,13 +1450,16 @@
 
     // Add a transport to our set of available backends.  If 'transport' is null, this
     // is an unregistration, and the transport's entry is removed from our bookkeeping.
-    private void registerTransport(String name, IBackupTransport transport) {
+    private void registerTransport(String name, String component, IBackupTransport transport) {
         synchronized (mTransports) {
-            if (DEBUG) Slog.v(TAG, "Registering transport " + name + " = " + transport);
+            if (DEBUG) Slog.v(TAG, "Registering transport "
+                    + component + "::" + name + " = " + transport);
             if (transport != null) {
                 mTransports.put(name, transport);
+                mTransportNames.put(component, name);
             } else {
-                mTransports.remove(name);
+                mTransports.remove(mTransportNames.get(component));
+                mTransportNames.remove(component);
                 // Nothing further to do in the unregistration case
                 return;
             }
@@ -1330,7 +1485,10 @@
                 }
             }
         } catch (RemoteException e) {
-            // can't happen, the transport is local
+            // the transport threw when asked its file naming prefs; declare it invalid
+            Slog.e(TAG, "Unable to register transport as " + name);
+            mTransportNames.remove(component);
+            mTransports.remove(name);
         }
     }
 
@@ -1390,18 +1548,23 @@
         }
     };
 
-    // ----- Track connection to GoogleBackupTransport service -----
-    ServiceConnection mGoogleConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (DEBUG) Slog.v(TAG, "Connected to Google transport");
-            mGoogleTransport = IBackupTransport.Stub.asInterface(service);
-            registerTransport(name.flattenToShortString(), mGoogleTransport);
+    // ----- Track connection to transports service -----
+    class TransportConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName component, IBinder service) {
+            if (DEBUG) Slog.v(TAG, "Connected to transport " + component);
+            try {
+                IBackupTransport transport = IBackupTransport.Stub.asInterface(service);
+                registerTransport(transport.name(), component.flattenToShortString(), transport);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to register transport " + component);
+            }
         }
 
-        public void onServiceDisconnected(ComponentName name) {
-            if (DEBUG) Slog.v(TAG, "Disconnected from Google transport");
-            mGoogleTransport = null;
-            registerTransport(name.flattenToShortString(), null);
+        @Override
+        public void onServiceDisconnected(ComponentName component) {
+            if (DEBUG) Slog.v(TAG, "Disconnected from transport " + component);
+            registerTransport(null, component.flattenToShortString(), null);
         }
     };
 
@@ -1645,7 +1808,7 @@
                     agent = mConnectedAgent;
                 }
             } catch (RemoteException e) {
-                // can't happen
+                // can't happen - ActivityManager is local
             }
         }
         return agent;
@@ -1821,17 +1984,13 @@
         int mStatus;
         boolean mFinished;
 
-        public PerformBackupTask(IBackupTransport transport, ArrayList<BackupRequest> queue,
-                File journal) {
+        public PerformBackupTask(IBackupTransport transport, String dirName,
+                ArrayList<BackupRequest> queue, File journal) {
             mTransport = transport;
             mOriginalQueue = queue;
             mJournal = journal;
 
-            try {
-                mStateDir = new File(mBaseStateDir, transport.transportDirName());
-            } catch (RemoteException e) {
-                // can't happen; the transport is local
-            }
+            mStateDir = new File(mBaseStateDir, dirName);
 
             mCurrentState = BackupState.INITIAL;
             mFinished = false;
@@ -2077,8 +2236,12 @@
                 addBackupTrace("success; recording token");
                 try {
                     mCurrentToken = mTransport.getCurrentRestoreSet();
-                } catch (RemoteException e) {} // can't happen
-                writeRestoreTokens();
+                    writeRestoreTokens();
+                } catch (RemoteException e) {
+                    // nothing for it at this point, unfortunately, but this will be
+                    // recorded the next time we fully succeed.
+                    addBackupTrace("transport threw returning token");
+                }
             }
 
             // Set up the next backup pass - at this point we can set mBackupRunning
@@ -2299,7 +2462,7 @@
                 addBackupTrace("unbinding " + mCurrentPackage.packageName);
                 try {  // unbind even on timeout, just in case
                     mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
-                } catch (RemoteException e) {}
+                } catch (RemoteException e) { /* can't happen; activity manager is local */ }
             }
         }
 
@@ -2621,11 +2784,9 @@
 
                 // Verify that the given password matches the currently-active
                 // backup password, if any
-                if (hasBackupPassword()) {
-                    if (!passwordMatchesSaved(mCurrentPassword, PBKDF2_HASH_ROUNDS)) {
-                        if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
-                        return;
-                    }
+                if (!backupPasswordMatches(mCurrentPassword)) {
+                    if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
+                    return;
                 }
 
                 // Write the global file header.  All strings are UTF-8 encoded; lines end
@@ -2633,7 +2794,7 @@
                 // final '\n'.
                 //
                 // line 1: "ANDROID BACKUP"
-                // line 2: backup file format version, currently "1"
+                // line 2: backup file format version, currently "2"
                 // line 3: compressed?  "0" if not compressed, "1" if compressed.
                 // line 4: name of encryption algorithm [currently only "none" or "AES-256"]
                 //
@@ -2741,7 +2902,7 @@
                 OutputStream ofstream) throws Exception {
             // User key will be used to encrypt the master key.
             byte[] newUserSalt = randomBytes(PBKDF2_SALT_SIZE);
-            SecretKey userKey = buildPasswordKey(mEncryptPassword, newUserSalt,
+            SecretKey userKey = buildPasswordKey(PBKDF_CURRENT, mEncryptPassword, newUserSalt,
                     PBKDF2_HASH_ROUNDS);
 
             // the master key is random for each backup
@@ -2788,7 +2949,7 @@
             // stated number of PBKDF2 rounds
             IV = c.getIV();
             byte[] mk = masterKeySpec.getEncoded();
-            byte[] checksum = makeKeyChecksum(masterKeySpec.getEncoded(),
+            byte[] checksum = makeKeyChecksum(PBKDF_CURRENT, masterKeySpec.getEncoded(),
                     checksumSalt, PBKDF2_HASH_ROUNDS);
 
             ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length
@@ -3131,11 +3292,9 @@
             FileInputStream rawInStream = null;
             DataInputStream rawDataIn = null;
             try {
-                if (hasBackupPassword()) {
-                    if (!passwordMatchesSaved(mCurrentPassword, PBKDF2_HASH_ROUNDS)) {
-                        if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
-                        return;
-                    }
+                if (!backupPasswordMatches(mCurrentPassword)) {
+                    if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
+                    return;
                 }
 
                 mBytes = 0;
@@ -3156,8 +3315,12 @@
                 if (Arrays.equals(magicBytes, streamHeader)) {
                     // okay, header looks good.  now parse out the rest of the fields.
                     String s = readHeaderLine(rawInStream);
-                    if (Integer.parseInt(s) == BACKUP_FILE_VERSION) {
-                        // okay, it's a version we recognize
+                    final int archiveVersion = Integer.parseInt(s);
+                    if (archiveVersion <= BACKUP_FILE_VERSION) {
+                        // okay, it's a version we recognize.  if it's version 1, we may need
+                        // to try two different PBKDF2 regimes to compare checksums.
+                        final boolean pbkdf2Fallback = (archiveVersion == 1);
+
                         s = readHeaderLine(rawInStream);
                         compressed = (Integer.parseInt(s) != 0);
                         s = readHeaderLine(rawInStream);
@@ -3165,7 +3328,8 @@
                             // no more header to parse; we're good to go
                             okay = true;
                         } else if (mDecryptPassword != null && mDecryptPassword.length() > 0) {
-                            preCompressStream = decodeAesHeaderAndInitialize(s, rawInStream);
+                            preCompressStream = decodeAesHeaderAndInitialize(s, pbkdf2Fallback,
+                                    rawInStream);
                             if (preCompressStream != null) {
                                 okay = true;
                             }
@@ -3225,7 +3389,71 @@
             return buffer.toString();
         }
 
-        InputStream decodeAesHeaderAndInitialize(String encryptionName, InputStream rawInStream) {
+        InputStream attemptMasterKeyDecryption(String algorithm, byte[] userSalt, byte[] ckSalt,
+                int rounds, String userIvHex, String masterKeyBlobHex, InputStream rawInStream,
+                boolean doLog) {
+            InputStream result = null;
+
+            try {
+                Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
+                SecretKey userKey = buildPasswordKey(algorithm, mDecryptPassword, userSalt,
+                        rounds);
+                byte[] IV = hexToByteArray(userIvHex);
+                IvParameterSpec ivSpec = new IvParameterSpec(IV);
+                c.init(Cipher.DECRYPT_MODE,
+                        new SecretKeySpec(userKey.getEncoded(), "AES"),
+                        ivSpec);
+                byte[] mkCipher = hexToByteArray(masterKeyBlobHex);
+                byte[] mkBlob = c.doFinal(mkCipher);
+
+                // first, the master key IV
+                int offset = 0;
+                int len = mkBlob[offset++];
+                IV = Arrays.copyOfRange(mkBlob, offset, offset + len);
+                offset += len;
+                // then the master key itself
+                len = mkBlob[offset++];
+                byte[] mk = Arrays.copyOfRange(mkBlob,
+                        offset, offset + len);
+                offset += len;
+                // and finally the master key checksum hash
+                len = mkBlob[offset++];
+                byte[] mkChecksum = Arrays.copyOfRange(mkBlob,
+                        offset, offset + len);
+
+                // now validate the decrypted master key against the checksum
+                byte[] calculatedCk = makeKeyChecksum(algorithm, mk, ckSalt, rounds);
+                if (Arrays.equals(calculatedCk, mkChecksum)) {
+                    ivSpec = new IvParameterSpec(IV);
+                    c.init(Cipher.DECRYPT_MODE,
+                            new SecretKeySpec(mk, "AES"),
+                            ivSpec);
+                    // Only if all of the above worked properly will 'result' be assigned
+                    result = new CipherInputStream(rawInStream, c);
+                } else if (doLog) Slog.w(TAG, "Incorrect password");
+            } catch (InvalidAlgorithmParameterException e) {
+                if (doLog) Slog.e(TAG, "Needed parameter spec unavailable!", e);
+            } catch (BadPaddingException e) {
+                // This case frequently occurs when the wrong password is used to decrypt
+                // the master key.  Use the identical "incorrect password" log text as is
+                // used in the checksum failure log in order to avoid providing additional
+                // information to an attacker.
+                if (doLog) Slog.w(TAG, "Incorrect password");
+            } catch (IllegalBlockSizeException e) {
+                if (doLog) Slog.w(TAG, "Invalid block size in master key");
+            } catch (NoSuchAlgorithmException e) {
+                if (doLog) Slog.e(TAG, "Needed decryption algorithm unavailable!");
+            } catch (NoSuchPaddingException e) {
+                if (doLog) Slog.e(TAG, "Needed padding mechanism unavailable!");
+            } catch (InvalidKeyException e) {
+                if (doLog) Slog.w(TAG, "Illegal password; aborting");
+            }
+
+            return result;
+        }
+
+        InputStream decodeAesHeaderAndInitialize(String encryptionName, boolean pbkdf2Fallback,
+                InputStream rawInStream) {
             InputStream result = null;
             try {
                 if (encryptionName.equals(ENCRYPTION_ALGORITHM_NAME)) {
@@ -3242,59 +3470,13 @@
                     String masterKeyBlobHex = readHeaderLine(rawInStream); // 9
 
                     // decrypt the master key blob
-                    Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
-                    SecretKey userKey = buildPasswordKey(mDecryptPassword, userSalt,
-                            rounds);
-                    byte[] IV = hexToByteArray(userIvHex);
-                    IvParameterSpec ivSpec = new IvParameterSpec(IV);
-                    c.init(Cipher.DECRYPT_MODE,
-                            new SecretKeySpec(userKey.getEncoded(), "AES"),
-                            ivSpec);
-                    byte[] mkCipher = hexToByteArray(masterKeyBlobHex);
-                    byte[] mkBlob = c.doFinal(mkCipher);
-
-                    // first, the master key IV
-                    int offset = 0;
-                    int len = mkBlob[offset++];
-                    IV = Arrays.copyOfRange(mkBlob, offset, offset + len);
-                    offset += len;
-                    // then the master key itself
-                    len = mkBlob[offset++];
-                    byte[] mk = Arrays.copyOfRange(mkBlob,
-                            offset, offset + len);
-                    offset += len;
-                    // and finally the master key checksum hash
-                    len = mkBlob[offset++];
-                    byte[] mkChecksum = Arrays.copyOfRange(mkBlob,
-                            offset, offset + len);
-
-                    // now validate the decrypted master key against the checksum
-                    byte[] calculatedCk = makeKeyChecksum(mk, ckSalt, rounds);
-                    if (Arrays.equals(calculatedCk, mkChecksum)) {
-                        ivSpec = new IvParameterSpec(IV);
-                        c.init(Cipher.DECRYPT_MODE,
-                                new SecretKeySpec(mk, "AES"),
-                                ivSpec);
-                        // Only if all of the above worked properly will 'result' be assigned
-                        result = new CipherInputStream(rawInStream, c);
-                    } else Slog.w(TAG, "Incorrect password");
+                    result = attemptMasterKeyDecryption(PBKDF_CURRENT, userSalt, ckSalt,
+                            rounds, userIvHex, masterKeyBlobHex, rawInStream, false);
+                    if (result == null && pbkdf2Fallback) {
+                        result = attemptMasterKeyDecryption(PBKDF_FALLBACK, userSalt, ckSalt,
+                                rounds, userIvHex, masterKeyBlobHex, rawInStream, true);
+                    }
                 } else Slog.w(TAG, "Unsupported encryption method: " + encryptionName);
-            } catch (InvalidAlgorithmParameterException e) {
-                Slog.e(TAG, "Needed parameter spec unavailable!", e);
-            } catch (BadPaddingException e) {
-                // This case frequently occurs when the wrong password is used to decrypt
-                // the master key.  Use the identical "incorrect password" log text as is
-                // used in the checksum failure log in order to avoid providing additional
-                // information to an attacker.
-                Slog.w(TAG, "Incorrect password");
-            } catch (IllegalBlockSizeException e) {
-                Slog.w(TAG, "Invalid block size in master key");
-            } catch (NoSuchAlgorithmException e) {
-                Slog.e(TAG, "Needed decryption algorithm unavailable!");
-            } catch (NoSuchPaddingException e) {
-                Slog.e(TAG, "Needed padding mechanism unavailable!");
-            } catch (InvalidKeyException e) {
-                Slog.w(TAG, "Illegal password; aborting");
             } catch (NumberFormatException e) {
                 Slog.w(TAG, "Can't parse restore data header");
             } catch (IOException e) {
@@ -4314,7 +4496,7 @@
             }
         }
 
-        PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
+        PerformRestoreTask(IBackupTransport transport, String dirName, IRestoreObserver observer,
                 long restoreSetToken, PackageInfo targetPackage, int pmToken,
                 boolean needFullBackup, String[] filterSet) {
             mCurrentState = RestoreState.INITIAL;
@@ -4337,11 +4519,7 @@
                 mFilterSet = null;
             }
 
-            try {
-                mStateDir = new File(mBaseStateDir, transport.transportDirName());
-            } catch (RemoteException e) {
-                // can't happen; the transport is local
-            }
+            mStateDir = new File(mBaseStateDir, dirName);
         }
 
         // Execute one tick of whatever state machine the task implements
@@ -5067,8 +5245,8 @@
     }
 
     // Clear the given package's backup data from the current transport
-    public void clearBackupData(String packageName) {
-        if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName);
+    public void clearBackupData(String transportName, String packageName) {
+        if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName);
         PackageInfo info;
         try {
             info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
@@ -5099,13 +5277,22 @@
 
         // Is the given app an available participant?
         if (apps.contains(packageName)) {
-            if (DEBUG) Slog.v(TAG, "Found the app - running clear process");
             // found it; fire off the clear request
+            if (DEBUG) Slog.v(TAG, "Found the app - running clear process");
+            mBackupHandler.removeMessages(MSG_RETRY_CLEAR);
             synchronized (mQueueLock) {
+                final IBackupTransport transport = getTransport(transportName);
+                if (transport == null) {
+                    // transport is currently unavailable -- make sure to retry
+                    Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR,
+                            new ClearRetryParams(transportName, packageName));
+                    mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL);
+                    return;
+                }
                 long oldId = Binder.clearCallingIdentity();
                 mWakelock.acquire();
                 Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
-                        new ClearParams(getTransport(mCurrentTransport), info));
+                        new ClearParams(transport, info));
                 mBackupHandler.sendMessage(msg);
                 Binder.restoreCallingIdentity(oldId);
             }
@@ -5597,31 +5784,55 @@
             return;
         }
 
+        boolean skip = false;
+
         long restoreSet = getAvailableRestoreToken(packageName);
         if (DEBUG) Slog.v(TAG, "restoreAtInstall pkg=" + packageName
                 + " token=" + Integer.toHexString(token)
                 + " restoreSet=" + Long.toHexString(restoreSet));
+        if (restoreSet == 0) {
+            if (MORE_DEBUG) Slog.i(TAG, "No restore set");
+            skip = true;
+        }
 
-        if (mAutoRestore && mProvisioned && restoreSet != 0) {
-            // okay, we're going to attempt a restore of this package from this restore set.
-            // The eventual message back into the Package Manager to run the post-install
-            // steps for 'token' will be issued from the restore handling code.
+        // Do we have a transport to fetch data for us?
+        IBackupTransport transport = getTransport(mCurrentTransport);
+        if (transport == null) {
+            if (DEBUG) Slog.w(TAG, "No transport");
+            skip = true;
+        }
 
-            // We can use a synthetic PackageInfo here because:
-            //   1. We know it's valid, since the Package Manager supplied the name
-            //   2. Only the packageName field will be used by the restore code
-            PackageInfo pkg = new PackageInfo();
-            pkg.packageName = packageName;
+        if (!skip && mAutoRestore && mProvisioned) {
+            try {
+                // okay, we're going to attempt a restore of this package from this restore set.
+                // The eventual message back into the Package Manager to run the post-install
+                // steps for 'token' will be issued from the restore handling code.
 
-            mWakelock.acquire();
-            Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
-            msg.obj = new RestoreParams(getTransport(mCurrentTransport), null,
-                    restoreSet, pkg, token, true);
-            mBackupHandler.sendMessage(msg);
-        } else {
+                // This can throw and so *must* happen before the wakelock is acquired
+                String dirName = transport.transportDirName();
+
+                // We can use a synthetic PackageInfo here because:
+                //   1. We know it's valid, since the Package Manager supplied the name
+                //   2. Only the packageName field will be used by the restore code
+                PackageInfo pkg = new PackageInfo();
+                pkg.packageName = packageName;
+
+                mWakelock.acquire();
+                Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+                msg.obj = new RestoreParams(transport, dirName, null,
+                        restoreSet, pkg, token, true);
+                mBackupHandler.sendMessage(msg);
+            } catch (RemoteException e) {
+                // Binding to the transport broke; back off and proceed with the installation.
+                Slog.e(TAG, "Unable to contact transport");
+                skip = true;
+            }
+        }
+
+        if (skip) {
             // Auto-restore disabled or no way to attempt a restore; just tell the Package
             // Manager to proceed with the post-install handling for this package.
-            if (DEBUG) Slog.v(TAG, "No restore set -- skipping restore");
+            if (DEBUG) Slog.v(TAG, "Skipping");
             try {
                 mPackageManagerBinder.finishPackageInstall(token);
             } catch (RemoteException e) { /* can't happen */ }
@@ -5774,13 +5985,23 @@
                 return -1;
             }
 
+            String dirName;
+            try {
+                dirName = mRestoreTransport.transportDirName();
+            } catch (RemoteException e) {
+                // Transport went AWOL; fail.
+                Slog.e(TAG, "Unable to contact transport for restore");
+                return -1;
+            }
+
             synchronized (mQueueLock) {
                 for (int i = 0; i < mRestoreSets.length; i++) {
                     if (token == mRestoreSets[i].token) {
                         long oldId = Binder.clearCallingIdentity();
                         mWakelock.acquire();
                         Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
-                        msg.obj = new RestoreParams(mRestoreTransport, observer, token, true);
+                        msg.obj = new RestoreParams(mRestoreTransport, dirName,
+                                observer, token, true);
                         mBackupHandler.sendMessage(msg);
                         Binder.restoreCallingIdentity(oldId);
                         return 0;
@@ -5834,13 +6055,22 @@
                 return -1;
             }
 
+            String dirName;
+            try {
+                dirName = mRestoreTransport.transportDirName();
+            } catch (RemoteException e) {
+                // Transport went AWOL; fail.
+                Slog.e(TAG, "Unable to contact transport for restore");
+                return -1;
+            }
+
             synchronized (mQueueLock) {
                 for (int i = 0; i < mRestoreSets.length; i++) {
                     if (token == mRestoreSets[i].token) {
                         long oldId = Binder.clearCallingIdentity();
                         mWakelock.acquire();
                         Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
-                        msg.obj = new RestoreParams(mRestoreTransport, observer, token,
+                        msg.obj = new RestoreParams(mRestoreTransport, dirName, observer, token,
                                 packages, true);
                         mBackupHandler.sendMessage(msg);
                         Binder.restoreCallingIdentity(oldId);
@@ -5906,11 +6136,21 @@
                 return -1;
             }
 
+            String dirName;
+            try {
+                dirName = mRestoreTransport.transportDirName();
+            } catch (RemoteException e) {
+                // Transport went AWOL; fail.
+                Slog.e(TAG, "Unable to contact transport for restore");
+                return -1;
+            }
+
             // Ready to go:  enqueue the restore request and claim success
             long oldId = Binder.clearCallingIdentity();
             mWakelock.acquire();
             Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
-            msg.obj = new RestoreParams(mRestoreTransport, observer, token, app, 0, false);
+            msg.obj = new RestoreParams(mRestoreTransport, dirName,
+                    observer, token, app, 0, false);
             mBackupHandler.sendMessage(msg);
             Binder.restoreCallingIdentity(oldId);
             return 0;
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 7c61c44..41a0e6b 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -32,6 +32,7 @@
 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;
@@ -43,7 +44,9 @@
 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;
@@ -77,6 +80,7 @@
 import android.net.wimax.WimaxManagerConstants;
 import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Build;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -141,6 +145,7 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.URL;
+import java.net.URLConnection;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -154,6 +159,10 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+
 /**
  * @hide
  */
@@ -406,6 +415,8 @@
 
     private SettingsObserver mSettingsObserver;
 
+    private AppOpsManager mAppOpsManager;
+
     NetworkConfig[] mNetConfigs;
     int mNetworksDefined;
 
@@ -689,6 +700,8 @@
         filter = new IntentFilter();
         filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
         mContext.registerReceiver(mProvisioningReceiver, filter);
+
+        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
     }
 
     /**
@@ -1521,6 +1534,40 @@
     }
 
     /**
+     * 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.getNetworkPrefixLength();
+                    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
@@ -1531,14 +1578,14 @@
      * desired
      * @return {@code true} on success, {@code false} on failure
      */
-    public boolean requestRouteToHost(int networkType, int hostAddress) {
+    public boolean requestRouteToHost(int networkType, int hostAddress, String packageName) {
         InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
 
         if (inetAddress == null) {
             return false;
         }
 
-        return requestRouteToHostAddress(networkType, inetAddress.getAddress());
+        return requestRouteToHostAddress(networkType, inetAddress.getAddress(), packageName);
     }
 
     /**
@@ -1550,11 +1597,40 @@
      * desired
      * @return {@code true} on success, {@code false} on failure
      */
-    public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
+    public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress,
+            String packageName) {
         enforceChangePermission();
         if (mProtectedNetworks.contains(networkType)) {
             enforceConnectivityInternalPermission();
         }
+        boolean exempt;
+        InetAddress addr;
+        try {
+            addr = InetAddress.getByAddress(hostAddress);
+        } catch (UnknownHostException e) {
+            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);
@@ -1578,18 +1654,13 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            InetAddress addr = InetAddress.getByAddress(hostAddress);
             LinkProperties lp = tracker.getLinkProperties();
-            boolean ok = addRouteToAddress(lp, addr, EXEMPT);
+            boolean ok = addRouteToAddress(lp, addr, exempt);
             if (DBG) log("requestRouteToHostAddress ok=" + ok);
             return ok;
-        } catch (UnknownHostException e) {
-            if (DBG) log("requestRouteToHostAddress got " + e.toString());
         } finally {
             Binder.restoreCallingIdentity(token);
         }
-        if (DBG) log("requestRouteToHostAddress X bottom return false");
-        return false;
     }
 
     private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
@@ -2299,9 +2370,9 @@
             mInetConditionChangeInFlight = false;
             // Don't do this - if we never sign in stay, grey
             //reportNetworkCondition(mActiveDefaultNetwork, 100);
+            updateNetworkSettings(thisNet);
         }
         thisNet.setTeardownRequested(false);
-        updateNetworkSettings(thisNet);
         updateMtuSizeSettings(thisNet);
         handleConnectivityChange(newNetType, false);
         sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
@@ -2686,6 +2757,15 @@
             }
             setBufferSize(bufferSizes);
         }
+
+        final String defaultRwndKey = "net.tcp.default_init_rwnd";
+        int defaultRwndValue = SystemProperties.getInt(defaultRwndKey, 0);
+        Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(),
+            Settings.Global.TCP_DEFAULT_INIT_RWND, defaultRwndValue);
+        final String sysctlKey = "sys.sysctl.tcp_def_init_rwnd";
+        if (rwndValue != 0) {
+            SystemProperties.set(sysctlKey, rwndValue.toString());
+        }
     }
 
     /**
@@ -3028,7 +3108,7 @@
                 case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: {
                     info = (NetworkInfo) msg.obj;
                     int type = info.getType();
-                    updateNetworkSettings(mNetTrackers[type]);
+                    if (mNetConfigs[type].isDefault()) updateNetworkSettings(mNetTrackers[type]);
                     break;
                 }
             }
@@ -3374,6 +3454,11 @@
             String pacFileUrl = "";
             if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) ||
                     !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) {
+                if (!proxyProperties.isValid()) {
+                    if (DBG)
+                        log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+                    return;
+                }
                 mGlobalProxy = new ProxyProperties(proxyProperties);
                 host = mGlobalProxy.getHost();
                 port = mGlobalProxy.getPort();
@@ -3417,6 +3502,11 @@
             } else {
                 proxyProperties = new ProxyProperties(host, port, exclList);
             }
+            if (!proxyProperties.isValid()) {
+                if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+                return;
+            }
+
             synchronized (mProxyLock) {
                 mGlobalProxy = proxyProperties;
             }
@@ -3441,6 +3531,10 @@
         synchronized (mProxyLock) {
             if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
             if (mDefaultProxy == proxy) return; // catches repeated nulls
+            if (proxy != null &&  !proxy.isValid()) {
+                if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString());
+                return;
+            }
             mDefaultProxy = proxy;
 
             if (mGlobalProxy != null) return;
@@ -3573,8 +3667,7 @@
             int user = UserHandle.getUserId(Binder.getCallingUid());
             if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
                 synchronized(mVpns) {
-                    mVpns.get(user).protect(socket,
-                            mNetTrackers[type].getLinkProperties().getInterfaceName());
+                    mVpns.get(user).protect(socket);
                 }
                 return true;
             }
@@ -3814,7 +3907,7 @@
                 boolean forwardDns) {
             try {
                 mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
-                if (forwardDns) mNetd.clearDnsInterfaceForUidRange(uidStart, uidEnd);
+                if (forwardDns) mNetd.clearDnsInterfaceForUidRange(interfaze, uidStart, uidEnd);
             } catch (RemoteException e) {
             }
 
@@ -3969,6 +4062,14 @@
      */
     private static final int CMP_RESULT_CODE_PROVISIONING_NETWORK = 5;
 
+    /**
+     * The mobile network is provisioning
+     */
+    private static final int CMP_RESULT_CODE_IS_PROVISIONING = 6;
+
+    private AtomicBoolean mIsProvisioningNetwork = new AtomicBoolean(false);
+    private AtomicBoolean mIsStartingProvisioning = new AtomicBoolean(false);
+
     private AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false);
 
     @Override
@@ -4039,11 +4140,25 @@
                                 setProvNotificationVisible(true,
                                         ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(),
                                         url);
+                                // Mark that we've got a provisioning network and
+                                // Disable Mobile Data until user actually starts provisioning.
+                                mIsProvisioningNetwork.set(true);
+                                MobileDataStateTracker mdst = (MobileDataStateTracker)
+                                        mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+                                mdst.setInternalDataEnable(false);
                             } else {
                                 if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url");
                             }
                             break;
                         }
+                        case CMP_RESULT_CODE_IS_PROVISIONING: {
+                            // FIXME: Need to know when provisioning is done. Probably we can
+                            // check the completion status if successful we're done if we
+                            // "timedout" or still connected to provisioning APN turn off data?
+                            if (DBG) log("CheckMp.onComplete: provisioning started");
+                            mIsStartingProvisioning.set(false);
+                            break;
+                        }
                         default: {
                             loge("CheckMp.onComplete: ignore unexpected result=" + result);
                             break;
@@ -4066,8 +4181,28 @@
     static class CheckMp extends
             AsyncTask<CheckMp.Params, Void, Integer> {
         private static final String CHECKMP_TAG = "CheckMp";
+
+        // adb shell setprop persist.checkmp.testfailures 1 to enable testing failures
+        private static boolean mTestingFailures;
+
+        // Choosing 4 loops as half of them will use HTTPS and the other half HTTP
+        private static final int MAX_LOOPS = 4;
+
+        // Number of milli-seconds to complete all of the retires
         public static final int MAX_TIMEOUT_MS =  60000;
+
+        // The socket should retry only 5 seconds, the default is longer
         private static final int SOCKET_TIMEOUT_MS = 5000;
+
+        // Sleep time for network errors
+        private static final int NET_ERROR_SLEEP_SEC = 3;
+
+        // Sleep time for network route establishment
+        private static final int NET_ROUTE_ESTABLISHMENT_SLEEP_SEC = 3;
+
+        // Short sleep time for polling :(
+        private static final int POLLING_SLEEP_SEC = 1;
+
         private Context mContext;
         private ConnectivityService mCs;
         private TelephonyManager mTm;
@@ -4093,6 +4228,31 @@
             }
         }
 
+        // As explained to me by Brian Carlstrom and Kenny Root, Certificates can be
+        // issued by name or ip address, for Google its by name so when we construct
+        // this HostnameVerifier we'll pass the original Uri and use it to verify
+        // the host. If the host name in the original uril fails we'll test the
+        // hostname parameter just incase things change.
+        static class CheckMpHostnameVerifier implements HostnameVerifier {
+            Uri mOrgUri;
+
+            CheckMpHostnameVerifier(Uri orgUri) {
+                mOrgUri = orgUri;
+            }
+
+            @Override
+            public boolean verify(String hostname, SSLSession session) {
+                HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
+                String orgUriHost = mOrgUri.getHost();
+                boolean retVal = hv.verify(orgUriHost, session) || hv.verify(hostname, session);
+                if (DBG) {
+                    log("isMobileOk: hostnameVerify retVal=" + retVal + " hostname=" + hostname
+                        + " orgUriHost=" + orgUriHost);
+                }
+                return retVal;
+            }
+        }
+
         /**
          * The call back object passed in Params. onComplete will be called
          * on the main thread.
@@ -4103,6 +4263,13 @@
         }
 
         public CheckMp(Context context, ConnectivityService cs) {
+            if (Build.IS_DEBUGGABLE) {
+                mTestingFailures =
+                        SystemProperties.getInt("persist.checkmp.testfailures", 0) == 1;
+            } else {
+                mTestingFailures = false;
+            }
+
             mContext = context;
             mCs = cs;
 
@@ -4141,6 +4308,12 @@
                 return result;
             }
 
+            if (mCs.mIsStartingProvisioning.get()) {
+                result = CMP_RESULT_CODE_IS_PROVISIONING;
+                log("isMobileOk: X is provisioning result=" + result);
+                return result;
+            }
+
             // See if we've already determined we've got a provisioning connection,
             // if so we don't need to do anything active.
             MobileDataStateTracker mdstDefault = (MobileDataStateTracker)
@@ -4174,7 +4347,7 @@
                             mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
                             break;
                         }
-                        sleep(1);
+                        sleep(POLLING_SLEEP_SEC);
                     }
                 }
 
@@ -4192,7 +4365,7 @@
                     }
                     if (VDBG) log("isMobileOk: hipri not started yet");
                     result = CMP_RESULT_CODE_NO_CONNECTION;
-                    sleep(1);
+                    sleep(POLLING_SLEEP_SEC);
                 }
 
                 // Continue trying to connect until time has run out
@@ -4208,7 +4381,7 @@
                                 log("isMobileOk: not connected ni=" +
                                     mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
                             }
-                            sleep(1);
+                            sleep(POLLING_SLEEP_SEC);
                             result = CMP_RESULT_CODE_NO_CONNECTION;
                             continue;
                         }
@@ -4226,7 +4399,7 @@
 
                         // Get of the addresses associated with the url host. We need to use the
                         // address otherwise HttpURLConnection object will use the name to get
-                        // the addresses and is will try every address but that will bypass the
+                        // the addresses and will try every address but that will bypass the
                         // route to host we setup and the connection could succeed as the default
                         // interface might be connected to the internet via wifi or other interface.
                         InetAddress[] addresses;
@@ -4263,14 +4436,14 @@
 
                         int addrTried = 0;
                         while (true) {
-                            // Loop through at most 3 valid addresses or until
+                            // Loop through at most MAX_LOOPS valid addresses or until
                             // we run out of time
-                            if (addrTried++ >= 3) {
-                                log("too many loops tried - giving up");
+                            if (addrTried++ >= MAX_LOOPS) {
+                                log("isMobileOk: too many loops tried - giving up");
                                 break;
                             }
                             if (SystemClock.elapsedRealtime() >= endTime) {
-                                log("spend too much time - giving up");
+                                log("isMobileOk: spend too much time - giving up");
                                 break;
                             }
 
@@ -4279,29 +4452,41 @@
 
                             // Make a route to host so we check the specific interface.
                             if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
-                                    hostAddr.getAddress())) {
+                                    hostAddr.getAddress(), null)) {
                                 // Wait a short time to be sure the route is established ??
                                 log("isMobileOk:"
                                         + " wait to establish route to hostAddr=" + hostAddr);
-                                sleep(3);
+                                sleep(NET_ROUTE_ESTABLISHMENT_SLEEP_SEC);
                             } else {
                                 log("isMobileOk:"
                                         + " could not establish route to hostAddr=" + hostAddr);
+                                // Wait a short time before the next attempt
+                                sleep(NET_ERROR_SLEEP_SEC);
                                 continue;
                             }
 
-                            // Rewrite the url to have numeric address to use the specific route.
-                            // Add a pointless random query param to fool proxies into not caching.
-                            URL newUrl = new URL(orgUri.getScheme(),
-                                    hostAddr.getHostAddress(),
-                                    orgUri.getPath() + "?q=" + rand.nextInt(Integer.MAX_VALUE));
+                            // Rewrite the url to have numeric address to use the specific route
+                            // using http for half the attempts and https for the other half.
+                            // Doing https first and http second as on a redirected walled garden
+                            // such as t-mobile uses we get a SocketTimeoutException: "SSL
+                            // handshake timed out" which we declare as
+                            // CMP_RESULT_CODE_NO_TCP_CONNECTION. We could change this, but by
+                            // having http second we will be using logic used for some time.
+                            URL newUrl;
+                            String scheme = (addrTried <= (MAX_LOOPS/2)) ? "https" : "http";
+                            newUrl = new URL(scheme, hostAddr.getHostAddress(),
+                                        orgUri.getPath());
                             log("isMobileOk: newUrl=" + newUrl);
 
                             HttpURLConnection urlConn = null;
                             try {
-                                // Open the connection set the request header and get the response
-                                urlConn = (HttpURLConnection) newUrl.openConnection(
+                                // Open the connection set the request headers and get the response
+                                urlConn = (HttpURLConnection)newUrl.openConnection(
                                         java.net.Proxy.NO_PROXY);
+                                if (scheme.equals("https")) {
+                                    ((HttpsURLConnection)urlConn).setHostnameVerifier(
+                                            new CheckMpHostnameVerifier(orgUri));
+                                }
                                 urlConn.setInstanceFollowRedirects(false);
                                 urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS);
                                 urlConn.setReadTimeout(SOCKET_TIMEOUT_MS);
@@ -4320,10 +4505,17 @@
                                 urlConn.disconnect();
                                 urlConn = null;
 
+                                if (mTestingFailures) {
+                                    // Pretend no connection, this tests using http and https
+                                    result = CMP_RESULT_CODE_NO_CONNECTION;
+                                    log("isMobileOk: TESTING_FAILURES, pretend no connction");
+                                    continue;
+                                }
+
                                 if (responseCode == 204) {
                                     // Return
                                     result = CMP_RESULT_CODE_CONNECTABLE;
-                                    log("isMobileOk: X expected responseCode=" + responseCode
+                                    log("isMobileOk: X got expected responseCode=" + responseCode
                                             + " result=" + result);
                                     return result;
                                 } else {
@@ -4337,12 +4529,14 @@
                                     result = CMP_RESULT_CODE_REDIRECTED;
                                 }
                             } catch (Exception e) {
-                                log("isMobileOk: HttpURLConnection Exception e=" + e);
+                                log("isMobileOk: HttpURLConnection Exception" + e);
                                 result = CMP_RESULT_CODE_NO_TCP_CONNECTION;
                                 if (urlConn != null) {
                                     urlConn.disconnect();
                                     urlConn = null;
                                 }
+                                sleep(NET_ERROR_SLEEP_SEC);
+                                continue;
                             }
                         }
                         log("isMobileOk: X loops|timed out result=" + result);
@@ -4370,7 +4564,7 @@
                             log("isMobileOk: connected ni=" +
                                 mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
                         }
-                        sleep(1);
+                        sleep(POLLING_SLEEP_SEC);
                         continue;
                     }
                 }
@@ -4435,7 +4629,7 @@
             }
         }
 
-        private void log(String s) {
+        private static void log(String s) {
             Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s);
         }
     }
@@ -4454,19 +4648,20 @@
     };
 
     private void handleMobileProvisioningAction(String url) {
-        // Notication mark notification as not visible
+        // Mark notification as not visible
         setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null);
 
         // If provisioning network handle as a special case,
         // otherwise launch browser with the intent directly.
-        NetworkInfo ni = getProvisioningNetworkInfo();
-        if ((ni != null) && ni.isConnectedToProvisioningNetwork()) {
-            if (DBG) log("handleMobileProvisioningAction: on provisioning network");
+        if (mIsProvisioningNetwork.get()) {
+            if (DBG) log("handleMobileProvisioningAction: on prov network enable then launch");
+            mIsStartingProvisioning.set(true);
             MobileDataStateTracker mdst = (MobileDataStateTracker)
                     mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+            mdst.setEnableFailFastMobileData(DctConstants.ENABLED);
             mdst.enableMobileProvisioning(url);
         } else {
-            if (DBG) log("handleMobileProvisioningAction: on default network");
+            if (DBG) log("handleMobileProvisioningAction: not prov network, launch browser directly");
             Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
                     Intent.CATEGORY_APP_BROWSER);
             newIntent.setData(Uri.parse(url));
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 8cc80f7..2bb99d6 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -56,6 +56,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.net.ProxyProperties;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
@@ -522,6 +523,7 @@
                         || pm.getReceiverInfo(aa.info.getComponent(), 0, userHandle) == null) {
                     removed = true;
                     policy.mAdminList.remove(i);
+                    policy.mAdminMap.remove(aa.info.getComponent());
                 }
             } catch (RemoteException re) {
                 // Shouldn't happen
@@ -2463,6 +2465,12 @@
         }
         exclusionList = exclusionList.trim();
         ContentResolver res = mContext.getContentResolver();
+
+        ProxyProperties proxyProperties = new ProxyProperties(data[0], proxyPort, exclusionList);
+        if (!proxyProperties.isValid()) {
+            Slog.e(TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString());
+            return;
+        }
         Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
         Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
         Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index 8eaa91d..399e7d1 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -123,6 +123,18 @@
 # ---------------------------
 # Out of memory for surfaces.
 31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3)
+# Task created.
+31001 wm_task_created (TaskId|1|5),(StackId|1|5)
+# Task moved to top (1) or bottom (0).
+31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1)
+# Task removed with source explanation.
+31003 wm_task_removed (TaskId|1|5),(Reason|3)
+# Stack created.
+31004 wm_stack_created (StackId|1|5),(RelativeBoxId|1|5),(Position|1),(Weight|1|6)
+# Home stack moved to top (1) or bottom (0).
+31005 wm_home_stack_moved (ToTop|1)
+# Stack removed.
+31006 wm_stack_removed (StackId|1|5)
 
 
 # ---------------------------
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 562a50f..a996dbd 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -309,6 +309,9 @@
             mShortcutInputMethodsAndSubtypes =
                 new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
 
+    // Was the keyguard locked when this client became current?
+    private boolean mCurClientInKeyguard;
+
     /**
      * Set to true if our ServiceConnection is currently actively bound to
      * a service (whether or not we have gotten its IBinder back yet).
@@ -385,7 +388,6 @@
     private Locale mLastSystemLocale;
     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
     private final IPackageManager mIPackageManager;
-    private boolean mInputBoundToKeyguard;
 
     class SettingsObserver extends ContentObserver {
         String mLastEnabled = "";
@@ -874,12 +876,9 @@
         final boolean hardKeyShown = haveHardKeyboard
                 && conf.hardKeyboardHidden
                         != Configuration.HARDKEYBOARDHIDDEN_YES;
-        final boolean isScreenLocked =
-                mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
-        final boolean isScreenSecurelyLocked =
-                isScreenLocked && mKeyguardManager.isKeyguardSecure();
-        final boolean inputShown = mInputShown && (!isScreenLocked || mInputBoundToKeyguard);
-        final boolean inputActive = !isScreenSecurelyLocked && (inputShown || hardKeyShown);
+
+        final boolean isScreenLocked = isKeyguardLocked();
+        final boolean inputActive = !isScreenLocked && (mInputShown || hardKeyShown);
         // We assume the softkeyboard is shown when the input is active as long as the
         // hard keyboard is not shown.
         final boolean inputVisible = inputActive && !hardKeyShown;
@@ -1135,19 +1134,14 @@
             return mNoBinding;
         }
 
-        if (mCurClient == null) {
-            mInputBoundToKeyguard = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
-            if (DEBUG) {
-                Slog.v(TAG, "New bind. keyguard = " +  mInputBoundToKeyguard);
-            }
-        }
-
         if (mCurClient != cs) {
+            // Was the keyguard locked when switching over to the new client?
+            mCurClientInKeyguard = isKeyguardLocked();
             // If the client is changing, we need to switch over to the new
             // one.
             unbindCurrentClientLocked();
             if (DEBUG) Slog.v(TAG, "switching to client: client = "
-                    + cs.client.asBinder());
+                    + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
 
             // If the screen is on, inform the new client it is active
             if (mScreenOn) {
@@ -1499,6 +1493,10 @@
         }
     }
 
+    private boolean isKeyguardLocked() {
+        return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+    }
+
     // Caution! This method is called in this class. Handle multi-user carefully
     @SuppressWarnings("deprecation")
     @Override
@@ -1510,8 +1508,11 @@
                 Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
                 return;
             }
-
             synchronized (mMethodMap) {
+                // apply policy for binder calls
+                if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
+                    vis = 0;
+                }
                 mImeWindowVis = vis;
                 mBackDisposition = backDisposition;
                 if (mStatusBar != null) {
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 8f480dd..eebd1c5 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -1148,6 +1148,11 @@
             boolean shouldBeEnabled = isAllowedByCurrentUserSettingsLocked(name);
             if (isEnabled && !shouldBeEnabled) {
                 updateProviderListenersLocked(name, false, mCurrentUserId);
+                // If any provider has been disabled, clear all last locations for all providers.
+                // This is to be on the safe side in case a provider has location derived from
+                // this disabled provider.
+                mLastLocation.clear();
+                mLastLocationCoarseInterval.clear();
                 changesMade = true;
             } else if (!isEnabled && shouldBeEnabled) {
                 updateProviderListenersLocked(name, true, mCurrentUserId);
diff --git a/services/java/com/android/server/LockSettingsService.java b/services/java/com/android/server/LockSettingsService.java
index cd746cf..35e7afa 100644
--- a/services/java/com/android/server/LockSettingsService.java
+++ b/services/java/com/android/server/LockSettingsService.java
@@ -154,11 +154,11 @@
     }
 
     private final void checkWritePermission(int userId) {
-        mContext.checkCallingOrSelfPermission(PERMISSION);
+        mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
     }
 
     private final void checkPasswordReadPermission(int userId) {
-        mContext.checkCallingOrSelfPermission(PERMISSION);
+        mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
     }
 
     private final void checkReadPermission(String requestedKey, int userId) {
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 92f99c2..72fce62 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -1567,10 +1567,10 @@
     }
 
     @Override
-    public void clearDnsInterfaceForUidRange(int uid_start, int uid_end) {
+    public void clearDnsInterfaceForUidRange(String iface, int uid_start, int uid_end) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
-            mConnector.execute("resolver", "clearifaceforuidrange", uid_start, uid_end);
+            mConnector.execute("resolver", "clearifaceforuidrange", iface, uid_start, uid_end);
         } catch (NativeDaemonConnectorException e) {
             throw e.rethrowAsParcelableException();
         }
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 0438675..dedc9bd 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -260,10 +260,11 @@
 
         @Override
         public void binderDied() {
-            if (connection == null) {
-                // This is not a service; it won't be recreated. We can give up this connection.
-                unregisterListener(this.listener, this.userid);
-            }
+            // Remove the listener, but don't unbind from the service. The system will bring the
+            // service back up, and the onServiceConnected handler will readd the listener with the
+            // new binding. If this isn't a bound service, and is just a registered
+            // INotificationListener, just removing it from the list is all we need to do anyway.
+            removeListenerImpl(this.listener, this.userid);
         }
 
         /** convenience method for looking in mEnabledListenersForCurrentUser */
@@ -757,26 +758,36 @@
     }
 
     /**
-     * Remove a listener binder directly
+     * Removes a listener from the list and unbinds from its service.
      */
-    @Override
-    public void unregisterListener(INotificationListener listener, int userid) {
-        // no need to check permissions; if your listener binder is in the list,
-        // that's proof that you had permission to add it in the first place
+    public void unregisterListener(final INotificationListener listener, final int userid) {
+        if (listener == null) return;
 
+        NotificationListenerInfo info = removeListenerImpl(listener, userid);
+        if (info != null && info.connection != null) {
+            mContext.unbindService(info.connection);
+        }
+    }
+
+    /**
+     * Removes a listener from the list but does not unbind from the listener's service.
+     *
+     * @return the removed listener.
+     */
+    NotificationListenerInfo removeListenerImpl(
+            final INotificationListener listener, final int userid) {
+        NotificationListenerInfo listenerInfo = null;
         synchronized (mNotificationList) {
             final int N = mListeners.size();
             for (int i=N-1; i>=0; i--) {
                 final NotificationListenerInfo info = mListeners.get(i);
                 if (info.listener.asBinder() == listener.asBinder()
                         && info.userid == userid) {
-                    mListeners.remove(i);
-                    if (info.connection != null) {
-                        mContext.unbindService(info.connection);
-                    }
+                    listenerInfo = mListeners.remove(i);
                 }
             }
         }
+        return listenerInfo;
     }
 
     /**
diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java
index e0f415b..16d2468 100644
--- a/services/java/com/android/server/NsdService.java
+++ b/services/java/com/android/server/NsdService.java
@@ -483,10 +483,14 @@
                         clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
 
                         stopResolveService(id);
-                        if (!getAddrInfo(id, cooked[3])) {
+                        removeRequestMap(clientId, id, clientInfo);
+
+                        int id2 = getUniqueId();
+                        if (getAddrInfo(id2, cooked[3])) {
+                            storeRequestMap(clientId, id2, clientInfo);
+                        } else {
                             clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
                                     NsdManager.FAILURE_INTERNAL_ERROR, clientId);
-                            removeRequestMap(clientId, id, clientInfo);
                             clientInfo.mResolvedService = null;
                         }
                         break;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0e0f156..a42cbcf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -55,6 +55,7 @@
 import com.android.server.display.DisplayManagerService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.input.InputManagerService;
+import com.android.server.media.MediaRouterService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
 import com.android.server.os.SchedulingPolicyService;
@@ -356,6 +357,7 @@
         DreamManagerService dreamy = null;
         AssetAtlasService atlas = null;
         PrintManagerService printManager = null;
+        MediaRouterService mediaRouter = null;
 
         // Bring up services needed for UI.
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
@@ -804,6 +806,16 @@
             } catch (Throwable e) {
                 reportWtf("starting Print Service", e);
             }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "Media Router Service");
+                    mediaRouter = new MediaRouterService(context);
+                    ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
+                } catch (Throwable e) {
+                    reportWtf("starting MediaRouterService", e);
+                }
+            }
         }
 
         // Before things start rolling, be sure we have decided whether
@@ -916,6 +928,7 @@
         final InputManagerService inputManagerF = inputManager;
         final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
         final PrintManagerService printManagerF = printManager;
+        final MediaRouterService mediaRouterF = mediaRouter;
 
         // We now tell the activity manager it is okay to run third party
         // code.  It will call back into us once it has gotten to the state
@@ -1063,6 +1076,12 @@
                 } catch (Throwable e) {
                     reportWtf("Notifying PrintManagerService running", e);
                 }
+
+                try {
+                    if (mediaRouterF != null) mediaRouterF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying MediaRouterService running", e);
+                }
             }
         });
 
@@ -1104,6 +1123,19 @@
     private static native void nativeInit();
 
     public static void main(String[] args) {
+
+        /*
+         * In case the runtime switched since last boot (such as when
+         * the old runtime was removed in an OTA), set the system
+         * property so that it is in sync. We can't do this in
+         * libnativehelper's JniInvocation::Init code where we already
+         * had to fallback to a different runtime because it is
+         * running as root and we need to be the system user to set
+         * the property. http://b/11463182
+         */
+        SystemProperties.set("persist.sys.dalvik.vm.lib",
+                             VMRuntime.getRuntime().vmLibrary());
+
         if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) {
             // If a device's clock is before 1970 (before 0), a lot of
             // APIs crash dealing with negative numbers, notably
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index 6957bac..e6b6b93 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -40,6 +40,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.graphics.Point;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
@@ -637,6 +638,14 @@
         return false;
     }
 
+    private Point getDefaultDisplaySize() {
+        Point p = new Point();
+        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        Display d = wm.getDefaultDisplay();
+        d.getRealSize(p);
+        return p;
+    }
+
     public void setDimensionHints(int width, int height) throws RemoteException {
         checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
         synchronized (mLock) {
@@ -648,6 +657,10 @@
             if (width <= 0 || height <= 0) {
                 throw new IllegalArgumentException("width and height must be > 0");
             }
+            // Make sure it is at least as large as the display.
+            Point displaySize = getDefaultDisplaySize();
+            width = Math.max(width, displaySize.x);
+            height = Math.max(height, displaySize.y);
 
             if (width != wallpaper.width || height != wallpaper.height) {
                 wallpaper.width = width;
@@ -1146,9 +1159,7 @@
         }
 
         // We always want to have some reasonable width hint.
-        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
-        Display d = wm.getDefaultDisplay();
-        int baseSize = d.getMaximumSizeDimension();
+        int baseSize = getMaximumSizeDimension();
         if (wallpaper.width < baseSize) {
             wallpaper.width = baseSize;
         }
@@ -1157,6 +1168,12 @@
         }
     }
 
+    private int getMaximumSizeDimension() {
+        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        Display d = wm.getDefaultDisplay();
+        return d.getMaximumSizeDimension();
+    }
+
     // Called by SystemBackupAgent after files are restored to disk.
     void settingsRestored() {
         // TODO: If necessary, make it work for secondary users as well. This currently assumes
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index a99b58a..43f12eb 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -76,7 +76,7 @@
     private static final int STATE_DELEGATING = 0x00000004;
     private static final int STATE_GESTURE_DETECTING = 0x00000005;
 
-    // The minimum of the cosine between the vectors of two moving
+    // The maximum of the cosine between the vectors of two moving
     // pointers so they can be considered moving in the same direction.
     private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
 
@@ -436,13 +436,19 @@
                         final int pointerIdBits = (1 << pointerId);
                         mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits,
                                 policyFlags);
+                    } else {
+                        // Cache the event until we discern exploration from gesturing.
+                        mSendHoverEnterAndMoveDelayed.addEvent(event);
                     }
-                    // Cache the event until we discern exploration from gesturing.
-                    mSendHoverEnterAndMoveDelayed.addEvent(event);
                 }
             } break;
             case MotionEvent.ACTION_POINTER_DOWN: {
-                /* do nothing - let the code for ACTION_MOVE decide what to do */
+                // Another finger down means that if we have not started to deliver
+                // hover events, we will not have to. The code for ACTION_MOVE will
+                // decide what we will actually do next.
+                mSendHoverEnterAndMoveDelayed.cancel();
+                mSendHoverExitDelayed.cancel();
+                mPerformLongPressDelayed.cancel();
             } break;
             case MotionEvent.ACTION_MOVE: {
                 final int pointerId = receivedTracker.getPrimaryPointerId();
@@ -518,14 +524,11 @@
                                     mPerformLongPressDelayed.cancel();
                                 }
                             }
-                            // The user is either double tapping or performing a long
-                            // press, so do not send move events yet.
-                            if (mDoubleTapDetector.firstTapDetected()) {
-                                break;
+                            if (mTouchExplorationInProgress) {
+                                sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
+                                sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
+                                        policyFlags);
                             }
-                            sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
-                            sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
-                                    policyFlags);
                         }
                     } break;
                     case 2: {
@@ -539,22 +542,24 @@
                             mPerformLongPressDelayed.cancel();
                         } else {
                             mPerformLongPressDelayed.cancel();
-                            // If the user is touch exploring the second pointer may be
-                            // performing a double tap to activate an item without need
-                            // for the user to lift his exploring finger.
-                            // It is *important* to use the distance traveled by the pointers
-                            // on the screen which may or may not be magnified.
-                            final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
-                                    - rawEvent.getX(pointerIndex);
-                            final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
-                                    - rawEvent.getY(pointerIndex);
-                            final double moveDelta = Math.hypot(deltaX, deltaY);
-                            if (moveDelta < mDoubleTapSlop) {
-                                break;
+                            if (mTouchExplorationInProgress) {
+                                // If the user is touch exploring the second pointer may be
+                                // performing a double tap to activate an item without need
+                                // for the user to lift his exploring finger.
+                                // It is *important* to use the distance traveled by the pointers
+                                // on the screen which may or may not be magnified.
+                                final float deltaX = receivedTracker.getReceivedPointerDownX(
+                                        pointerId) - rawEvent.getX(pointerIndex);
+                                final float deltaY = receivedTracker.getReceivedPointerDownY(
+                                        pointerId) - rawEvent.getY(pointerIndex);
+                                final double moveDelta = Math.hypot(deltaX, deltaY);
+                                if (moveDelta < mDoubleTapSlop) {
+                                    break;
+                                }
+                                // We are sending events so send exit and gesture
+                                // end since we transition to another state.
+                                sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
                             }
-                            // We are sending events so send exit and gesture
-                            // end since we transition to another state.
-                            sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
                         }
 
                         // We know that a new state transition is to happen and the
@@ -736,20 +741,34 @@
                         + "there is at least one pointer down!");
             }
             case MotionEvent.ACTION_UP: {
-                mAms.onTouchInteractionEnd();
+                // Offset the event if we are doing a long press as the
+                // target is not necessarily under the user's finger.
+                if (mLongPressingPointerId >= 0) {
+                    event = offsetEvent(event, - mLongPressingPointerDeltaX,
+                            - mLongPressingPointerDeltaY);
+                    // Clear the long press state.
+                    mLongPressingPointerId = -1;
+                    mLongPressingPointerDeltaX = 0;
+                    mLongPressingPointerDeltaY = 0;
+                }
+
+                // Deliver the event.
+                sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+
                 // Announce the end of a the touch interaction.
+                mAms.onTouchInteractionEnd();
                 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
-                mLongPressingPointerId = -1;
-                mLongPressingPointerDeltaX = 0;
-                mLongPressingPointerDeltaY = 0;
+
                 mCurrentState = STATE_TOUCH_EXPLORING;
             } break;
             case MotionEvent.ACTION_CANCEL: {
                 clear(event, policyFlags);
             } break;
+            default: {
+                // Deliver the event.
+                sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+            }
         }
-        // Deliver the event.
-        sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
     }
 
     private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
@@ -959,27 +978,8 @@
         // long press it, or even worse to avoid the user long pressing
         // on the wrong item since click and long press behave differently.
         if (mLongPressingPointerId >= 0) {
-            final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
-            final int pointerCount = event.getPointerCount();
-            PointerProperties[] props = PointerProperties.createArray(pointerCount);
-            PointerCoords[] coords = PointerCoords.createArray(pointerCount);
-            for (int i = 0; i < pointerCount; i++) {
-                event.getPointerProperties(i, props[i]);
-                event.getPointerCoords(i, coords[i]);
-                if (i == remappedIndex) {
-                    coords[i].x -= mLongPressingPointerDeltaX;
-                    coords[i].y -= mLongPressingPointerDeltaY;
-                }
-            }
-            MotionEvent remapped = MotionEvent.obtain(event.getDownTime(),
-                    event.getEventTime(), event.getAction(), event.getPointerCount(),
-                    props, coords, event.getMetaState(), event.getButtonState(),
-                    1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
-                    event.getSource(), event.getFlags());
-            if (event != prototype) {
-                event.recycle();
-            }
-            event = remapped;
+            event = offsetEvent(event, - mLongPressingPointerDeltaX,
+                    - mLongPressingPointerDeltaY);
         }
 
         if (DEBUG) {
@@ -1004,6 +1004,39 @@
     }
 
     /**
+     * Offsets all pointers in the given event by adding the specified X and Y
+     * offsets.
+     *
+     * @param event The event to offset.
+     * @param offsetX The X offset.
+     * @param offsetY The Y offset.
+     * @return An event with the offset pointers or the original event if both
+     *         offsets are zero.
+     */
+    private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) {
+        if (offsetX == 0 && offsetY == 0) {
+            return event;
+        }
+        final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
+        final int pointerCount = event.getPointerCount();
+        PointerProperties[] props = PointerProperties.createArray(pointerCount);
+        PointerCoords[] coords = PointerCoords.createArray(pointerCount);
+        for (int i = 0; i < pointerCount; i++) {
+            event.getPointerProperties(i, props[i]);
+            event.getPointerCoords(i, coords[i]);
+            if (i == remappedIndex) {
+                coords[i].x += offsetX;
+                coords[i].y += offsetY;
+            }
+        }
+        return MotionEvent.obtain(event.getDownTime(),
+                event.getEventTime(), event.getAction(), event.getPointerCount(),
+                props, coords, event.getMetaState(), event.getButtonState(),
+                1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
+                event.getSource(), event.getFlags());
+    }
+
+    /**
      * Computes the action for an injected event based on a masked action
      * and a pointer index.
      *
@@ -1674,7 +1707,6 @@
 
         // Keep track of the last up pointer data.
         private long mLastReceivedUpPointerDownTime;
-        private int mLastReceivedUpPointerId;
         private float mLastReceivedUpPointerDownX;
         private float mLastReceivedUpPointerDownY;
 
@@ -1690,7 +1722,6 @@
             mReceivedPointersDown = 0;
             mPrimaryPointerId = 0;
             mLastReceivedUpPointerDownTime = 0;
-            mLastReceivedUpPointerId = 0;
             mLastReceivedUpPointerDownX = 0;
             mLastReceivedUpPointerDownY = 0;
         }
@@ -1823,7 +1854,6 @@
             final int pointerId = event.getPointerId(pointerIndex);
             final int pointerFlag = (1 << pointerId);
 
-            mLastReceivedUpPointerId = 0;
             mLastReceivedUpPointerDownTime = 0;
             mLastReceivedUpPointerDownX = 0;
             mLastReceivedUpPointerDownX = 0;
@@ -1848,7 +1878,6 @@
             final int pointerId = event.getPointerId(pointerIndex);
             final int pointerFlag = (1 << pointerId);
 
-            mLastReceivedUpPointerId = pointerId;
             mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
             mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId];
             mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId];
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index f972f70..aa9849e 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -190,10 +190,10 @@
         private final HashMap<String, Account[]> accountCache =
                 new LinkedHashMap<String, Account[]>();
         /** protected by the {@link #cacheLock} */
-        private HashMap<Account, HashMap<String, String>> userDataCache =
+        private final HashMap<Account, HashMap<String, String>> userDataCache =
                 new HashMap<Account, HashMap<String, String>>();
         /** protected by the {@link #cacheLock} */
-        private HashMap<Account, HashMap<String, String>> authTokenCache =
+        private final HashMap<Account, HashMap<String, String>> authTokenCache =
                 new HashMap<Account, HashMap<String, String>>();
 
         UserAccounts(Context context, int userId) {
@@ -475,6 +475,7 @@
         validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */);
     }
 
+    @Override
     public String getPassword(Account account) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "getPassword: " + account
@@ -514,6 +515,7 @@
         }
     }
 
+    @Override
     public String getUserData(Account account, String key) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "getUserData: " + account
@@ -533,6 +535,7 @@
         }
     }
 
+    @Override
     public AuthenticatorDescription[] getAuthenticatorTypes() {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "getAuthenticatorTypes: "
@@ -763,6 +766,7 @@
         return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
     }
 
+    @Override
     public void hasFeatures(IAccountManagerResponse response,
             Account account, String[] features) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -840,6 +844,7 @@
         }
     }
 
+    @Override
     public void removeAccount(IAccountManagerResponse response, Account account) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "removeAccount: " + account
@@ -1049,6 +1054,7 @@
         }
     }
 
+    @Override
     public String peekAuthToken(Account account, String authTokenType) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "peekAuthToken: " + account
@@ -1068,6 +1074,7 @@
         }
     }
 
+    @Override
     public void setAuthToken(Account account, String authTokenType, String authToken) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "setAuthToken: " + account
@@ -1087,6 +1094,7 @@
         }
     }
 
+    @Override
     public void setPassword(Account account, String password) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "setAuthToken: " + account
@@ -1135,6 +1143,7 @@
         mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
     }
 
+    @Override
     public void clearPassword(Account account) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "clearPassword: " + account
@@ -1152,6 +1161,7 @@
         }
     }
 
+    @Override
     public void setUserData(Account account, String key, String value) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "setUserData: " + account
@@ -1225,6 +1235,7 @@
         }
     }
 
+    @Override
     public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType,
                                   final String authTokenType)
             throws RemoteException {
@@ -1271,6 +1282,7 @@
         }
     }
 
+    @Override
     public void getAuthToken(IAccountManagerResponse response, final Account account,
             final String authTokenType, final boolean notifyOnAuthFailure,
             final boolean expectActivityLaunch, Bundle loginOptionsIn) {
@@ -1284,8 +1296,22 @@
                     + ", pid " + Binder.getCallingPid());
         }
         if (response == null) throw new IllegalArgumentException("response is null");
-        if (account == null) throw new IllegalArgumentException("account is null");
-        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        try {
+            if (account == null) {
+                Slog.w(TAG, "getAuthToken called with null account");
+                response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "account is null");
+                return;
+            }
+            if (authTokenType == null) {
+                Slog.w(TAG, "getAuthToken called with null authTokenType");
+                response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "authTokenType is null");
+                return;
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to report error back to the client." + e);
+            return;
+        }
+
         checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
         final UserAccounts accounts = getUserAccountsForCaller();
         final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
@@ -1294,11 +1320,6 @@
         final boolean customTokens =
             authenticatorInfo != null && authenticatorInfo.type.customTokens;
 
-        // Check to see that the app is authorized to access the account, in case it's a
-        // restricted account.
-        if (!ArrayUtils.contains(getAccounts((String) null), account)) {
-            throw new IllegalArgumentException("no such account");
-        }
         // skip the check if customTokens
         final int callerUid = Binder.getCallingUid();
         final boolean permissionGranted = customTokens ||
@@ -1472,6 +1493,7 @@
         return id;
     }
 
+    @Override
     public void addAccount(final IAccountManagerResponse response, final String accountType,
             final String authTokenType, final String[] requiredFeatures,
             final boolean expectActivityLaunch, final Bundle optionsIn) {
@@ -1582,6 +1604,7 @@
         }
     }
 
+    @Override
     public void updateCredentials(IAccountManagerResponse response, final Account account,
             final String authTokenType, final boolean expectActivityLaunch,
             final Bundle loginOptions) {
@@ -1620,6 +1643,7 @@
         }
     }
 
+    @Override
     public void editProperties(IAccountManagerResponse response, final String accountType,
             final boolean expectActivityLaunch) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1657,7 +1681,7 @@
         private volatile Account[] mAccountsOfType = null;
         private volatile ArrayList<Account> mAccountsWithFeatures = null;
         private volatile int mCurrentAccount = 0;
-        private int mCallingUid;
+        private final int mCallingUid;
 
         public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
                 IAccountManagerResponse response, String type, String[] features, int callingUid) {
@@ -1941,6 +1965,7 @@
         return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, packageUid);
     }
 
+    @Override
     public void getAccountsByFeatures(IAccountManagerResponse response,
             String type, String[] features) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2069,6 +2094,7 @@
             unbind();
         }
 
+        @Override
         public void binderDied() {
             mResponse = null;
             close();
@@ -2112,6 +2138,7 @@
             mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
         }
 
+        @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
             try {
@@ -2122,6 +2149,7 @@
             }
         }
 
+        @Override
         public void onServiceDisconnected(ComponentName name) {
             mAuthenticator = null;
             IAccountManagerResponse response = getResponseAndClose();
@@ -2217,7 +2245,14 @@
                             Log.v(TAG, getClass().getSimpleName()
                                     + " calling onResult() on response " + response);
                         }
-                        response.onResult(result);
+                        if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) &&
+                                (intent == null)) {
+                            // All AccountManager error codes are greater than 0
+                            response.onError(result.getInt(AccountManager.KEY_ERROR_CODE),
+                                    result.getString(AccountManager.KEY_ERROR_MESSAGE));
+                        } else {
+                            response.onResult(result);
+                        }
                     }
                 } catch (RemoteException e) {
                     // if the caller is dead then there is no one to care about remote exceptions
@@ -2228,10 +2263,12 @@
             }
         }
 
+        @Override
         public void onRequestContinued() {
             mNumRequestContinued++;
         }
 
+        @Override
         public void onError(int errorCode, String errorMessage) {
             mNumErrors++;
             IAccountManagerResponse response = getResponseAndClose();
@@ -2731,6 +2768,7 @@
         return true;
     }
 
+    @Override
     public void updateAppPermission(Account account, String authTokenType, int uid, boolean value)
             throws RemoteException {
         final int callingUid = getCallingUid();
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index a64940c..17bb3e0 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,7 @@
 
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import com.android.internal.app.ProcessStats;
 import com.android.internal.os.BatteryStatsImpl;
@@ -63,7 +64,7 @@
     static final boolean DEBUG_SERVICE = ActivityManagerService.DEBUG_SERVICE;
     static final boolean DEBUG_SERVICE_EXECUTING = ActivityManagerService.DEBUG_SERVICE_EXECUTING;
     static final boolean DEBUG_DELAYED_SERVICE = ActivityManagerService.DEBUG_SERVICE;
-    static final boolean DEBUG_DELAYED_STATS = DEBUG_DELAYED_SERVICE;
+    static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE;
     static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU;
     static final String TAG = ActivityManagerService.TAG;
     static final String TAG_MU = ActivityManagerService.TAG_MU;
@@ -76,7 +77,7 @@
 
     // How long a service needs to be running until restarting its process
     // is no longer considered to be a relaunch of the service.
-    static final int SERVICE_RESTART_DURATION = 5*1000;
+    static final int SERVICE_RESTART_DURATION = 1*1000;
 
     // How long a service needs to be running until it will start back at
     // SERVICE_RESTART_DURATION after being killed.
@@ -185,11 +186,11 @@
 
         void ensureNotStartingBackground(ServiceRecord r) {
             if (mStartingBackground.remove(r)) {
-                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer background starting: " + r);
+                if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer background starting: " + r);
                 rescheduleDelayedStarts();
             }
             if (mDelayedStartList.remove(r)) {
-                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer delaying start: " + r);
+                if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer delaying start: " + r);
             }
         }
 
@@ -207,7 +208,7 @@
             while (mDelayedStartList.size() > 0
                     && mStartingBackground.size() < mMaxStartingBackground) {
                 ServiceRecord r = mDelayedStartList.remove(0);
-                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r);
+                if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r);
                 if (r.pendingStarts.size() <= 0) {
                     Slog.w(TAG, "**** NO PENDING STARTS! " + r + " startReq=" + r.startRequested
                             + " delayedStop=" + r.delayedStop);
@@ -239,7 +240,12 @@
 
     public ActiveServices(ActivityManagerService service) {
         mAm = service;
-        mMaxStartingBackground = ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
+        int maxBg = 0;
+        try {
+            maxBg = Integer.parseInt(SystemProperties.get("ro.config.max_starting_bg", "0"));
+        } catch(RuntimeException e) {
+        }
+        mMaxStartingBackground = maxBg > 0 ? maxBg : ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
     }
 
     ServiceRecord getServiceByName(ComponentName name, int callingUser) {
@@ -270,7 +276,7 @@
     ComponentName startServiceLocked(IApplicationThread caller,
             Intent service, String resolvedType,
             int callingPid, int callingUid, int userId) {
-        if (DEBUG_DELAYED_STATS) Slog.v(TAG, "startService: " + service
+        if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "startService: " + service
                 + " type=" + resolvedType + " args=" + service.getExtras());
 
         final boolean callerFg;
@@ -301,7 +307,7 @@
         ServiceRecord r = res.record;
         NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
                 callingUid, r.packageName, service, service.getFlags(), null);
-        if (unscheduleServiceRestartLocked(r)) {
+        if (unscheduleServiceRestartLocked(r, callingUid, false)) {
             if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r);
         }
         r.lastActivity = SystemClock.uptimeMillis();
@@ -330,7 +336,7 @@
                 if (r.delayed) {
                     // This service is already scheduled for a delayed start; just leave
                     // it still waiting.
-                    if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Continuing to delay: " + r);
+                    if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Continuing to delay: " + r);
                     return r.name;
                 }
                 if (smap.mStartingBackground.size() >= mMaxStartingBackground) {
@@ -340,15 +346,15 @@
                     r.delayed = true;
                     return r.name;
                 }
-                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying: " + r);
+                if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Not delaying: " + r);
                 addToStarting = true;
             } else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
                 // We slightly loosen when we will enqueue this new service as a background
                 // starting service we are waiting for, to also include processes that are
                 // currently running other services or receivers.
                 addToStarting = true;
-                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying, but counting as bg: " + r);
-            } else if (DEBUG_DELAYED_STATS) {
+                if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Not delaying, but counting as bg: " + r);
+            } else if (DEBUG_DELAYED_STARTS) {
                 StringBuilder sb = new StringBuilder(128);
                 sb.append("Not potential delay (state=").append(proc.curProcState)
                         .append(' ').append(proc.adjType);
@@ -361,7 +367,7 @@
                 sb.append(r.toString());
                 Slog.v(TAG, sb.toString());
             }
-        } else if (DEBUG_DELAYED_STATS) {
+        } else if (DEBUG_DELAYED_STARTS) {
             if (callerFg) {
                 Slog.v(TAG, "Not potential delay (callerFg=" + callerFg + " uid="
                         + callingUid + " pid=" + callingPid + "): " + r);
@@ -398,7 +404,7 @@
                 RuntimeException here = new RuntimeException("here");
                 here.fillInStackTrace();
                 Slog.v(TAG, "Starting background (first=" + first + "): " + r, here);
-            } else if (DEBUG_DELAYED_STATS) {
+            } else if (DEBUG_DELAYED_STARTS) {
                 Slog.v(TAG, "Starting background (first=" + first + "): " + r);
             }
             if (first) {
@@ -416,7 +422,7 @@
             // If service isn't actually running, but is is being held in the
             // delayed list, then we need to keep it started but note that it
             // should be stopped once no longer delayed.
-            if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Delaying stop of pending: " + service);
+            if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Delaying stop of pending: " + service);
             service.delayedStop = true;
             return;
         }
@@ -564,7 +570,7 @@
                     if (r.isForeground) {
                         r.isForeground = false;
                         if (r.app != null) {
-                            mAm.updateLruProcessLocked(r.app, false, false);
+                            mAm.updateLruProcessLocked(r.app, false, null);
                             updateServiceForegroundLocked(r.app, true);
                         }
                     }
@@ -597,6 +603,42 @@
         }
     }
 
+    private boolean updateServiceClientActivitiesLocked(ProcessRecord proc,
+            ConnectionRecord modCr) {
+        if (modCr != null && modCr.binding.client != null) {
+            if (modCr.binding.client.activities.size() <= 0) {
+                // This connection is from a client without activities, so adding
+                // and removing is not interesting.
+                return false;
+            }
+        }
+
+        boolean anyClientActivities = false;
+        for (int i=proc.services.size()-1; i>=0 && !anyClientActivities; i--) {
+            ServiceRecord sr = proc.services.valueAt(i);
+            for (int conni=sr.connections.size()-1; conni>=0 && !anyClientActivities; conni--) {
+                ArrayList<ConnectionRecord> clist = sr.connections.valueAt(conni);
+                for (int cri=clist.size()-1; cri>=0; cri--) {
+                    ConnectionRecord cr = clist.get(cri);
+                    if (cr.binding.client == null || cr.binding.client == proc) {
+                        // Binding to ourself is not interesting.
+                        continue;
+                    }
+                    if (cr.binding.client.activities.size() > 0) {
+                        anyClientActivities = true;
+                        break;
+                    }
+                }
+            }
+        }
+        if (anyClientActivities != proc.hasClientActivities) {
+            proc.hasClientActivities = anyClientActivities;
+            mAm.updateLruProcessLocked(proc, anyClientActivities, null);
+            return true;
+        }
+        return false;
+    }
+
     int bindServiceLocked(IApplicationThread caller, IBinder token,
             Intent service, String resolvedType,
             IServiceConnection connection, int flags, int userId) {
@@ -659,7 +701,7 @@
         final long origId = Binder.clearCallingIdentity();
 
         try {
-            if (unscheduleServiceRestartLocked(s)) {
+            if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) {
                 if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: "
                         + s);
             }
@@ -698,6 +740,9 @@
             if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
                 b.client.hasAboveClient = true;
             }
+            if (s.app != null) {
+                updateServiceClientActivitiesLocked(s.app, c);
+            }
             clist = mServiceConnections.get(binder);
             if (clist == null) {
                 clist = new ArrayList<ConnectionRecord>();
@@ -714,6 +759,7 @@
 
             if (s.app != null) {
                 // This could have made the service more important.
+                mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities, b.client);
                 mAm.updateOomAdjLocked(s.app);
             }
 
@@ -956,14 +1002,11 @@
                     smap.mServicesByIntent.put(filter, r);
 
                     // Make sure this component isn't in the pending list.
-                    int N = mPendingServices.size();
-                    for (int i=0; i<N; i++) {
+                    for (int i=mPendingServices.size()-1; i>=0; i--) {
                         ServiceRecord pr = mPendingServices.get(i);
                         if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid
                                 && pr.name.equals(name)) {
                             mPendingServices.remove(i);
-                            i--;
-                            N--;
                         }
                     }
                 }
@@ -1055,6 +1098,14 @@
             boolean allowCancel) {
         boolean canceled = false;
 
+        ServiceMap smap = getServiceMap(r.userId);
+        if (smap.mServicesByName.get(r.name) != r) {
+            ServiceRecord cur = smap.mServicesByName.get(r.name);
+            Slog.wtf(TAG, "Attempting to schedule restart of " + r
+                    + " when found in map: " + cur);
+            return false;
+        }
+
         final long now = SystemClock.uptimeMillis();
 
         if ((r.serviceInfo.applicationInfo.flags
@@ -1101,16 +1152,9 @@
                     r.restartCount = 1;
                     r.restartDelay = minDuration;
                 } else {
-                    if ((r.serviceInfo.applicationInfo.flags
-                            &ApplicationInfo.FLAG_PERSISTENT) != 0) {
-                        // Services in peristent processes will restart much more
-                        // quickly, since they are pretty important.  (Think SystemUI).
-                        r.restartDelay += minDuration/2;
-                    } else {
-                        r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR;
-                        if (r.restartDelay < minDuration) {
-                            r.restartDelay = minDuration;
-                        }
+                    r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR;
+                    if (r.restartDelay < minDuration) {
+                        r.restartDelay = minDuration;
                     }
                 }
             }
@@ -1137,7 +1181,7 @@
             } while (repeat);
 
         } else {
-            // Persistent processes are immediately restrted, so there is no
+            // Persistent processes are immediately restarted, so there is no
             // reason to hold of on restarting their services.
             r.totalRestartCount++;
             r.restartCount = 0;
@@ -1148,6 +1192,7 @@
         if (!mRestartingServices.contains(r)) {
             r.createdFromFg = false;
             mRestartingServices.add(r);
+            r.makeRestarting(mAm.mProcessStats.getMemFactorLocked(), now);
         }
 
         r.cancelNotification();
@@ -1170,16 +1215,44 @@
         bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
     }
 
-    private final boolean unscheduleServiceRestartLocked(ServiceRecord r) {
-        if (r.restartDelay == 0) {
+    private final boolean unscheduleServiceRestartLocked(ServiceRecord r, int callingUid,
+            boolean force) {
+        if (!force && r.restartDelay == 0) {
             return false;
         }
-        r.resetRestartCounter();
-        mRestartingServices.remove(r);
+        // Remove from the restarting list; if the service is currently on the
+        // restarting list, or the call is coming from another app, then this
+        // service has become of much more interest so we reset the restart interval.
+        boolean removed = mRestartingServices.remove(r);
+        if (removed || callingUid != r.appInfo.uid) {
+            r.resetRestartCounter();
+        }
+        if (removed) {
+            clearRestartingIfNeededLocked(r);
+        }
         mAm.mHandler.removeCallbacks(r.restarter);
         return true;
     }
 
+    private void clearRestartingIfNeededLocked(ServiceRecord r) {
+        if (r.restartTracker != null) {
+            // If this is the last restarting record with this tracker, then clear
+            // the tracker's restarting state.
+            boolean stillTracking = false;
+            for (int i=mRestartingServices.size()-1; i>=0; i--) {
+                if (mRestartingServices.get(i).restartTracker == r.restartTracker) {
+                    stillTracking = true;
+                    break;
+                }
+            }
+            if (!stillTracking) {
+                r.restartTracker.setRestarting(false, mAm.mProcessStats.getMemFactorLocked(),
+                        SystemClock.uptimeMillis());
+                r.restartTracker = null;
+            }
+        }
+    }
+
     private final String bringUpServiceLocked(ServiceRecord r,
             int intentFlags, boolean execInFg, boolean whileRestarting) {
         //Slog.i(TAG, "Bring up service:");
@@ -1199,11 +1272,13 @@
 
         // We are now bringing the service up, so no longer in the
         // restarting state.
-        mRestartingServices.remove(r);
+        if (mRestartingServices.remove(r)) {
+            clearRestartingIfNeededLocked(r);
+        }
 
         // Make sure this service is no longer considered delayed, we are starting it now.
         if (r.delayed) {
-            if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r);
+            if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r);
             getServiceMap(r.userId).mDelayedStartList.remove(r);
             r.delayed = false;
         }
@@ -1286,7 +1361,7 @@
             // Oh and hey we've already been asked to stop!
             r.delayedStop = false;
             if (r.startRequested) {
-                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r);
+                if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r);
                 stopServiceLocked(r);
             }
         }
@@ -1316,7 +1391,8 @@
 
         app.services.add(r);
         bumpServiceExecutingLocked(r, execInFg, "create");
-        mAm.updateLruProcessLocked(app, true, false);
+        mAm.updateLruProcessLocked(app, false, null);
+        mAm.updateOomAdjLocked();
 
         boolean created = false;
         try {
@@ -1338,6 +1414,7 @@
         } finally {
             if (!created) {
                 app.services.remove(r);
+                r.app = null;
                 scheduleServiceRestartLocked(r, false);
             }
         }
@@ -1355,7 +1432,7 @@
         sendServiceArgsLocked(r, execInFg, true);
 
         if (r.delayed) {
-            if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r);
+            if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r);
             getServiceMap(r.userId).mDelayedStartList.remove(r);
             r.delayed = false;
         }
@@ -1364,7 +1441,7 @@
             // Oh and hey we've already been asked to stop!
             r.delayedStop = false;
             if (r.startRequested) {
-                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (from start): " + r);
+                if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (from start): " + r);
                 stopServiceLocked(r);
             }
         }
@@ -1508,16 +1585,13 @@
         smap.mServicesByName.remove(r.name);
         smap.mServicesByIntent.remove(r.intent);
         r.totalRestartCount = 0;
-        unscheduleServiceRestartLocked(r);
+        unscheduleServiceRestartLocked(r, 0, true);
 
         // Also make sure it is not on the pending list.
-        int N = mPendingServices.size();
-        for (int i=0; i<N; i++) {
+        for (int i=mPendingServices.size()-1; i>=0; i--) {
             if (mPendingServices.get(i) == r) {
                 mPendingServices.remove(i);
                 if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r);
-                i--;
-                N--;
             }
         }
 
@@ -1536,6 +1610,7 @@
             }
             r.app.services.remove(r);
             if (r.app.thread != null) {
+                updateServiceForegroundLocked(r.app, false);
                 try {
                     bumpServiceExecutingLocked(r, false, "destroy");
                     mDestroyingServices.add(r);
@@ -1546,7 +1621,6 @@
                             + r.shortName, e);
                     serviceProcessGoneLocked(r);
                 }
-                updateServiceForegroundLocked(r.app, false);
             } else {
                 if (DEBUG_SERVICE) Slog.v(
                     TAG, "Removed service that has no process: " + r);
@@ -1601,6 +1675,9 @@
             if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
                 b.client.updateHasAboveClientLocked();
             }
+            if (s.app != null) {
+                updateServiceClientActivitiesLocked(s.app, c);
+            }
         }
         clist = mServiceConnections.get(binder);
         if (clist != null) {
@@ -1621,6 +1698,13 @@
                     && b.intent.hasBound) {
                 try {
                     bumpServiceExecutingLocked(s, false, "unbind");
+                    if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0
+                            && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+                        // If this service's process is not already in the cached list,
+                        // then update it in the LRU list here because this may be causing
+                        // it to go down there and we want it to start out near the top.
+                        mAm.updateLruProcessLocked(s.app, false, null);
+                    }
                     mAm.updateOomAdjLocked(s.app);
                     b.intent.hasBound = false;
                     // Assume the client doesn't want to know about a rebind;
@@ -1714,6 +1798,7 @@
             long now = SystemClock.uptimeMillis();
             r.tracker.setExecuting(false, memFactor, now);
             r.tracker.setBound(false, memFactor, now);
+            r.tracker.setStarted(false, memFactor, now);
         }
         serviceDoneExecutingLocked(r, true, true);
     }
@@ -1761,6 +1846,12 @@
                     r.tracker = null;
                 }
             }
+            if (finishing) {
+                if (r.app != null && !r.app.persistent) {
+                    r.app.services.remove(r);
+                }
+                r.app = null;
+            }
         }
     }
 
@@ -1839,6 +1930,9 @@
                 Slog.i(TAG, "  Force stopping service " + service);
                 if (service.app != null) {
                     service.app.removed = true;
+                    if (!service.app.persistent) {
+                        service.app.services.remove(service);
+                    }
                 }
                 service.app = null;
                 service.isolatedProc = null;
@@ -1905,8 +1999,7 @@
         }
     }
 
-    final void killServicesLocked(ProcessRecord app,
-            boolean allowRestart) {
+    final void killServicesLocked(ProcessRecord app, boolean allowRestart) {
         // Report disconnected services.
         if (false) {
             // XXX we are letting the client link to the service for
@@ -1935,20 +2028,15 @@
             }
         }
 
-        // Clean up any connections this application has to other services.
-        for (int i=app.connections.size()-1; i>=0; i--) {
-            ConnectionRecord r = app.connections.valueAt(i);
-            removeConnectionLocked(r, app, null);
-        }
-        app.connections.clear();
-
+        // First clear app state from services.
         for (int i=app.services.size()-1; i>=0; i--) {
-            // Any services running in the application need to be placed
-            // back in the pending list.
             ServiceRecord sr = app.services.valueAt(i);
             synchronized (sr.stats.getBatteryStats()) {
                 sr.stats.stopLaunchedLocked();
             }
+            if (sr.app != app && sr.app != null && !sr.app.persistent) {
+                sr.app.services.remove(sr);
+            }
             sr.app = null;
             sr.isolatedProc = null;
             sr.executeNesting = 0;
@@ -1965,8 +2053,39 @@
                 b.binder = null;
                 b.requested = b.received = b.hasBound = false;
             }
+        }
 
-            if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
+        // Clean up any connections this application has to other services.
+        for (int i=app.connections.size()-1; i>=0; i--) {
+            ConnectionRecord r = app.connections.valueAt(i);
+            removeConnectionLocked(r, app, null);
+        }
+        app.connections.clear();
+
+        ServiceMap smap = getServiceMap(app.userId);
+
+        // Now do remaining service cleanup.
+        for (int i=app.services.size()-1; i>=0; i--) {
+            ServiceRecord sr = app.services.valueAt(i);
+
+            // Unless the process is persistent, this process record is going away,
+            // so make sure the service is cleaned out of it.
+            if (!app.persistent) {
+                app.services.removeAt(i);
+            }
+
+            // Sanity check: if the service listed for the app is not one
+            // we actually are maintaining, just let it drop.
+            if (smap.mServicesByName.get(sr.name) != sr) {
+                ServiceRecord cur = smap.mServicesByName.get(sr.name);
+                Slog.wtf(TAG, "Service " + sr + " in process " + app
+                        + " not same as in map: " + cur);
+                continue;
+            }
+
+            // Any services running in the application may need to be placed
+            // back in the pending list.
+            if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
                     &ApplicationInfo.FLAG_PERSISTENT) == 0) {
                 Slog.w(TAG, "Service crashed " + sr.crashCount
                         + " times, stopping: " + sr);
@@ -1999,6 +2118,23 @@
 
         if (!allowRestart) {
             app.services.clear();
+
+            // Make sure there are no more restarting services for this process.
+            for (int i=mRestartingServices.size()-1; i>=0; i--) {
+                ServiceRecord r = mRestartingServices.get(i);
+                if (r.processName.equals(app.processName) &&
+                        r.serviceInfo.applicationInfo.uid == app.info.uid) {
+                    mRestartingServices.remove(i);
+                    clearRestartingIfNeededLocked(r);
+                }
+            }
+            for (int i=mPendingServices.size()-1; i>=0; i--) {
+                ServiceRecord r = mPendingServices.get(i);
+                if (r.processName.equals(app.processName) &&
+                        r.serviceInfo.applicationInfo.uid == app.info.uid) {
+                    mPendingServices.remove(i);
+                }
+            }
         }
 
         // Make sure we have no more records on the stopping list.
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index d75fe5e..e2050fc 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -34,7 +34,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.ProcessStats;
-import com.android.internal.app.ResolverActivity;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.os.ProcessCpuTracker;
@@ -218,6 +217,7 @@
     static final boolean DEBUG_IMMERSIVE = localLOGV || false;
     static final boolean DEBUG_MU = localLOGV || false;
     static final boolean DEBUG_OOM_ADJ = localLOGV || false;
+    static final boolean DEBUG_LRU = localLOGV || false;
     static final boolean DEBUG_PAUSE = localLOGV || false;
     static final boolean DEBUG_POWER = localLOGV || false;
     static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
@@ -457,6 +457,23 @@
     final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<Long>();
 
     /**
+     * Information about a process that is currently marked as bad.
+     */
+    static final class BadProcessInfo {
+        BadProcessInfo(long time, String shortMsg, String longMsg, String stack) {
+            this.time = time;
+            this.shortMsg = shortMsg;
+            this.longMsg = longMsg;
+            this.stack = stack;
+        }
+
+        final long time;
+        final String shortMsg;
+        final String longMsg;
+        final String stack;
+    }
+
+    /**
      * Set of applications that we consider to be bad, and will reject
      * incoming broadcasts from (which the user has no control over).
      * Processes are added to this set when they have crashed twice within
@@ -464,7 +481,7 @@
      * later restarted (hopefully due to some user action).  The value is the
      * time it was added to the list.
      */
-    final ProcessMap<Long> mBadProcesses = new ProcessMap<Long>();
+    final ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<BadProcessInfo>();
 
     /**
      * All of the processes we currently have running organized by pid.
@@ -1311,7 +1328,7 @@
                     String pkg = bundle.getString("pkg");
                     String reason = bundle.getString("reason");
                     forceStopPackageLocked(pkg, appid, restart, false, true, false,
-                            UserHandle.USER_ALL, reason);
+                            false, UserHandle.USER_ALL, reason);
                 }
             } break;
             case FINALIZE_PENDING_INTENT_MSG: {
@@ -1743,7 +1760,8 @@
                 synchronized (mSelf.mPidsSelfLocked) {
                     mSelf.mPidsSelfLocked.put(app.pid, app);
                 }
-                mSelf.updateLruProcessLocked(app, true, false);
+                mSelf.updateLruProcessLocked(app, false, null);
+                mSelf.updateOomAdjLocked();
             }
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException(
@@ -2131,7 +2149,8 @@
                                 totalUTime += otherUTime;
                                 totalSTime += otherSTime;
                                 if (pr != null) {
-                                    BatteryStatsImpl.Uid.Proc ps = pr.batteryStats;
+                                    BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked(
+                                            st.name, st.pid);
                                     ps.addCpuTimeLocked(st.rel_utime-otherUTime,
                                             st.rel_stime-otherSTime);
                                     ps.addSpeedStepTimes(cpuSpeedTimes);
@@ -2269,7 +2288,7 @@
 
         int lrui = mLruProcesses.lastIndexOf(app);
         if (lrui < 0) {
-            Log.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
+            Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
                     + what + " " + obj + " from " + srcApp);
             return index;
         }
@@ -2289,6 +2308,8 @@
         if (index > 0) {
             index--;
         }
+        if (DEBUG_LRU) Slog.d(TAG, "Moving dep from " + lrui + " to " + index
+                + " in LRU list: " + app);
         mLruProcesses.add(index, app);
         return index;
     }
@@ -2306,8 +2327,9 @@
         }
     }
 
-    final void updateLruProcessLocked(ProcessRecord app, boolean oomAdj, boolean activityChange) {
-        final boolean hasActivity = app.activities.size() > 0;
+    final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
+            ProcessRecord client) {
+        final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities;
         final boolean hasService = false; // not impl yet. app.services.size() > 0;
         if (!activityChange && hasActivity) {
             // The process has activties, so we are only going to allow activity-based
@@ -2321,8 +2343,65 @@
         final long now = SystemClock.uptimeMillis();
         app.lastActivityTime = now;
 
+        // First a quick reject: if the app is already at the position we will
+        // put it, then there is nothing to do.
+        if (hasActivity) {
+            final int N = mLruProcesses.size();
+            if (N > 0 && mLruProcesses.get(N-1) == app) {
+                if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top activity: " + app);
+                return;
+            }
+        } else {
+            if (mLruProcessServiceStart > 0
+                    && mLruProcesses.get(mLruProcessServiceStart-1) == app) {
+                if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top other: " + app);
+                return;
+            }
+        }
+
         int lrui = mLruProcesses.lastIndexOf(app);
 
+        if (app.persistent && lrui >= 0) {
+            // We don't care about the position of persistent processes, as long as
+            // they are in the list.
+            if (DEBUG_LRU) Slog.d(TAG, "Not moving, persistent: " + app);
+            return;
+        }
+
+        /* In progress: compute new position first, so we can avoid doing work
+           if the process is not actually going to move.  Not yet working.
+        int addIndex;
+        int nextIndex;
+        boolean inActivity = false, inService = false;
+        if (hasActivity) {
+            // Process has activities, put it at the very tipsy-top.
+            addIndex = mLruProcesses.size();
+            nextIndex = mLruProcessServiceStart;
+            inActivity = true;
+        } else if (hasService) {
+            // Process has services, put it at the top of the service list.
+            addIndex = mLruProcessActivityStart;
+            nextIndex = mLruProcessServiceStart;
+            inActivity = true;
+            inService = true;
+        } else  {
+            // Process not otherwise of interest, it goes to the top of the non-service area.
+            addIndex = mLruProcessServiceStart;
+            if (client != null) {
+                int clientIndex = mLruProcesses.lastIndexOf(client);
+                if (clientIndex < 0) Slog.d(TAG, "Unknown client " + client + " when updating "
+                        + app);
+                if (clientIndex >= 0 && addIndex > clientIndex) {
+                    addIndex = clientIndex;
+                }
+            }
+            nextIndex = addIndex > 0 ? addIndex-1 : addIndex;
+        }
+
+        Slog.d(TAG, "Update LRU at " + lrui + " to " + addIndex + " (act="
+                + mLruProcessActivityStart + "): " + app);
+        */
+
         if (lrui >= 0) {
             if (lrui < mLruProcessActivityStart) {
                 mLruProcessActivityStart--;
@@ -2330,23 +2409,91 @@
             if (lrui < mLruProcessServiceStart) {
                 mLruProcessServiceStart--;
             }
+            /*
+            if (addIndex > lrui) {
+                addIndex--;
+            }
+            if (nextIndex > lrui) {
+                nextIndex--;
+            }
+            */
             mLruProcesses.remove(lrui);
         }
 
+        /*
+        mLruProcesses.add(addIndex, app);
+        if (inActivity) {
+            mLruProcessActivityStart++;
+        }
+        if (inService) {
+            mLruProcessActivityStart++;
+        }
+        */
+
         int nextIndex;
         if (hasActivity) {
-            // Process has activities, put it at the very tipsy-top.
-            mLruProcesses.add(app);
-            nextIndex = mLruProcessActivityStart;
+            final int N = mLruProcesses.size();
+            if (app.activities.size() == 0 && mLruProcessActivityStart < (N-1)) {
+                // Process doesn't have activities, but has clients with
+                // activities...  move it up, but one below the top (the top
+                // should always have a real activity).
+                if (DEBUG_LRU) Slog.d(TAG, "Adding to second-top of LRU activity list: " + app);
+                mLruProcesses.add(N-1, app);
+                // To keep it from spamming the LRU list (by making a bunch of clients),
+                // we will push down any other entries owned by the app.
+                final int uid = app.info.uid;
+                for (int i=N-2; i>mLruProcessActivityStart; i--) {
+                    ProcessRecord subProc = mLruProcesses.get(i);
+                    if (subProc.info.uid == uid) {
+                        // We want to push this one down the list.  If the process after
+                        // it is for the same uid, however, don't do so, because we don't
+                        // want them internally to be re-ordered.
+                        if (mLruProcesses.get(i-1).info.uid != uid) {
+                            if (DEBUG_LRU) Slog.d(TAG, "Pushing uid " + uid + " swapping at " + i
+                                    + ": " + mLruProcesses.get(i) + " : " + mLruProcesses.get(i-1));
+                            ProcessRecord tmp = mLruProcesses.get(i);
+                            mLruProcesses.set(i, mLruProcesses.get(i-1));
+                            mLruProcesses.set(i-1, tmp);
+                            i--;
+                        }
+                    } else {
+                        // A gap, we can stop here.
+                        break;
+                    }
+                }
+            } else {
+                // Process has activities, put it at the very tipsy-top.
+                if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU activity list: " + app);
+                mLruProcesses.add(app);
+            }
+            nextIndex = mLruProcessServiceStart;
         } else if (hasService) {
             // Process has services, put it at the top of the service list.
+            if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU service list: " + app);
             mLruProcesses.add(mLruProcessActivityStart, app);
             nextIndex = mLruProcessServiceStart;
             mLruProcessActivityStart++;
         } else  {
             // Process not otherwise of interest, it goes to the top of the non-service area.
-            mLruProcesses.add(mLruProcessServiceStart, app);
-            nextIndex = mLruProcessServiceStart-1;
+            int index = mLruProcessServiceStart;
+            if (client != null) {
+                // If there is a client, don't allow the process to be moved up higher
+                // in the list than that client.
+                int clientIndex = mLruProcesses.lastIndexOf(client);
+                if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG, "Unknown client " + client
+                        + " when updating " + app);
+                if (clientIndex <= lrui) {
+                    // Don't allow the client index restriction to push it down farther in the
+                    // list than it already is.
+                    clientIndex = lrui;
+                }
+                if (clientIndex >= 0 && index > clientIndex) {
+                    index = clientIndex;
+                }
+            }
+            if (DEBUG_LRU) Slog.d(TAG, "Adding at " + index + " of LRU list: " + app);
+            mLruProcesses.add(index, app);
+            nextIndex = index-1;
             mLruProcessActivityStart++;
             mLruProcessServiceStart++;
         }
@@ -2357,23 +2504,19 @@
             ConnectionRecord cr = app.connections.valueAt(j);
             if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
                     && cr.binding.service.app != null
-                    && cr.binding.service.app.lruSeq != mLruSeq) {
+                    && cr.binding.service.app.lruSeq != mLruSeq
+                    && !cr.binding.service.app.persistent) {
                 nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex,
                         "service connection", cr, app);
             }
         }
         for (int j=app.conProviders.size()-1; j>=0; j--) {
             ContentProviderRecord cpr = app.conProviders.get(j).provider;
-            if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) {
+            if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.persistent) {
                 nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex,
                         "provider reference", cpr, app);
             }
         }
-
-        //Slog.i(TAG, "Putting proc to front: " + app.processName);
-        if (oomAdj) {
-            updateOomAdjLocked();
-        }
     }
 
     final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) {
@@ -2627,10 +2770,10 @@
                     app.processName, uid, uid, gids, debugFlags, mountExternal,
                     app.info.targetSdkVersion, app.info.seinfo, null);
 
-            BatteryStatsImpl bs = app.batteryStats.getBatteryStats();
+            BatteryStatsImpl bs = mBatteryStatsService.getActiveStatistics();
             synchronized (bs) {
                 if (bs.isOnBattery()) {
-                    app.batteryStats.incStartsLocked();
+                    bs.getProcessStatsLocked(app.uid, app.processName).incStartsLocked();
                 }
             }
 
@@ -4351,7 +4494,7 @@
 
     private void forceStopPackageLocked(final String packageName, int uid, String reason) {
         forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false,
-                false, true, false, UserHandle.getUserId(uid), reason);
+                false, true, false, false, UserHandle.getUserId(uid), reason);
         Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
                 Uri.fromParts("package", packageName, null));
         if (!mProcessesReady) {
@@ -4367,7 +4510,7 @@
     }
 
     private void forceStopUserLocked(int userId, String reason) {
-        forceStopPackageLocked(null, -1, false, false, true, false, userId, reason);
+        forceStopPackageLocked(null, -1, false, false, true, false, false, userId, reason);
         Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                 | Intent.FLAG_RECEIVER_FOREGROUND);
@@ -4452,7 +4595,7 @@
 
     private final boolean forceStopPackageLocked(String name, int appId,
             boolean callerWillRestart, boolean purgeCache, boolean doit,
-            boolean evenPersistent, int userId, String reason) {
+            boolean evenPersistent, boolean uninstalling, int userId, String reason) {
         int i;
         int N;
 
@@ -4544,7 +4687,7 @@
         // Remove transient permissions granted from/to this package/user
         removeUriPermissionsForPackageLocked(name, userId, false);
 
-        if (name == null) {
+        if (name == null || uninstalling) {
             // Remove pending intents.  For now we only do this when force
             // stopping users, because we have some problems when doing this
             // for packages -- app widgets are not currently cleaned up for
@@ -4831,7 +4974,7 @@
                     isRestrictedBackupMode || !normalMode, app.persistent,
                     new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
                     mCoreSettingsObserver.getCoreSettingsLocked());
-            updateLruProcessLocked(app, false, false);
+            updateLruProcessLocked(app, false, null);
             app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
         } catch (Exception e) {
             // todo: Yikes!  What should we do?  For now we will try to
@@ -4989,7 +5132,7 @@
                 if (pkgs != null) {
                     for (String pkg : pkgs) {
                         synchronized (ActivityManagerService.this) {
-                            if (forceStopPackageLocked(pkg, -1, false, false, false, false, 0,
+                            if (forceStopPackageLocked(pkg, -1, false, false, false, false, false, 0,
                                     "finished booting")) {
                                 setResultCode(Activity.RESULT_OK);
                                 return;
@@ -7419,7 +7562,7 @@
                         // make sure to count it as being accessed and thus
                         // back up on the LRU list.  This is good because
                         // content providers are often expensive to start.
-                        updateLruProcessLocked(cpr.proc, false, false);
+                        updateLruProcessLocked(cpr.proc, false, null);
                     }
                 }
 
@@ -8008,10 +8151,7 @@
                 }
             }
         }
-        synchronized (stats) {
-            ps = stats.getProcessStatsLocked(info.uid, proc);
-        }
-        return new ProcessRecord(ps, info, proc, uid);
+        return new ProcessRecord(stats, info, proc, uid);
     }
 
     final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) {
@@ -8028,7 +8168,8 @@
             if (isolated) {
                 mIsolatedProcesses.put(app.uid, app);
             }
-            updateLruProcessLocked(app, true, false);
+            updateLruProcessLocked(app, false, null);
+            updateOomAdjLocked();
         }
 
         // This package really, really can not be stopped.
@@ -8304,7 +8445,7 @@
                 mDebugTransient = !persistent;
                 if (packageName != null) {
                     forceStopPackageLocked(packageName, -1, false, false, true, true,
-                            UserHandle.USER_ALL, "set debug app");
+                            false, UserHandle.USER_ALL, "set debug app");
                 }
             }
         } finally {
@@ -9084,8 +9225,13 @@
                         ActivityInfo ai = ris.get(i).activityInfo;
                         ComponentName comp = new ComponentName(ai.packageName, ai.name);
                         if (lastDoneReceivers.contains(comp)) {
+                            // We already did the pre boot receiver for this app with the current
+                            // platform version, so don't do it again...
                             ris.remove(i);
                             i--;
+                            // ...however, do keep it as one that has been done, so we don't
+                            // forget about it when rewriting the file of last done receivers.
+                            doneReceivers.add(comp);
                         }
                     }
 
@@ -9292,7 +9438,7 @@
                 ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
         startAppProblemLocked(app);
         app.stopFreezingAllLocked();
-        return handleAppCrashLocked(app);
+        return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace);
     }
 
     private void makeAppNotRespondingLocked(ProcessRecord app,
@@ -9347,13 +9493,14 @@
                 app.waitDialog = null;
             }
             if (app.pid > 0 && app.pid != MY_PID) {
-                handleAppCrashLocked(app);
+                handleAppCrashLocked(app, null, null, null);
                 killUnneededProcessLocked(app, "user request after error");
             }
         }
     }
 
-    private boolean handleAppCrashLocked(ProcessRecord app) {
+    private boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg,
+            String stackTrace) {
         if (mHeadless) {
             Log.e(TAG, "handleAppCrashLocked: " + app.processName);
             return false;
@@ -9383,7 +9530,8 @@
                 if (!app.isolated) {
                     // XXX We don't have a way to mark isolated processes
                     // as bad, since they don't have a peristent identity.
-                    mBadProcesses.put(app.info.processName, app.uid, now);
+                    mBadProcesses.put(app.info.processName, app.uid,
+                            new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
                     mProcessCrashTimes.remove(app.info.processName, app.uid);
                 }
                 app.bad = true;
@@ -10101,7 +10249,7 @@
         if (app.persistent) {
             outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT;
         }
-        if (app.hasActivities) {
+        if (app.activities.size() > 0) {
             outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES;
         }
         outInfo.lastTrimLevel = app.trimMemoryLevel;
@@ -10645,11 +10793,11 @@
 
         if (mBadProcesses.getMap().size() > 0) {
             boolean printed = false;
-            final ArrayMap<String, SparseArray<Long>> pmap = mBadProcesses.getMap();
+            final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap();
             final int NP = pmap.size();
             for (int ip=0; ip<NP; ip++) {
                 String pname = pmap.keyAt(ip);
-                SparseArray<Long> uids = pmap.valueAt(ip);
+                SparseArray<BadProcessInfo> uids = pmap.valueAt(ip);
                 final int N = uids.size();
                 for (int i=0; i<N; i++) {
                     int puid = uids.keyAt(i);
@@ -10664,10 +10812,33 @@
                         pw.println("  Bad processes:");
                         printedAnything = true;
                     }
+                    BadProcessInfo info = uids.valueAt(i);
                     pw.print("    Bad process "); pw.print(pname);
                             pw.print(" uid "); pw.print(puid);
-                            pw.print(": crashed at time ");
-                            pw.println(uids.valueAt(i));
+                            pw.print(": crashed at time "); pw.println(info.time);
+                    if (info.shortMsg != null) {
+                        pw.print("      Short msg: "); pw.println(info.shortMsg);
+                    }
+                    if (info.longMsg != null) {
+                        pw.print("      Long msg: "); pw.println(info.longMsg);
+                    }
+                    if (info.stack != null) {
+                        pw.println("      Stack:");
+                        int lastPos = 0;
+                        for (int pos=0; pos<info.stack.length(); pos++) {
+                            if (info.stack.charAt(pos) == '\n') {
+                                pw.print("        ");
+                                pw.write(info.stack, lastPos, pos-lastPos);
+                                pw.println();
+                                lastPos = pos+1;
+                            }
+                        }
+                        if (lastPos < info.stack.length()) {
+                            pw.print("        ");
+                            pw.write(info.stack, lastPos, info.stack.length()-lastPos);
+                            pw.println();
+                        }
+                    }
                 }
             }
         }
@@ -11733,6 +11904,7 @@
         boolean dumpDalvik = false;
         boolean oomOnly = false;
         boolean isCompact = false;
+        boolean localOnly = false;
         
         int opti = 0;
         while (opti < args.length) {
@@ -11751,12 +11923,15 @@
                 isCompact = true;
             } else if ("--oom".equals(opt)) {
                 oomOnly = true;
+            } else if ("--local".equals(opt)) {
+                localOnly = true;
             } else if ("-h".equals(opt)) {
                 pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]");
                 pw.println("  -a: include all available information for each process.");
                 pw.println("  -d: include dalvik details when dumping process details.");
                 pw.println("  -c: dump in a compact machine-parseable representation.");
                 pw.println("  --oom: only show processes organized by oom adj.");
+                pw.println("  --local: only collect details locally, don't call process.");
                 pw.println("If [process] is specified it can be the name or ");
                 pw.println("pid of a specific process to dump.");
                 return;
@@ -11857,7 +12032,7 @@
                 thread = r.thread;
                 pid = r.pid;
                 oomAdj = r.getSetAdjWithServices();
-                hasActivities = r.hasActivities;
+                hasActivities = r.activities.size() > 0;
             }
             if (thread != null) {
                 if (!isCheckinRequest && dumpDetails) {
@@ -11873,14 +12048,22 @@
                     mi.dalvikPrivateDirty = (int)tmpLong[0];
                 }
                 if (dumpDetails) {
-                    try {
-                        pw.flush();
-                        thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails,
-                                dumpDalvik, innerArgs);
-                    } catch (RemoteException e) {
-                        if (!isCheckinRequest) {
-                            pw.println("Got RemoteException!");
+                    if (localOnly) {
+                        ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
+                                dumpDalvik, pid, r.processName, 0, 0, 0, 0, 0, 0);
+                        if (isCheckinRequest) {
+                            pw.println();
+                        }
+                    } else {
+                        try {
                             pw.flush();
+                            thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails,
+                                    dumpDalvik, innerArgs);
+                        } catch (RemoteException e) {
+                            if (!isCheckinRequest) {
+                                pw.println("Got RemoteException!");
+                                pw.flush();
+                            }
                         }
                     }
                 }
@@ -13195,7 +13378,7 @@
                         String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                         if (list != null && (list.length > 0)) {
                             for (String pkg : list) {
-                                forceStopPackageLocked(pkg, -1, false, true, true, false, userId,
+                                forceStopPackageLocked(pkg, -1, false, true, true, false, false, userId,
                                         "storage unmount");
                             }
                             sendPackageBroadcastLocked(
@@ -13207,10 +13390,13 @@
                         if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
                             boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(
                                     intent.getAction());
+                            boolean fullUninstall = removed &&
+                                    !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
                             if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) {
                                 forceStopPackageLocked(ssp, UserHandle.getAppId(
                                         intent.getIntExtra(Intent.EXTRA_UID, -1)), false, true, true,
-                                        false, userId, removed ? "pkg removed" : "pkg changed");
+                                        false, fullUninstall, userId,
+                                        removed ? "pkg removed" : "pkg changed");
                             }
                             if (removed) {
                                 sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED,
@@ -13688,7 +13874,7 @@
 
             final long origId = Binder.clearCallingIdentity();
             // Instrumentation can kill and relaunch even persistent processes
-            forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId,
+            forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId,
                     "start instr");
             ProcessRecord app = addAppLocked(ai, false);
             app.instrumentationClass = className;
@@ -13756,7 +13942,7 @@
         app.instrumentationProfileFile = null;
         app.instrumentationArguments = null;
 
-        forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId,
+        forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false, app.userId,
                 "finished inst");
     }
 
@@ -14079,7 +14265,6 @@
         app.adjTarget = null;
         app.empty = false;
         app.cached = false;
-        app.hasClientActivities = false;
 
         final int activitiesSize = app.activities.size();
 
@@ -14089,7 +14274,6 @@
             app.adjType = "fixed";
             app.adjSeq = mAdjSeq;
             app.curRawAdj = app.maxAdj;
-            app.hasActivities = false;
             app.foregroundActivities = false;
             app.keeping = true;
             app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
@@ -14101,16 +14285,12 @@
             app.systemNoUi = true;
             if (app == TOP_APP) {
                 app.systemNoUi = false;
-                app.hasActivities = true;
             } else if (activitiesSize > 0) {
                 for (int j = 0; j < activitiesSize; j++) {
                     final ActivityRecord r = app.activities.get(j);
                     if (r.visible) {
                         app.systemNoUi = false;
                     }
-                    if (r.app == app) {
-                        app.hasActivities = true;
-                    }
                 }
             }
             if (!app.systemNoUi) {
@@ -14121,7 +14301,6 @@
 
         app.keeping = false;
         app.systemNoUi = false;
-        app.hasActivities = false;
 
         // Determine the importance of the process, starting with most
         // important to least, and assign an appropriate OOM adjustment.
@@ -14138,7 +14317,6 @@
             app.adjType = "top-activity";
             foregroundActivities = true;
             interesting = true;
-            app.hasActivities = true;
             procState = ActivityManager.PROCESS_STATE_TOP;
         } else if (app.instrumentationClass != null) {
             // Don't want to kill running instrumentation.
@@ -14187,7 +14365,6 @@
                             + app + "?!?");
                     continue;
                 }
-                app.hasActivities = true;
                 if (r.visible) {
                     // App has a visible activity; only upgrade adjustment.
                     if (adj > ProcessList.VISIBLE_APP_ADJ) {
@@ -14436,27 +14613,6 @@
                                     clientAdj = adj;
                                 }
                             }
-                        } else if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) {
-                            if ((cr.flags&Context.BIND_NOT_VISIBLE) == 0) {
-                                // If this connection is keeping the service
-                                // created, then we want to try to better follow
-                                // its memory management semantics for activities.
-                                // That is, if it is sitting in the background
-                                // LRU list as a cached process (with activities),
-                                // we don't want the service it is connected to
-                                // to go into the empty LRU and quickly get killed,
-                                // because all we'll do is just end up restarting
-                                // the service.
-                                if (client.hasActivities) {
-                                    if (procState >
-                                            ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT) {
-                                        procState =
-                                                ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
-                                        app.adjType = "cch-client-act";
-                                    }
-                                    app.hasClientActivities = true;
-                                }
-                            }
                         }
                         if (adj > clientAdj) {
                             // If this process has recently shown UI, and
@@ -14674,6 +14830,12 @@
             }
         }
 
+        if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY && app.hasClientActivities) {
+            // This is a cached process, but with client activities.  Mark it so.
+            procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+            app.adjType = "cch-client-act";
+        }
+
         if (adj == ProcessList.SERVICE_ADJ) {
             if (doingAll) {
                 app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
@@ -15302,7 +15464,6 @@
         // application processes based on their current state.
         int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
         int nextCachedAdj = curCachedAdj+1;
-        int curClientCachedAdj = curCachedAdj+1;
         int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
         int nextEmptyAdj = curEmptyAdj+2;
         for (int i=N-1; i>=0; i--) {
@@ -15317,11 +15478,15 @@
                 if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
                     switch (app.curProcState) {
                         case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
+                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                             // This process is a cached process holding activities...
                             // assign it the next cached value for that type, and then
                             // step that cached level.
                             app.curRawAdj = curCachedAdj;
                             app.curAdj = app.modifyRawOomAdj(curCachedAdj);
+                            if (DEBUG_LRU && false) Slog.d(TAG, "Assigning activity LRU #" + i
+                                    + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
+                                    + ")");
                             if (curCachedAdj != nextCachedAdj) {
                                 stepCached++;
                                 if (stepCached >= cachedFactor) {
@@ -15331,25 +15496,9 @@
                                     if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
                                         nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
                                     }
-                                    if (curClientCachedAdj <= curCachedAdj) {
-                                        curClientCachedAdj = curCachedAdj + 1;
-                                        if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
-                                            curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
-                                        }
-                                    }
                                 }
                             }
                             break;
-                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
-                            // Special case for cached client processes...  just step
-                            // down from after regular cached processes.
-                            app.curRawAdj = curClientCachedAdj;
-                            app.curAdj = app.modifyRawOomAdj(curClientCachedAdj);
-                            curClientCachedAdj++;
-                            if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
-                                curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
-                            }
-                            break;
                         default:
                             // For everything else, assign next empty cached process
                             // level and bump that up.  Note that this means that
@@ -15358,6 +15507,9 @@
                             // state is still as a service), which is what we want.
                             app.curRawAdj = curEmptyAdj;
                             app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
+                            if (DEBUG_LRU && false) Slog.d(TAG, "Assigning empty LRU #" + i
+                                    + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
+                                    + ")");
                             if (curEmptyAdj != nextEmptyAdj) {
                                 stepEmpty++;
                                 if (stepEmpty >= emptyFactor) {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 44ff3bc..596c84d 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -36,7 +36,6 @@
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES;
 import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
 
-import android.os.Trace;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.util.Objects;
 import com.android.server.Watchdog;
@@ -61,16 +60,17 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.util.EventLog;
 import android.util.Slog;
@@ -566,7 +566,7 @@
 
         // Move userId's tasks to the top.
         int index = mTaskHistory.size();
-        for (int i = 0; i < index; ++i) {
+        for (int i = 0; i < index; ) {
             TaskRecord task = mTaskHistory.get(i);
             if (task.userId == userId) {
                 if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() +
@@ -574,6 +574,9 @@
                 mTaskHistory.remove(i);
                 mTaskHistory.add(task);
                 --index;
+                // Use same value for i.
+            } else {
+                ++i;
             }
         }
         if (VALIDATE_TOKENS) {
@@ -997,8 +1000,8 @@
                 if (r.isHomeActivity()) {
                     return true;
                 }
-                if (!r.finishing && r.visible && r.fullscreen) {
-                    // Passed activity is over a visible fullscreen activity.
+                if (!r.finishing && r.fullscreen) {
+                    // Passed activity is over a fullscreen activity.
                     return false;
                 }
             }
@@ -1091,6 +1094,7 @@
                             if (!r.visible) {
                                 if (DEBUG_VISBILITY) Slog.v(
                                         TAG, "Starting and making visible: " + r);
+                                r.visible = true;
                                 mWindowManager.setAppVisibility(r.appToken, true);
                             }
                             if (r != starting) {
@@ -1141,7 +1145,7 @@
                     } else if (isActivityOverHome(r)) {
                         if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
                         showHomeBehindStack = true;
-                        behindFullscreen = true;
+                        behindFullscreen = !isHomeStack();
                     }
                 } else {
                     if (DEBUG_VISBILITY) Slog.v(
@@ -1284,17 +1288,7 @@
         if (prevTask != null && prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) {
             if (DEBUG_STACK)  mStackSupervisor.validateTopActivitiesLocked();
             if (prevTask == nextTask) {
-                ArrayList<ActivityRecord> activities = prevTask.mActivities;
-                final int numActivities = activities.size();
-                for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
-                    final ActivityRecord r = activities.get(activityNdx);
-                    // r is usually the same as next, but what if two activities were launched
-                    // before prev finished?
-                    if (!r.finishing) {
-                        r.frontOfTask = true;
-                        break;
-                    }
-                }
+                prevTask.setFrontOfTask();
             } else if (prevTask != topTask()) {
                 // This task is going away but it was supposed to return to the home task.
                 // Now the task above it has to return to the home task instead.
@@ -1398,7 +1392,7 @@
             if (next.app != null && next.app.thread != null) {
                 // No reason to do full oom adj update here; we'll let that
                 // happen whenever it needs to later.
-                mService.updateLruProcessLocked(next.app, false, true);
+                mService.updateLruProcessLocked(next.app, true, null);
             }
             if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
             return true;
@@ -1526,8 +1520,9 @@
             mResumedActivity = next;
             next.task.touchActiveTime();
             mService.addRecentTaskLocked(next.task);
-            mService.updateLruProcessLocked(next.app, true, true);
+            mService.updateLruProcessLocked(next.app, true, null);
             updateLRUListLocked(next);
+            mService.updateOomAdjLocked();
 
             // Have the window manager re-evaluate the orientation of
             // the screen based on the new activity order.
@@ -1713,7 +1708,7 @@
                         mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
                                 r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
                                 (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
-                                r.userId);
+                                r.userId, r.info.configChanges);
                         if (VALIDATE_TOKENS) {
                             validateAppTokensLocked();
                         }
@@ -1745,9 +1740,9 @@
         if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to stack to task " + task,
                 new RuntimeException("here").fillInStackTrace());
         task.addActivityToTop(r);
+        task.setFrontOfTask();
 
         r.putInHistory();
-        r.frontOfTask = newTask;
         if (!isHomeStack() || numActivities() > 0) {
             // We want to show the starting preview window if we are
             // switching to a new task, or the next activity's process is
@@ -1774,7 +1769,8 @@
             r.updateOptionsLocked(options);
             mWindowManager.addAppToken(task.mActivities.indexOf(r),
                     r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
-                    (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId);
+                    (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
+                    r.info.configChanges);
             boolean doShow = true;
             if (newTask) {
                 // Even though this activity is starting fresh, we still need
@@ -1817,7 +1813,8 @@
             // because there is nothing for it to animate on top of.
             mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
                     r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
-                    (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId);
+                    (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
+                    r.info.configChanges);
             ActivityOptions.abort(options);
         }
         if (VALIDATE_TOKENS) {
@@ -1907,26 +1904,38 @@
                 // bottom of the activity stack.  This also keeps it
                 // correctly ordered with any activities we previously
                 // moved.
+                final ThumbnailHolder newThumbHolder;
+                final TaskRecord targetTask;
                 final ActivityRecord bottom =
                         !mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ?
-                        mTaskHistory.get(0).mActivities.get(0) : null;
+                                mTaskHistory.get(0).mActivities.get(0) : null;
                 if (bottom != null && target.taskAffinity != null
                         && target.taskAffinity.equals(bottom.task.affinity)) {
                     // If the activity currently at the bottom has the
                     // same task affinity as the one we are moving,
                     // then merge it into the same task.
-                    target.setTask(bottom.task, bottom.thumbHolder, false);
+                    targetTask = bottom.task;
+                    newThumbHolder = bottom.thumbHolder == null ? targetTask : bottom.thumbHolder;
                     if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
                             + " out to bottom task " + bottom.task);
                 } else {
-                    target.setTask(createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
-                            null, false), null, false);
-                    target.task.affinityIntent = target.intent;
+                    targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
+                            null, false);
+                    newThumbHolder = targetTask;
+                    targetTask.affinityIntent = target.intent;
                     if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
                             + " out to new task " + target.task);
                 }
 
-                final TaskRecord targetTask = target.task;
+                if (clearWhenTaskReset) {
+                    // This is the start of a new sub-task.
+                    if (target.thumbHolder == null) {
+                        target.thumbHolder = new ThumbnailHolder();
+                    }
+                } else {
+                    target.thumbHolder = newThumbHolder;
+                }
+
                 final int targetTaskId = targetTask.taskId;
                 mWindowManager.setAppGroupId(target.appToken, targetTaskId);
 
@@ -1947,8 +1956,8 @@
                         }
                     }
                     if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing activity " + p + " from task="
-                            + task + " adding to task=" + targetTask,
-                            new RuntimeException("here").fillInStackTrace());
+                            + task + " adding to task=" + targetTask
+                            + " Callers=" + Debug.getCallers(4));
                     if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p
                             + " out to target's task " + target.task);
                     p.setTask(targetTask, curThumbHolder, false);
@@ -2394,15 +2403,12 @@
         final ArrayList<ActivityRecord> activities = r.task.mActivities;
         final int index = activities.indexOf(r);
         if (index < (activities.size() - 1)) {
-            ActivityRecord next = activities.get(index+1);
-            if (r.frontOfTask) {
-                // The next activity is now the front of the task.
-                next.frontOfTask = true;
-            }
+            r.task.setFrontOfTask();
             if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
                 // If the caller asked that this activity (and all above it)
                 // be cleared when the task is reset, don't lose that information,
                 // but propagate it up to the next activity.
+                ActivityRecord next = activities.get(index+1);
                 next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
             }
         }
@@ -2781,7 +2787,7 @@
                 }
                 if (r.app.activities.isEmpty()) {
                     // No longer have activities, so update LRU list and oom adj.
-                    mService.updateLruProcessLocked(r.app, false, false);
+                    mService.updateLruProcessLocked(r.app, false, null);
                     mService.updateOomAdjLocked();
                 }
             }
@@ -3142,7 +3148,9 @@
 
         final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
         if (task == tr && task.mOnTopOfHome || numTasks <= 1) {
-            task.mOnTopOfHome = false;
+            if (task != null) {
+                task.mOnTopOfHome = false;
+            }
             return mStackSupervisor.resumeHomeActivity(null);
         }
 
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 7650a65..d616f1b 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -905,7 +905,8 @@
         if (idx < 0) {
             app.activities.add(r);
         }
-        mService.updateLruProcessLocked(app, true, true);
+        mService.updateLruProcessLocked(app, true, null);
+        mService.updateOomAdjLocked();
 
         final ActivityStack stack = r.task.stack;
         try {
@@ -1133,6 +1134,19 @@
                 resultRecord.removeResultsLocked(
                     sourceRecord, resultWho, requestCode);
             }
+            if (sourceRecord.launchedFromUid == callingUid) {
+                // The new activity is being launched from the same uid as the previous
+                // activity in the flow, and asking to forward its result back to the
+                // previous.  In this case the activity is serving as a trampoline between
+                // the two, so we also want to update its launchedFromPackage to be the
+                // same as the previous activity.  Note that this is safe, since we know
+                // these two packages come from the same uid; the caller could just as
+                // well have supplied that same package name itself.  This specifially
+                // deals with the case of an intent picker/chooser being launched in the app
+                // flow to redirect to an activity picked by the user, where we want the final
+                // activity to consider it to have been launched by the previous app activity.
+                callingPackage = sourceRecord.launchedFromPackage;
+            }
         }
 
         if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
@@ -1385,17 +1399,22 @@
             launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
         }
 
+        ActivityInfo newTaskInfo = null;
+        Intent newTaskIntent = null;
         final ActivityStack sourceStack;
         if (sourceRecord != null) {
             if (sourceRecord.finishing) {
                 // If the source is finishing, we can't further count it as our source.  This
                 // is because the task it is associated with may now be empty and on its way out,
                 // so we don't want to blindly throw it in to that task.  Instead we will take
-                // the NEW_TASK flow and try to find a task for it.
+                // the NEW_TASK flow and try to find a task for it. But save the task information
+                // so it can be used when creating the new task.
                 if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
                     Slog.w(TAG, "startActivity called from finishing " + sourceRecord
                             + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
                     launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+                    newTaskInfo = sourceRecord.info;
+                    newTaskIntent = sourceRecord.task.intent;
                 }
                 sourceRecord = null;
                 sourceStack = null;
@@ -1667,8 +1686,10 @@
             targetStack = adjustStackFocus(r);
             moveHomeStack(targetStack.isHomeStack());
             if (reuseTask == null) {
-                r.setTask(targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true),
-                        null, true);
+                r.setTask(targetStack.createTaskRecord(getNextTaskId(),
+                        newTaskInfo != null ? newTaskInfo : r.info,
+                        newTaskIntent != null ? newTaskIntent : intent,
+                        true), null, true);
                 if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
                         r.task);
             } else {
@@ -1688,6 +1709,7 @@
             TaskRecord sourceTask = sourceRecord.task;
             targetStack = sourceTask.stack;
             moveHomeStack(targetStack.isHomeStack());
+            mWindowManager.moveTaskToTop(sourceTask.taskId);
             if (!addingToTask &&
                     (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
                 // In this case, we are adding the activity to an existing
@@ -1746,6 +1768,7 @@
             r.setTask(prev != null ? prev.task
                     : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true),
                     null, true);
+            mWindowManager.moveTaskToTop(r.task.taskId);
             if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                     + " in new guessed " + r.task);
         }
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 0dd950e..2d59678 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -419,6 +419,20 @@
         }
     }
 
+    public void noteWifiBatchedScanStartedFromSource(WorkSource ws, int csph) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiBatchedScanStartedFromSourceLocked(ws, csph);
+        }
+    }
+
+    public void noteWifiBatchedScanStoppedFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiBatchedScanStoppedFromSourceLocked(ws);
+        }
+    }
+
     public void noteWifiMulticastEnabledFromSource(WorkSource ws) {
         enforceCallingPermission();
         synchronized (mStats) {
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index 5e80135..bfb667f 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -27,6 +27,7 @@
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
@@ -220,7 +221,8 @@
         r.curApp = app;
         app.curReceiver = r;
         app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
-        mService.updateLruProcessLocked(app, true, false);
+        mService.updateLruProcessLocked(app, false, null);
+        mService.updateOomAdjLocked();
 
         // Tell the application to launch this receiver.
         r.intent.setComponent(r.curComponent);
@@ -813,6 +815,26 @@
                         + " to " + r.curApp + ": process crashing");
                 skip = true;
             }
+            if (!skip) {
+                boolean isAvailable = false;
+                try {
+                    isAvailable = AppGlobals.getPackageManager().isPackageAvailable(
+                            info.activityInfo.packageName,
+                            UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
+                } catch (Exception e) {
+                    // all such failures mean we skip this receiver
+                    Slog.w(TAG, "Exception getting recipient info for "
+                            + info.activityInfo.packageName, e);
+                }
+                if (!isAvailable) {
+                    if (DEBUG_BROADCAST) {
+                        Slog.v(TAG, "Skipping delivery to " + info.activityInfo.packageName
+                                + " / " + info.activityInfo.applicationInfo.uid
+                                + " : package no longer available");
+                    }
+                    skip = true;
+                }
+            }
 
             if (skip) {
                 if (DEBUG_BROADCAST)  Slog.v(TAG,
diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java
index 576adc2..423e540 100644
--- a/services/java/com/android/server/am/ConnectionRecord.java
+++ b/services/java/com/android/server/am/ConnectionRecord.java
@@ -27,7 +27,7 @@
  */
 final class ConnectionRecord {
     final AppBindRecord binding;    // The application/service binding.
-    final ActivityRecord activity;   // If non-null, the owning activity.
+    final ActivityRecord activity;  // If non-null, the owning activity.
     final IServiceConnection conn;  // The client connection.
     final int flags;                // Binding options.
     final int clientLabel;          // String resource labeling this client.
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 486e916..217a8d6 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -46,7 +46,7 @@
  * is currently running.
  */
 final class ProcessRecord {
-    final BatteryStatsImpl.Uid.Proc batteryStats; // where to collect runtime statistics
+    private final BatteryStatsImpl mBatteryStats; // where to collect runtime statistics
     final ApplicationInfo info; // all about the first app in the process
     final boolean isolated;     // true if this is a special isolated process
     final int uid;              // uid of process; may be different from 'info' if isolated
@@ -86,7 +86,6 @@
     boolean keeping;            // Actively running code so don't kill due to that?
     boolean setIsForeground;    // Running foreground UI when last set?
     boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle?
-    boolean hasActivities;      // Are there any activities running in this process?
     boolean hasClientActivities;  // Are there any client services with activities?
     boolean hasStartedServices; // Are there any started services running in this process?
     boolean foregroundServices; // Running any services that are foreground?
@@ -265,9 +264,8 @@
             pw.print(prefix); pw.print("persistent="); pw.print(persistent);
                     pw.print(" removed="); pw.println(removed);
         }
-        if (hasActivities || hasClientActivities || foregroundActivities) {
-            pw.print(prefix); pw.print("hasActivities="); pw.print(hasActivities);
-                    pw.print(" hasClientActivities="); pw.print(hasClientActivities);
+        if (hasClientActivities || foregroundActivities) {
+            pw.print(prefix); pw.print("hasClientActivities="); pw.print(hasClientActivities);
                     pw.print(" foregroundActivities="); pw.println(foregroundActivities);
         }
         if (hasStartedServices) {
@@ -275,8 +273,8 @@
         }
         if (!keeping) {
             long wtime;
-            synchronized (batteryStats.getBatteryStats()) {
-                wtime = batteryStats.getBatteryStats().getProcessWakeTime(info.uid,
+            synchronized (mBatteryStats) {
+                wtime = mBatteryStats.getProcessWakeTime(info.uid,
                         pid, SystemClock.elapsedRealtime());
             }
             long timeUsed = wtime - lastWakeTime;
@@ -361,9 +359,9 @@
         }
     }
     
-    ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, ApplicationInfo _info,
+    ProcessRecord(BatteryStatsImpl _batteryStats, ApplicationInfo _info,
             String _processName, int _uid) {
-        batteryStats = _batteryStats;
+        mBatteryStats = _batteryStats;
         info = _info;
         isolated = _info.uid != _uid;
         uid = _uid;
diff --git a/services/java/com/android/server/am/ProcessStatsService.java b/services/java/com/android/server/am/ProcessStatsService.java
index 8d16880..e05fcda 100644
--- a/services/java/com/android/server/am/ProcessStatsService.java
+++ b/services/java/com/android/server/am/ProcessStatsService.java
@@ -750,23 +750,12 @@
                     return;
                 } else {
                     // Not an option, last argument must be a package name.
-                    try {
-                        IPackageManager pm = AppGlobals.getPackageManager();
-                        if (pm.getPackageUid(arg, UserHandle.getCallingUserId()) >= 0) {
-                            reqPackage = arg;
-                            // Include all details, since we know we are only going to
-                            // be dumping a smaller set of data.  In fact only the details
-                            // container per-package data, so that are needed to be able
-                            // to dump anything at all when filtering by package.
-                            dumpDetails = true;
-                        }
-                    } catch (RemoteException e) {
-                    }
-                    if (reqPackage == null) {
-                        pw.println("Unknown package: " + arg);
-                        dumpHelp(pw);
-                        return;
-                    }
+                    reqPackage = arg;
+                    // Include all details, since we know we are only going to
+                    // be dumping a smaller set of data.  In fact only the details
+                    // container per-package data, so that are needed to be able
+                    // to dump anything at all when filtering by package.
+                    dumpDetails = true;
                 }
             }
         }
@@ -816,13 +805,14 @@
             }
             return;
         } else if (aggregateHours != 0) {
+            pw.print("AGGREGATED OVER LAST "); pw.print(aggregateHours); pw.println(" HOURS:");
             dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact,
                     dumpDetails, dumpFullDetails, dumpAll, activeOnly);
             return;
         }
 
         boolean sepNeeded = false;
-        if (!currentOnly || isCheckin) {
+        if (dumpAll || isCheckin) {
             mWriteLock.lock();
             try {
                 ArrayList<String> files = getCommittedFiles(0, false, !isCheckin);
@@ -882,11 +872,11 @@
             }
         }
         if (!isCheckin) {
-            if (dumpAll) {
+            if (!currentOnly) {
                 if (sepNeeded) {
                     pw.println();
-                    pw.println("AGGREGATED OVER LAST 24 HOURS:");
                 }
+                pw.println("AGGREGATED OVER LAST 24 HOURS:");
                 dumpAggregatedStats(pw, 24, now, reqPackage, isCompact,
                         dumpDetails, dumpFullDetails, dumpAll, activeOnly);
                 pw.println();
@@ -901,8 +891,8 @@
                 } else {
                     if (sepNeeded) {
                         pw.println();
-                        pw.println("CURRENT STATS:");
                     }
+                    pw.println("CURRENT STATS:");
                     if (dumpDetails || dumpFullDetails) {
                         mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
                                 activeOnly);
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index cc1172a..80e6e94 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -85,6 +85,7 @@
     ProcessRecord app;      // where this service is running or null.
     ProcessRecord isolatedProc; // keep track of isolated process, if requested
     ProcessStats.ServiceState tracker; // tracking service execution, may be null
+    ProcessStats.ServiceState restartTracker; // tracking service restart
     boolean delayed;        // are we waiting to start this service in the background?
     boolean isForeground;   // is service currently in foreground mode?
     int foregroundId;       // Notification ID of last foreground req.
@@ -340,6 +341,19 @@
         }
     }
 
+    public void makeRestarting(int memFactor, long now) {
+        if (restartTracker == null) {
+            if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
+                restartTracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
+                        serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+            }
+            if (restartTracker == null) {
+                return;
+            }
+        }
+        restartTracker.setRestarting(true, memFactor, now);
+    }
+
     public AppBindRecord retrieveAppBindingLocked(Intent intent,
             ProcessRecord app) {
         Intent.FilterComparison filter = new Intent.FilterComparison(intent);
diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java
index 3d568ff..9105103 100644
--- a/services/java/com/android/server/am/TaskRecord.java
+++ b/services/java/com/android/server/am/TaskRecord.java
@@ -159,18 +159,33 @@
         return null;
     }
 
+    /** Call after activity movement or finish to make sure that frontOfTask is set correctly */
+    final void setFrontOfTask() {
+        boolean foundFront = false;
+        final int numActivities = mActivities.size();
+        for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
+            final ActivityRecord r = mActivities.get(activityNdx);
+            if (foundFront || r.finishing) {
+                r.frontOfTask = false;
+            } else {
+                r.frontOfTask = true;
+                // Set frontOfTask false for every following activity.
+                foundFront = true;
+            }
+        }
+    }
+
     /**
-     * Reorder the history stack so that the activity at the given index is
-     * brought to the front.
+     * Reorder the history stack so that the passed activity is brought to the front.
      */
     final void moveActivityToFrontLocked(ActivityRecord newTop) {
         if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + newTop
             + " to stack at top", new RuntimeException("here").fillInStackTrace());
 
-        getTopActivity().frontOfTask = false;
         mActivities.remove(newTop);
         mActivities.add(newTop);
-        newTop.frontOfTask = true;
+
+        setFrontOfTask();
     }
 
     void addActivityAtBottom(ActivityRecord r) {
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index f5a7039..8f25860 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -45,6 +45,7 @@
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
 import android.net.NetworkInfo;
+import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.os.Binder;
@@ -77,6 +78,7 @@
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.nio.charset.StandardCharsets;
@@ -282,13 +284,12 @@
     }
 
     /**
-     * Protect a socket from routing changes by binding it to the given
-     * interface. The socket is NOT closed by this method.
+     * Protect a socket from VPN rules by binding it to the main routing table.
+     * The socket is NOT closed by this method.
      *
      * @param socket The socket to be bound.
-     * @param interfaze The name of the interface.
      */
-    public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
+    public void protect(ParcelFileDescriptor socket) throws Exception {
 
         PackageManager pm = mContext.getPackageManager();
         int appUid = pm.getPackageUid(mPackage, mUserId);
@@ -302,8 +303,6 @@
         } finally {
             Binder.restoreCallingIdentity(token);
         }
-        // bind the socket to the interface
-        jniProtect(socket.getFd(), interfaze);
 
     }
 
@@ -353,6 +352,12 @@
             Binder.restoreCallingIdentity(token);
         }
 
+        // Save the old config in case we need to go back.
+        VpnConfig oldConfig = mConfig;
+        String oldInterface = mInterface;
+        Connection oldConnection = mConnection;
+        SparseBooleanArray oldUsers = mVpnUsers;
+
         // Configure the interface. Abort if any of these steps fails.
         ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
         try {
@@ -372,12 +377,7 @@
                         new UserHandle(mUserId))) {
                 throw new IllegalStateException("Cannot bind " + config.user);
             }
-            if (mConnection != null) {
-                mContext.unbindService(mConnection);
-            }
-            if (mInterface != null && !mInterface.equals(interfaze)) {
-                jniReset(mInterface);
-            }
+
             mConnection = connection;
             mInterface = interfaze;
 
@@ -386,55 +386,91 @@
             config.interfaze = mInterface;
             config.startTime = SystemClock.elapsedRealtime();
             mConfig = config;
+
             // Set up forwarding and DNS rules.
             mVpnUsers = new SparseBooleanArray();
             token = Binder.clearCallingIdentity();
             try {
                 mCallback.setMarkedForwarding(mInterface);
-                mCallback.setRoutes(interfaze, config.routes);
+                mCallback.setRoutes(mInterface, config.routes);
                 mCallback.override(mInterface, config.dnsServers, config.searchDomains);
                 addVpnUserLocked(mUserId);
-
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-
-        } catch (RuntimeException e) {
-            updateState(DetailedState.FAILED, "establish");
-            IoUtils.closeQuietly(tun);
-            // make sure marked forwarding is cleared if it was set
-            try {
-                mCallback.clearMarkedForwarding(mInterface);
-            } catch (Exception ingored) {
-                // ignored
-            }
-            throw e;
-        }
-        Log.i(TAG, "Established by " + config.user + " on " + mInterface);
-
-
-        // If we are owner assign all Restricted Users to this VPN
-        if (mUserId == UserHandle.USER_OWNER) {
-            token = Binder.clearCallingIdentity();
-            try {
-                for (UserInfo user : mgr.getUsers()) {
-                    if (user.isRestricted()) {
-                        try {
-                            addVpnUserLocked(user.id);
-                        } catch (Exception e) {
-                            Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN");
+                // If we are owner assign all Restricted Users to this VPN
+                if (mUserId == UserHandle.USER_OWNER) {
+                    for (UserInfo user : mgr.getUsers()) {
+                        if (user.isRestricted()) {
+                            try {
+                                addVpnUserLocked(user.id);
+                            } catch (Exception e) {
+                                Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN");
+                            }
                         }
                     }
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
+
+            if (oldConnection != null) {
+                mContext.unbindService(oldConnection);
+            }
+            if (oldInterface != null && !oldInterface.equals(interfaze)) {
+                // Remove the old tun's user forwarding rules
+                // The new tun's user rules have already been added so they will take over
+                // as rules are deleted. This prevents data leakage as the rules are moved over.
+                token = Binder.clearCallingIdentity();
+                try {
+                        final int size = oldUsers.size();
+                        final boolean forwardDns = (oldConfig.dnsServers != null &&
+                                oldConfig.dnsServers.size() != 0);
+                        for (int i = 0; i < size; i++) {
+                            int user = oldUsers.keyAt(i);
+                            mCallback.clearUserForwarding(oldInterface, user, forwardDns);
+                        }
+                        mCallback.clearMarkedForwarding(oldInterface);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                jniReset(oldInterface);
+            }
+        } catch (RuntimeException e) {
+            updateState(DetailedState.FAILED, "establish");
+            IoUtils.closeQuietly(tun);
+            // make sure marked forwarding is cleared if it was set
+            token = Binder.clearCallingIdentity();
+            try {
+                mCallback.clearMarkedForwarding(mInterface);
+            } catch (Exception ingored) {
+                // ignored
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+            // restore old state
+            mConfig = oldConfig;
+            mConnection = oldConnection;
+            mVpnUsers = oldUsers;
+            mInterface = oldInterface;
+            throw e;
         }
+        Log.i(TAG, "Established by " + config.user + " on " + mInterface);
+
         // TODO: ensure that contract class eventually marks as connected
         updateState(DetailedState.AUTHENTICATING, "establish");
         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;
     }
@@ -670,7 +706,6 @@
     private native int jniSetRoutes(String interfaze, String routes);
     private native void jniReset(String interfaze);
     private native int jniCheck(String interfaze);
-    private native void jniProtect(int socket, String interfaze);
 
     private static RouteInfo findIPv4DefaultRoute(LinkProperties prop) {
         for (RouteInfo route : prop.getAllRoutes()) {
diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java
index cb35ef1..023bf2b 100644
--- a/services/java/com/android/server/content/ContentService.java
+++ b/services/java/com/android/server/content/ContentService.java
@@ -660,7 +660,7 @@
         int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
-            return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId);
+            return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
         } finally {
             restoreCallingIdentity(identityToken);
         }
diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java
index 71d8d99..9e3dad6 100644
--- a/services/java/com/android/server/content/SyncManager.java
+++ b/services/java/com/android/server/content/SyncManager.java
@@ -2352,7 +2352,7 @@
                             Log.v(TAG, "canceling and rescheduling sync since an initialization "
                                     + "takes higher priority, " + conflict);
                         }
-                    } else if (candidate.expedited && !conflict.mSyncOperation.expedited
+                    } else if (candidate.isExpedited() && !conflict.mSyncOperation.isExpedited()
                             && (candidateIsInitialization
                                 == conflict.mSyncOperation.isInitialization())) {
                         toReschedule = conflict;
diff --git a/services/java/com/android/server/content/SyncOperation.java b/services/java/com/android/server/content/SyncOperation.java
index 4856747..67e3b09 100644
--- a/services/java/com/android/server/content/SyncOperation.java
+++ b/services/java/com/android/server/content/SyncOperation.java
@@ -66,7 +66,8 @@
     public final boolean allowParallelSyncs;
     public Bundle extras;
     public final String key;
-    public boolean expedited;
+    /** Internal boolean to avoid reading a bundle everytime we want to compare operations. */
+    private final boolean expedited;
     public SyncStorageEngine.PendingOperation pendingOperation;
     /** Elapsed real time in millis at which to run this sync. */
     public long latestRunTime;
@@ -79,7 +80,7 @@
      * Depends on max(backoff, latestRunTime, and delayUntil).
      */
     public long effectiveRunTime;
-    /** Amount of time before {@link effectiveRunTime} from which this sync can run. */
+    /** Amount of time before {@link #effectiveRunTime} from which this sync can run. */
     public long flexTime;
 
     public SyncOperation(Account account, int userId, int reason, int source, String authority,
@@ -98,11 +99,16 @@
         this.backoff = backoff;
         final long now = SystemClock.elapsedRealtime();
         // Checks the extras bundle. Must occur after we set the internal bundle.
-        if (runTimeFromNow < 0 || isExpedited()) {
+        if (runTimeFromNow < 0) {
+            // Sanity check: Will always be true.
+            if (!this.extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
+                this.extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+            }
             this.expedited = true;
             this.latestRunTime = now;
             this.flexTime = 0;
         } else {
+            this.extras.remove(ContentResolver.SYNC_EXTRAS_EXPEDITED);
             this.expedited = false;
             this.latestRunTime = now + runTimeFromNow;
             this.flexTime = flexTime;
@@ -111,6 +117,24 @@
         this.key = toKey();
     }
 
+    /** Only used to immediately reschedule a sync. */
+    SyncOperation(SyncOperation other) {
+        this.service = other.service;
+        this.account = other.account;
+        this.authority = other.authority;
+        this.userId = other.userId;
+        this.reason = other.reason;
+        this.syncSource = other.syncSource;
+        this.extras = new Bundle(other.extras);
+        this.expedited = other.expedited;
+        this.latestRunTime = SystemClock.elapsedRealtime();
+        this.flexTime = 0L;
+        this.backoff = other.backoff;
+        this.allowParallelSyncs = other.allowParallelSyncs;
+        this.updateEffectiveRunTime();
+        this.key = toKey();
+    }
+
     /**
      * Make sure the bundle attached to this SyncOperation doesn't have unnecessary
      * flags set.
@@ -138,24 +162,6 @@
         }
     }
 
-    /** Only used to immediately reschedule a sync. */
-    SyncOperation(SyncOperation other) {
-        this.service = other.service;
-        this.account = other.account;
-        this.authority = other.authority;
-        this.userId = other.userId;
-        this.reason = other.reason;
-        this.syncSource = other.syncSource;
-        this.extras = new Bundle(other.extras);
-        this.expedited = other.expedited;
-        this.latestRunTime = SystemClock.elapsedRealtime();
-        this.flexTime = 0L;
-        this.backoff = other.backoff;
-        this.allowParallelSyncs = other.allowParallelSyncs;
-        this.updateEffectiveRunTime();
-        this.key = toKey();
-    }
-
     @Override
     public String toString() {
         return dump(null, true);
@@ -220,7 +226,7 @@
     }
 
     public boolean isExpedited() {
-        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false) || expedited;
+        return expedited;
     }
 
     public boolean ignoreBackoff() {
diff --git a/services/java/com/android/server/content/SyncQueue.java b/services/java/com/android/server/content/SyncQueue.java
index 6f3fe6e..22fa2de 100644
--- a/services/java/com/android/server/content/SyncQueue.java
+++ b/services/java/com/android/server/content/SyncQueue.java
@@ -73,10 +73,9 @@
             }
             SyncOperation syncOperation = new SyncOperation(
                     op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras,
-                    0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0,
+                    op.expedited ? -1: 0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0,
                     mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
                     syncAdapterInfo.type.allowParallelSyncs());
-            syncOperation.expedited = op.expedited;
             syncOperation.pendingOperation = op;
             add(syncOperation, op);
         }
@@ -104,7 +103,6 @@
         if (existingOperation != null) {
             boolean changed = false;
             if (operation.compareTo(existingOperation) <= 0 ) {
-                existingOperation.expedited = operation.expedited;
                 long newRunTime =
                         Math.min(existingOperation.latestRunTime, operation.latestRunTime);
                 // Take smaller runtime.
@@ -123,7 +121,7 @@
         if (operation.pendingOperation == null) {
             pop = new SyncStorageEngine.PendingOperation(
                     operation.account, operation.userId, operation.reason, operation.syncSource,
-                    operation.authority, operation.extras, operation.expedited);
+                    operation.authority, operation.extras, operation.isExpedited());
             pop = mSyncStorageEngine.insertIntoPending(pop);
             if (pop == null) {
                 throw new IllegalStateException("error adding pending sync operation "
diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java
index 41ef229..781280e 100644
--- a/services/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/java/com/android/server/content/SyncStorageEngine.java
@@ -501,7 +501,7 @@
      * @return amount of seconds before syncTimeSeconds that the sync can occur.
      *      I.e.
      *      earliest_sync_time = syncTimeSeconds - calculateDefaultFlexTime(syncTimeSeconds)
-     * The flex time is capped at a percentage of the {@link DEFAULT_POLL_FREQUENCY_SECONDS}.
+     * The flex time is capped at a percentage of the {@link #DEFAULT_POLL_FREQUENCY_SECONDS}.
      */
     public static long calculateDefaultFlexTime(long syncTimeSeconds) {
         if (syncTimeSeconds < DEFAULT_MIN_FLEX_ALLOWED_SECS) {
@@ -1295,21 +1295,41 @@
     }
 
     /**
-     * Return a list of the currently active syncs. Note that the returned items are the
-     * real, live active sync objects, so be careful what you do with it.
+     * Return a list of the currently active syncs. Note that the returned
+     * items are the real, live active sync objects, so be careful what you do
+     * with it.
      */
-    public List<SyncInfo> getCurrentSyncs(int userId) {
+    private List<SyncInfo> getCurrentSyncs(int userId) {
         synchronized (mAuthorities) {
-            ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
-            if (syncs == null) {
-                syncs = new ArrayList<SyncInfo>();
-                mCurrentSyncs.put(userId, syncs);
-            }
-            return syncs;
+            return getCurrentSyncsLocked(userId);
         }
     }
 
     /**
+     * @return a copy of the current syncs data structure. Will not return
+     * null.
+     */
+    public List<SyncInfo> getCurrentSyncsCopy(int userId) {
+        synchronized (mAuthorities) {
+            final List<SyncInfo> syncs = getCurrentSyncsLocked(userId);
+            final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>();
+            for (SyncInfo sync : syncs) {
+                syncsCopy.add(new SyncInfo(sync));
+            }
+            return syncsCopy;
+        }
+    }
+
+    private List<SyncInfo> getCurrentSyncsLocked(int userId) {
+        ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
+        if (syncs == null) {
+            syncs = new ArrayList<SyncInfo>();
+            mCurrentSyncs.put(userId, syncs);
+        }
+        return syncs;
+    }
+
+    /**
      * Return an array of the current sync status for all authorities.  Note
      * that the objects inside the array are the real, live status objects,
      * so be careful what you do with them.
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 249c8b0..bcb677f 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -35,6 +35,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -173,6 +174,9 @@
     // The Wifi display adapter, or null if not registered.
     private WifiDisplayAdapter mWifiDisplayAdapter;
 
+    // The number of active wifi display scan requests.
+    private int mWifiDisplayScanRequestCount;
+
     // The virtual display adapter, or null if not registered.
     private VirtualDisplayAdapter mVirtualDisplayAdapter;
 
@@ -458,38 +462,94 @@
         }
     }
 
-    private void onCallbackDied(int pid) {
+    private void onCallbackDied(CallbackRecord record) {
         synchronized (mSyncRoot) {
-            mCallbacks.remove(pid);
+            mCallbacks.remove(record.mPid);
+            stopWifiDisplayScanLocked(record);
         }
     }
 
     @Override // Binder call
-    public void scanWifiDisplays() {
+    public void startWifiDisplayScan() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to start wifi display scans");
+
+        final int callingPid = Binder.getCallingPid();
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mSyncRoot) {
-                if (mWifiDisplayAdapter != null) {
-                    mWifiDisplayAdapter.requestScanLocked();
+                CallbackRecord record = mCallbacks.get(callingPid);
+                if (record == null) {
+                    throw new IllegalStateException("The calling process has not "
+                            + "registered an IDisplayManagerCallback.");
                 }
+                startWifiDisplayScanLocked(record);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
+    private void startWifiDisplayScanLocked(CallbackRecord record) {
+        if (!record.mWifiDisplayScanRequested) {
+            record.mWifiDisplayScanRequested = true;
+            if (mWifiDisplayScanRequestCount++ == 0) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestStartScanLocked();
+                }
+            }
+        }
+    }
+
+    @Override // Binder call
+    public void stopWifiDisplayScan() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to stop wifi display scans");
+
+        final int callingPid = Binder.getCallingPid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                CallbackRecord record = mCallbacks.get(callingPid);
+                if (record == null) {
+                    throw new IllegalStateException("The calling process has not "
+                            + "registered an IDisplayManagerCallback.");
+                }
+                stopWifiDisplayScanLocked(record);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void stopWifiDisplayScanLocked(CallbackRecord record) {
+        if (record.mWifiDisplayScanRequested) {
+            record.mWifiDisplayScanRequested = false;
+            if (--mWifiDisplayScanRequestCount == 0) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestStopScanLocked();
+                }
+            } else if (mWifiDisplayScanRequestCount < 0) {
+                Log.wtf(TAG, "mWifiDisplayScanRequestCount became negative: "
+                        + mWifiDisplayScanRequestCount);
+                mWifiDisplayScanRequestCount = 0;
+            }
+        }
+    }
+
     @Override // Binder call
     public void connectWifiDisplay(String address) {
         if (address == null) {
             throw new IllegalArgumentException("address must not be null");
         }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to connect to a wifi display");
 
-        final boolean trusted = canCallerConfigureWifiDisplay();
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mSyncRoot) {
                 if (mWifiDisplayAdapter != null) {
-                    mWifiDisplayAdapter.requestConnectLocked(address, trusted);
+                    mWifiDisplayAdapter.requestConnectLocked(address);
                 }
             }
         } finally {
@@ -499,12 +559,8 @@
 
     @Override
     public void pauseWifiDisplay() {
-        if (mContext.checkCallingPermission(
-                android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
-                        != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY"
-                    + "permission to pause a wifi display session.");
-        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to pause a wifi display session");
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -520,12 +576,8 @@
 
     @Override
     public void resumeWifiDisplay() {
-        if (mContext.checkCallingPermission(
-                android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
-                        != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY"
-                    + "permission to resume a wifi display session.");
-        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to resume a wifi display session");
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -541,6 +593,11 @@
 
     @Override // Binder call
     public void disconnectWifiDisplay() {
+        // This request does not require special permissions.
+        // Any app can request disconnection from the currently active wifi display.
+        // This exception should no longer be needed once wifi display control moves
+        // to the media router service.
+
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mSyncRoot) {
@@ -558,10 +615,8 @@
         if (address == null) {
             throw new IllegalArgumentException("address must not be null");
         }
-        if (!canCallerConfigureWifiDisplay()) {
-            throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission to "
-                    + "rename a wifi display.");
-        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to rename to a wifi display");
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -580,10 +635,8 @@
         if (address == null) {
             throw new IllegalArgumentException("address must not be null");
         }
-        if (!canCallerConfigureWifiDisplay()) {
-            throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission to "
-                    + "forget a wifi display.");
-        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to forget to a wifi display");
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -599,6 +652,9 @@
 
     @Override // Binder call
     public WifiDisplayStatus getWifiDisplayStatus() {
+        // This request does not require special permissions.
+        // Any app can get information about available wifi displays.
+
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mSyncRoot) {
@@ -612,11 +668,6 @@
         }
     }
 
-    private boolean canCallerConfigureWifiDisplay() {
-        return mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
     @Override // Binder call
     public int createVirtualDisplay(IBinder appToken, String packageName,
             String name, int width, int height, int densityDpi, Surface surface, int flags) {
@@ -1112,6 +1163,7 @@
             pw.println("  mDefaultViewport=" + mDefaultViewport);
             pw.println("  mExternalTouchViewport=" + mExternalTouchViewport);
             pw.println("  mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
+            pw.println("  mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
 
             IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
             ipw.increaseIndent();
@@ -1139,6 +1191,15 @@
                 pw.println("  Display " + displayId + ":");
                 display.dumpLocked(ipw);
             }
+
+            final int callbackCount = mCallbacks.size();
+            pw.println();
+            pw.println("Callbacks: size=" + callbackCount);
+            for (int i = 0; i < callbackCount; i++) {
+                CallbackRecord callback = mCallbacks.valueAt(i);
+                pw.println("  " + i + ": mPid=" + callback.mPid
+                        + ", mWifiDisplayScanRequested=" + callback.mWifiDisplayScanRequested);
+            }
         }
     }
 
@@ -1239,9 +1300,11 @@
     }
 
     private final class CallbackRecord implements DeathRecipient {
-        private final int mPid;
+        public final int mPid;
         private final IDisplayManagerCallback mCallback;
 
+        public boolean mWifiDisplayScanRequested;
+
         public CallbackRecord(int pid, IDisplayManagerCallback callback) {
             mPid = pid;
             mCallback = callback;
@@ -1252,7 +1315,7 @@
             if (DEBUG) {
                 Slog.d(TAG, "Display listener for pid " + mPid + " died.");
             }
-            onCallbackDied(mPid);
+            onCallbackDied(this);
         }
 
         public void notifyDisplayEventAsync(int displayId, int event) {
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
index f7bbdf8..cd57941 100644
--- a/services/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -127,7 +127,7 @@
         pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
         pw.println("mPendingNotificationUpdate=" + mPendingNotificationUpdate);
         pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers);
-
+ 
         // Try to dump the controller state.
         if (mDisplayController == null) {
             pw.println("mDisplayController=null");
@@ -157,34 +157,39 @@
         });
     }
 
-    public void requestScanLocked() {
+    public void requestStartScanLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "requestScanLocked");
+            Slog.d(TAG, "requestStartScanLocked");
         }
 
         getHandler().post(new Runnable() {
             @Override
             public void run() {
                 if (mDisplayController != null) {
-                    mDisplayController.requestScan();
+                    mDisplayController.requestStartScan();
                 }
             }
         });
     }
 
-    public void requestConnectLocked(final String address, final boolean trusted) {
+    public void requestStopScanLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted);
+            Slog.d(TAG, "requestStopScanLocked");
         }
 
-        if (!trusted) {
-            synchronized (getSyncRoot()) {
-                if (!isRememberedDisplayLocked(address)) {
-                    Slog.w(TAG, "Ignoring request by an untrusted client to connect to "
-                            + "an unknown wifi display: " + address);
-                    return;
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestStopScan();
                 }
             }
+        });
+    }
+
+    public void requestConnectLocked(final String address) {
+        if (DEBUG) {
+            Slog.d(TAG, "requestConnectLocked: address=" + address);
         }
 
         getHandler().post(new Runnable() {
@@ -197,15 +202,6 @@
         });
     }
 
-    private boolean isRememberedDisplayLocked(String address) {
-        for (WifiDisplay display : mRememberedDisplays) {
-            if (display.getDeviceAddress().equals(address)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     public void requestPauseLocked() {
         if (DEBUG) {
             Slog.d(TAG, "requestPauseLocked");
@@ -400,8 +396,6 @@
         mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
                 refreshRate, deviceFlags, address, surface);
         sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
-
-        scheduleUpdateNotificationLocked();
     }
 
     private void removeDisplayDeviceLocked() {
@@ -409,8 +403,6 @@
             mDisplayDevice.destroyLocked();
             sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
             mDisplayDevice = null;
-
-            scheduleUpdateNotificationLocked();
         }
     }
 
@@ -457,21 +449,24 @@
 
     // Runs on the handler.
     private void handleUpdateNotification() {
-        final boolean isConnected;
+        final int state;
+        final WifiDisplay display;
         synchronized (getSyncRoot()) {
             if (!mPendingNotificationUpdate) {
                 return;
             }
 
             mPendingNotificationUpdate = false;
-            isConnected = (mDisplayDevice != null);
+            state = mActiveDisplayState;
+            display = mActiveDisplay;
         }
 
         // Cancel the old notification if there is one.
         mNotificationManager.cancelAsUser(null,
-                R.string.wifi_display_notification_title, UserHandle.ALL);
+                R.string.wifi_display_notification_disconnect, UserHandle.ALL);
 
-        if (isConnected) {
+        if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING
+                || state == WifiDisplayStatus.DISPLAY_STATE_CONNECTED) {
             Context context = getContext();
 
             // Initialize pending intents for the notification outside of the lock because
@@ -493,20 +488,38 @@
 
             // Post the notification.
             Resources r = context.getResources();
-            Notification notification = new Notification.Builder(context)
-                    .setContentTitle(r.getString(
-                            R.string.wifi_display_notification_title))
-                    .setContentText(r.getString(
-                            R.string.wifi_display_notification_message))
-                    .setContentIntent(mSettingsPendingIntent)
-                    .setSmallIcon(R.drawable.ic_notify_wifidisplay)
-                    .setOngoing(true)
-                    .addAction(android.R.drawable.ic_menu_close_clear_cancel,
-                            r.getString(R.string.wifi_display_notification_disconnect),
-                            mDisconnectPendingIntent)
-                    .build();
+            Notification notification;
+            if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING) {
+                notification = new Notification.Builder(context)
+                        .setContentTitle(r.getString(
+                                R.string.wifi_display_notification_connecting_title))
+                        .setContentText(r.getString(
+                                R.string.wifi_display_notification_connecting_message,
+                                display.getFriendlyDisplayName()))
+                        .setContentIntent(mSettingsPendingIntent)
+                        .setSmallIcon(R.drawable.ic_notification_cast_connecting)
+                        .setOngoing(true)
+                        .addAction(android.R.drawable.ic_menu_close_clear_cancel,
+                                r.getString(R.string.wifi_display_notification_disconnect),
+                                mDisconnectPendingIntent)
+                        .build();
+            } else {
+                notification = new Notification.Builder(context)
+                        .setContentTitle(r.getString(
+                                R.string.wifi_display_notification_connected_title))
+                        .setContentText(r.getString(
+                                R.string.wifi_display_notification_connected_message,
+                                display.getFriendlyDisplayName()))
+                        .setContentIntent(mSettingsPendingIntent)
+                        .setSmallIcon(R.drawable.ic_notification_cast_on)
+                        .setOngoing(true)
+                        .addAction(android.R.drawable.ic_menu_close_clear_cancel,
+                                r.getString(R.string.wifi_display_notification_disconnect),
+                                mDisconnectPendingIntent)
+                        .build();
+            }
             mNotificationManager.notifyAsUser(null,
-                    R.string.wifi_display_notification_title,
+                    R.string.wifi_display_notification_disconnect,
                     notification, UserHandle.ALL);
         }
     }
@@ -545,20 +558,20 @@
         }
 
         @Override
-        public void onScanFinished(WifiDisplay[] availableDisplays) {
+        public void onScanResults(WifiDisplay[] availableDisplays) {
             synchronized (getSyncRoot()) {
                 availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
                         availableDisplays);
 
-                // check if any of the available displays changed canConnect status
                 boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);
+
+                // Check whether any of the available displays changed canConnect status.
                 for (int i = 0; !changed && i<availableDisplays.length; i++) {
                     changed = availableDisplays[i].canConnect()
                             != mAvailableDisplays[i].canConnect();
                 }
 
-                if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING || changed) {
-                    mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
+                if (changed) {
                     mAvailableDisplays = availableDisplays;
                     fixRememberedDisplayNamesFromAvailableDisplaysLocked();
                     updateDisplaysLocked();
@@ -568,6 +581,16 @@
         }
 
         @Override
+        public void onScanFinished() {
+            synchronized (getSyncRoot()) {
+                if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING) {
+                    mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        @Override
         public void onDisplayConnecting(WifiDisplay display) {
             synchronized (getSyncRoot()) {
                 display = mPersistentDataStore.applyWifiDisplayAlias(display);
@@ -578,6 +601,7 @@
                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
                     mActiveDisplay = display;
                     scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
                 }
             }
         }
@@ -590,6 +614,7 @@
                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
                     mActiveDisplay = null;
                     scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
                 }
             }
         }
@@ -607,6 +632,7 @@
                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
                     mActiveDisplay = display;
                     scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
                 }
             }
         }
@@ -629,6 +655,7 @@
                     mActiveDisplay = display;
                     renameDisplayDeviceLocked(display.getFriendlyDisplayName());
                     scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
                 }
             }
         }
@@ -644,6 +671,7 @@
                     mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
                     mActiveDisplay = null;
                     scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
                 }
             }
         }
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
index 9a4cfb7..dbb59b2 100644
--- a/services/java/com/android/server/display/WifiDisplayController.java
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -27,7 +27,6 @@
 import android.hardware.display.WifiDisplay;
 import android.hardware.display.WifiDisplaySessionInfo;
 import android.hardware.display.WifiDisplayStatus;
-import android.media.AudioManager;
 import android.media.RemoteDisplay;
 import android.net.NetworkInfo;
 import android.net.Uri;
@@ -75,12 +74,19 @@
 
     private static final int DEFAULT_CONTROL_PORT = 7236;
     private static final int MAX_THROUGHPUT = 50;
-    private static final int CONNECTION_TIMEOUT_SECONDS = 60;
-    private static final int RTSP_TIMEOUT_SECONDS = 15;
+    private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+    private static final int RTSP_TIMEOUT_SECONDS = 30;
     private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120;
 
-    private static final int DISCOVER_PEERS_MAX_RETRIES = 10;
-    private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500;
+    // We repeatedly issue calls to discover peers every so often for a few reasons.
+    // 1. The initial request may fail and need to retried.
+    // 2. Discovery will self-abort after any group is initiated, which may not necessarily
+    //    be what we want to have happen.
+    // 3. Discovery will self-timeout after 2 minutes, whereas we want discovery to
+    //    be occur for as long as a client is requesting it be.
+    // 4. We don't seem to get updated results for displays we've already found until
+    //    we ask to discover again, particularly for the isSessionAvailable() property.
+    private static final int DISCOVER_PEERS_INTERVAL_MILLIS = 10000;
 
     private static final int CONNECT_MAX_RETRIES = 3;
     private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
@@ -103,12 +109,12 @@
     // True if Wifi display is enabled by the user.
     private boolean mWifiDisplayOnSetting;
 
+    // True if a scan was requested independent of whether one is actually in progress.
+    private boolean mScanRequested;
+
     // True if there is a call to discoverPeers in progress.
     private boolean mDiscoverPeersInProgress;
 
-    // Number of discover peers retries remaining.
-    private int mDiscoverPeersRetriesLeft;
-
     // The device to which we want to connect, or null if we want to be disconnected.
     private WifiP2pDevice mDesiredDevice;
 
@@ -209,8 +215,8 @@
         pw.println("mWfdEnabled=" + mWfdEnabled);
         pw.println("mWfdEnabling=" + mWfdEnabling);
         pw.println("mNetworkInfo=" + mNetworkInfo);
+        pw.println("mScanRequested=" + mScanRequested);
         pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
-        pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft);
         pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
         pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
         pw.println("mDisconnectingDisplay=" + describeWifiP2pDevice(mDisconnectingDevice));
@@ -232,8 +238,18 @@
         }
     }
 
-    public void requestScan() {
-        discoverPeers();
+    public void requestStartScan() {
+        if (!mScanRequested) {
+            mScanRequested = true;
+            updateScanState();
+        }
+    }
+
+    public void requestStopScan() {
+        if (mScanRequested) {
+            mScanRequested = false;
+            updateScanState();
+        }
     }
 
     public void requestConnect(String address) {
@@ -282,6 +298,7 @@
                             mWfdEnabling = false;
                             mWfdEnabled = true;
                             reportFeatureState();
+                            updateScanState();
                         }
                     }
 
@@ -318,6 +335,7 @@
             mWfdEnabling = false;
             mWfdEnabled = false;
             reportFeatureState();
+            updateScanState();
             disconnect();
         }
     }
@@ -340,12 +358,29 @@
                 WifiDisplayStatus.FEATURE_STATE_OFF;
     }
 
-    private void discoverPeers() {
-        if (!mDiscoverPeersInProgress) {
-            mDiscoverPeersInProgress = true;
-            mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;
-            handleScanStarted();
-            tryDiscoverPeers();
+    private void updateScanState() {
+        if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
+            if (!mDiscoverPeersInProgress) {
+                Slog.i(TAG, "Starting Wifi display scan.");
+                mDiscoverPeersInProgress = true;
+                handleScanStarted();
+                tryDiscoverPeers();
+            }
+        } else {
+            if (mDiscoverPeersInProgress) {
+                // Cancel automatic retry right away.
+                mHandler.removeCallbacks(mDiscoverPeers);
+
+                // Defer actually stopping discovery if we have a connection attempt in progress.
+                // The wifi display connection attempt often fails if we are not in discovery
+                // mode.  So we allow discovery to continue until we give up trying to connect.
+                if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) {
+                    Slog.i(TAG, "Stopping Wifi display scan.");
+                    mDiscoverPeersInProgress = false;
+                    stopPeerDiscovery();
+                    handleScanFinished();
+                }
+            }
         }
     }
 
@@ -357,8 +392,9 @@
                     Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
                 }
 
-                mDiscoverPeersInProgress = false;
-                requestPeers();
+                if (mDiscoverPeersInProgress) {
+                    requestPeers();
+                }
             }
 
             @Override
@@ -367,30 +403,28 @@
                     Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
                 }
 
-                if (mDiscoverPeersInProgress) {
-                    if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
-                        mHandler.postDelayed(new Runnable() {
-                            @Override
-                            public void run() {
-                                if (mDiscoverPeersInProgress) {
-                                    if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
-                                        mDiscoverPeersRetriesLeft -= 1;
-                                        if (DEBUG) {
-                                            Slog.d(TAG, "Retrying discovery.  Retries left: "
-                                                    + mDiscoverPeersRetriesLeft);
-                                        }
-                                        tryDiscoverPeers();
-                                    } else {
-                                        handleScanFinished();
-                                        mDiscoverPeersInProgress = false;
-                                    }
-                                }
-                            }
-                        }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);
-                    } else {
-                        handleScanFinished();
-                        mDiscoverPeersInProgress = false;
-                    }
+                // Ignore the error.
+                // We will retry automatically in a little bit.
+            }
+        });
+
+        // Retry discover peers periodically until stopped.
+        mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS);
+    }
+
+    private void stopPeerDiscovery() {
+        mWifiP2pManager.stopPeerDiscovery(mWifiP2pChannel, new ActionListener() {
+            @Override
+            public void onSuccess() {
+                if (DEBUG) {
+                    Slog.d(TAG, "Stop peer discovery succeeded.");
+                }
+            }
+
+            @Override
+            public void onFailure(int reason) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Stop peer discovery failed with reason " + reason + ".");
                 }
             }
         });
@@ -415,7 +449,9 @@
                     }
                 }
 
-                handleScanFinished();
+                if (mDiscoverPeersInProgress) {
+                    handleScanResults();
+                }
             }
         });
     }
@@ -429,7 +465,7 @@
         });
     }
 
-    private void handleScanFinished() {
+    private void handleScanResults() {
         final int count = mAvailableWifiDisplayPeers.size();
         final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
         for (int i = 0; i < count; i++) {
@@ -441,7 +477,16 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                mListener.onScanFinished(displays);
+                mListener.onScanResults(displays);
+            }
+        });
+    }
+
+    private void handleScanFinished() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onScanFinished();
             }
         });
     }
@@ -484,6 +529,12 @@
             return;
         }
 
+        if (!mWfdEnabled) {
+            Slog.i(TAG, "Ignoring request to connect to Wifi display because the "
+                    +" feature is currently disabled: " + device.deviceName);
+            return;
+        }
+
         mDesiredDevice = device;
         mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
         updateConnection();
@@ -508,6 +559,10 @@
      * connection is established (or not).
      */
     private void updateConnection() {
+        // Step 0. Stop scans if necessary to prevent interference while connected.
+        // Resume scans later when no longer attempting to connect.
+        updateScanState();
+
         // Step 1. Before we try to connect to a new device, tell the system we
         // have disconnected from the old one.
         if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
@@ -661,7 +716,7 @@
             return; // wait for asynchronous callback
         }
 
-        // Step 6. Listen for incoming connections.
+        // Step 6. Listen for incoming RTSP connection.
         if (mConnectedDevice != null && mRemoteDisplay == null) {
             Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
             if (addr == null) {
@@ -817,7 +872,11 @@
             }
         } else {
             mConnectedDeviceGroupInfo = null;
-            disconnect();
+
+            // Disconnect if we lost the network while connecting or connected to a display.
+            if (mConnectingDevice != null || mConnectedDevice != null) {
+                disconnect();
+            }
 
             // After disconnection for a group, for some reason we have a tendency
             // to get a peer change notification with an empty list of peers.
@@ -828,6 +887,13 @@
         }
     }
 
+    private final Runnable mDiscoverPeers = new Runnable() {
+        @Override
+        public void run() {
+            tryDiscoverPeers();
+        }
+    };
+
     private final Runnable mConnectionTimeout = new Runnable() {
         @Override
         public void run() {
@@ -1033,7 +1099,8 @@
         void onFeatureStateChanged(int featureState);
 
         void onScanStarted();
-        void onScanFinished(WifiDisplay[] availableDisplays);
+        void onScanResults(WifiDisplay[] availableDisplays);
+        void onScanFinished();
 
         void onDisplayConnecting(WifiDisplay display);
         void onDisplayConnectionFailed();
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index d749e6c..3145805 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -294,6 +294,7 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         filter.addDataScheme("package");
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
diff --git a/services/java/com/android/server/media/MediaRouterService.java b/services/java/com/android/server/media/MediaRouterService.java
new file mode 100644
index 0000000..a31695b
--- /dev/null
+++ b/services/java/com/android/server/media/MediaRouterService.java
@@ -0,0 +1,1423 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import com.android.internal.util.Objects;
+import com.android.server.Watchdog;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioSystem;
+import android.media.IMediaRouterClient;
+import android.media.IMediaRouterService;
+import android.media.MediaRouter;
+import android.media.MediaRouterClientState;
+import android.media.RemoteDisplayState;
+import android.media.RemoteDisplayState.RemoteDisplayInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides a mechanism for discovering media routes and manages media playback
+ * behalf of applications.
+ * <p>
+ * Currently supports discovering remote displays via remote display provider
+ * services that have been registered by applications.
+ * </p>
+ */
+public final class MediaRouterService extends IMediaRouterService.Stub
+        implements Watchdog.Monitor {
+    private static final String TAG = "MediaRouterService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    /**
+     * Timeout in milliseconds for a selected route to transition from a
+     * disconnected state to a connecting state.  If we don't observe any
+     * progress within this interval, then we will give up and unselect the route.
+     */
+    static final long CONNECTING_TIMEOUT = 5000;
+
+    /**
+     * Timeout in milliseconds for a selected route to transition from a
+     * connecting state to a connected state.  If we don't observe any
+     * progress within this interval, then we will give up and unselect the route.
+     */
+    static final long CONNECTED_TIMEOUT = 60000;
+
+    private final Context mContext;
+
+    // State guarded by mLock.
+    private final Object mLock = new Object();
+    private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
+    private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
+            new ArrayMap<IBinder, ClientRecord>();
+    private int mCurrentUserId = -1;
+
+    public MediaRouterService(Context context) {
+        mContext = context;
+        Watchdog.getInstance().addMonitor(this);
+    }
+
+    public void systemRunning() {
+        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
+                    switchUser();
+                }
+            }
+        }, filter);
+
+        switchUser();
+    }
+
+    @Override
+    public void monitor() {
+        synchronized (mLock) { /* check for deadlock */ }
+    }
+
+    // Binder call
+    @Override
+    public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final int uid = Binder.getCallingUid();
+        if (!validatePackageName(uid, packageName)) {
+            throw new SecurityException("packageName must match the calling uid");
+        }
+
+        final int pid = Binder.getCallingPid();
+        final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+                false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
+        final boolean trusted = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) ==
+                PackageManager.PERMISSION_GRANTED;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                registerClientLocked(client, pid, packageName, resolvedUserId, trusted);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void unregisterClient(IMediaRouterClient client) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                unregisterClientLocked(client, false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public MediaRouterClientState getState(IMediaRouterClient client) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                return getStateLocked(client);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void setDiscoveryRequest(IMediaRouterClient client,
+            int routeTypes, boolean activeScan) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                setDiscoveryRequestLocked(client, routeTypes, activeScan);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    // A null routeId means that the client wants to unselect its current route.
+    // The explicit flag indicates whether the change was explicitly requested by the
+    // user or the application which may cause changes to propagate out to the rest
+    // of the system.  Should be false when the change is in response to a new globally
+    // selected route or a default selection.
+    @Override
+    public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                setSelectedRouteLocked(client, routeId, explicit);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+        if (routeId == null) {
+            throw new IllegalArgumentException("routeId must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                requestSetVolumeLocked(client, routeId, volume);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+        if (routeId == null) {
+            throw new IllegalArgumentException("routeId must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                requestUpdateVolumeLocked(client, routeId, direction);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump MediaRouterService from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
+        pw.println();
+        pw.println("Global state");
+        pw.println("  mCurrentUserId=" + mCurrentUserId);
+
+        synchronized (mLock) {
+            final int count = mUserRecords.size();
+            for (int i = 0; i < count; i++) {
+                UserRecord userRecord = mUserRecords.valueAt(i);
+                pw.println();
+                userRecord.dump(pw, "");
+            }
+        }
+    }
+
+    void switchUser() {
+        synchronized (mLock) {
+            int userId = ActivityManager.getCurrentUser();
+            if (mCurrentUserId != userId) {
+                final int oldUserId = mCurrentUserId;
+                mCurrentUserId = userId; // do this first
+
+                UserRecord oldUser = mUserRecords.get(oldUserId);
+                if (oldUser != null) {
+                    oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
+                    disposeUserIfNeededLocked(oldUser); // since no longer current user
+                }
+
+                UserRecord newUser = mUserRecords.get(userId);
+                if (newUser != null) {
+                    newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+                }
+            }
+        }
+    }
+
+    void clientDied(ClientRecord clientRecord) {
+        synchronized (mLock) {
+            unregisterClientLocked(clientRecord.mClient, true);
+        }
+    }
+
+    private void registerClientLocked(IMediaRouterClient client,
+            int pid, String packageName, int userId, boolean trusted) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+        if (clientRecord == null) {
+            boolean newUser = false;
+            UserRecord userRecord = mUserRecords.get(userId);
+            if (userRecord == null) {
+                userRecord = new UserRecord(userId);
+                newUser = true;
+            }
+            clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted);
+            try {
+                binder.linkToDeath(clientRecord, 0);
+            } catch (RemoteException ex) {
+                throw new RuntimeException("Media router client died prematurely.", ex);
+            }
+
+            if (newUser) {
+                mUserRecords.put(userId, userRecord);
+                initializeUserLocked(userRecord);
+            }
+
+            userRecord.mClientRecords.add(clientRecord);
+            mAllClientRecords.put(binder, clientRecord);
+            initializeClientLocked(clientRecord);
+        }
+    }
+
+    private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
+        ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
+        if (clientRecord != null) {
+            UserRecord userRecord = clientRecord.mUserRecord;
+            userRecord.mClientRecords.remove(clientRecord);
+            disposeClientLocked(clientRecord, died);
+            disposeUserIfNeededLocked(userRecord); // since client removed from user
+        }
+    }
+
+    private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
+        ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+        if (clientRecord != null) {
+            return clientRecord.getState();
+        }
+        return null;
+    }
+
+    private void setDiscoveryRequestLocked(IMediaRouterClient client,
+            int routeTypes, boolean activeScan) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+        if (clientRecord != null) {
+            // Only let the system discover remote display routes for now.
+            if (!clientRecord.mTrusted) {
+                routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+            }
+
+            if (clientRecord.mRouteTypes != routeTypes
+                    || clientRecord.mActiveScan != activeScan) {
+                if (DEBUG) {
+                    Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
+                            + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
+                }
+                clientRecord.mRouteTypes = routeTypes;
+                clientRecord.mActiveScan = activeScan;
+                clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+                        UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+            }
+        }
+    }
+
+    private void setSelectedRouteLocked(IMediaRouterClient client,
+            String routeId, boolean explicit) {
+        ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+        if (clientRecord != null) {
+            final String oldRouteId = clientRecord.mSelectedRouteId;
+            if (!Objects.equal(routeId, oldRouteId)) {
+                if (DEBUG) {
+                    Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
+                            + ", oldRouteId=" + oldRouteId
+                            + ", explicit=" + explicit);
+                }
+
+                clientRecord.mSelectedRouteId = routeId;
+                if (explicit) {
+                    // Any app can disconnect from the globally selected route.
+                    if (oldRouteId != null) {
+                        clientRecord.mUserRecord.mHandler.obtainMessage(
+                                UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
+                    }
+                    // Only let the system connect to new global routes for now.
+                    // A similar check exists in the display manager for wifi display.
+                    if (routeId != null && clientRecord.mTrusted) {
+                        clientRecord.mUserRecord.mHandler.obtainMessage(
+                                UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
+                    }
+                }
+            }
+        }
+    }
+
+    private void requestSetVolumeLocked(IMediaRouterClient client,
+            String routeId, int volume) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+        if (clientRecord != null) {
+            clientRecord.mUserRecord.mHandler.obtainMessage(
+                    UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
+        }
+    }
+
+    private void requestUpdateVolumeLocked(IMediaRouterClient client,
+            String routeId, int direction) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+        if (clientRecord != null) {
+            clientRecord.mUserRecord.mHandler.obtainMessage(
+                    UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
+        }
+    }
+
+    private void initializeUserLocked(UserRecord userRecord) {
+        if (DEBUG) {
+            Slog.d(TAG, userRecord + ": Initialized");
+        }
+        if (userRecord.mUserId == mCurrentUserId) {
+            userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+        }
+    }
+
+    private void disposeUserIfNeededLocked(UserRecord userRecord) {
+        // If there are no records left and the user is no longer current then go ahead
+        // and purge the user record and all of its associated state.  If the user is current
+        // then leave it alone since we might be connected to a route or want to query
+        // the same route information again soon.
+        if (userRecord.mUserId != mCurrentUserId
+                && userRecord.mClientRecords.isEmpty()) {
+            if (DEBUG) {
+                Slog.d(TAG, userRecord + ": Disposed");
+            }
+            mUserRecords.remove(userRecord.mUserId);
+            // Note: User already stopped (by switchUser) so no need to send stop message here.
+        }
+    }
+
+    private void initializeClientLocked(ClientRecord clientRecord) {
+        if (DEBUG) {
+            Slog.d(TAG, clientRecord + ": Registered");
+        }
+    }
+
+    private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
+        if (DEBUG) {
+            if (died) {
+                Slog.d(TAG, clientRecord + ": Died!");
+            } else {
+                Slog.d(TAG, clientRecord + ": Unregistered");
+            }
+        }
+        if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
+            clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+                    UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+        }
+        clientRecord.dispose();
+    }
+
+    private boolean validatePackageName(int uid, String packageName) {
+        if (packageName != null) {
+            String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+            if (packageNames != null) {
+                for (String n : packageNames) {
+                    if (n.equals(packageName)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Information about a particular client of the media router.
+     * The contents of this object is guarded by mLock.
+     */
+    final class ClientRecord implements DeathRecipient {
+        public final UserRecord mUserRecord;
+        public final IMediaRouterClient mClient;
+        public final int mPid;
+        public final String mPackageName;
+        public final boolean mTrusted;
+
+        public int mRouteTypes;
+        public boolean mActiveScan;
+        public String mSelectedRouteId;
+
+        public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
+                int pid, String packageName, boolean trusted) {
+            mUserRecord = userRecord;
+            mClient = client;
+            mPid = pid;
+            mPackageName = packageName;
+            mTrusted = trusted;
+        }
+
+        public void dispose() {
+            mClient.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            clientDied(this);
+        }
+
+        MediaRouterClientState getState() {
+            return mTrusted ? mUserRecord.mTrustedState : mUserRecord.mUntrustedState;
+        }
+
+        public void dump(PrintWriter pw, String prefix) {
+            pw.println(prefix + this);
+
+            final String indent = prefix + "  ";
+            pw.println(indent + "mTrusted=" + mTrusted);
+            pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
+            pw.println(indent + "mActiveScan=" + mActiveScan);
+            pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
+        }
+
+        @Override
+        public String toString() {
+            return "Client " + mPackageName + " (pid " + mPid + ")";
+        }
+    }
+
+    /**
+     * Information about a particular user.
+     * The contents of this object is guarded by mLock.
+     */
+    final class UserRecord {
+        public final int mUserId;
+        public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>();
+        public final UserHandler mHandler;
+        public MediaRouterClientState mTrustedState;
+        public MediaRouterClientState mUntrustedState;
+
+        public UserRecord(int userId) {
+            mUserId = userId;
+            mHandler = new UserHandler(MediaRouterService.this, this);
+        }
+
+        public void dump(final PrintWriter pw, String prefix) {
+            pw.println(prefix + this);
+
+            final String indent = prefix + "  ";
+            final int clientCount = mClientRecords.size();
+            if (clientCount != 0) {
+                for (int i = 0; i < clientCount; i++) {
+                    mClientRecords.get(i).dump(pw, indent);
+                }
+            } else {
+                pw.println(indent + "<no clients>");
+            }
+
+            pw.println(indent + "State");
+            pw.println(indent + "mTrustedState=" + mTrustedState);
+            pw.println(indent + "mUntrustedState=" + mUntrustedState);
+
+            if (!mHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    mHandler.dump(pw, indent);
+                }
+            }, 1000)) {
+                pw.println(indent + "<could not dump handler state>");
+            }
+         }
+
+        @Override
+        public String toString() {
+            return "User " + mUserId;
+        }
+    }
+
+    /**
+     * Media router handler
+     * <p>
+     * Since remote display providers are designed to be single-threaded by nature,
+     * this class encapsulates all of the associated functionality and exports state
+     * to the service as it evolves.
+     * </p><p>
+     * One important task of this class is to keep track of the current globally selected
+     * route id for certain routes that have global effects, such as remote displays.
+     * Global route selections override local selections made within apps.  The change
+     * is propagated to all apps so that they are all in sync.  Synchronization works
+     * both ways.  Whenever the globally selected route is explicitly unselected by any
+     * app, then it becomes unselected globally and all apps are informed.
+     * </p><p>
+     * This class is currently hardcoded to work with remote display providers but
+     * it is intended to be eventually extended to support more general route providers
+     * similar to the support library media router.
+     * </p>
+     */
+    static final class UserHandler extends Handler
+            implements RemoteDisplayProviderWatcher.Callback,
+            RemoteDisplayProviderProxy.Callback {
+        public static final int MSG_START = 1;
+        public static final int MSG_STOP = 2;
+        public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
+        public static final int MSG_SELECT_ROUTE = 4;
+        public static final int MSG_UNSELECT_ROUTE = 5;
+        public static final int MSG_REQUEST_SET_VOLUME = 6;
+        public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
+        private static final int MSG_UPDATE_CLIENT_STATE = 8;
+        private static final int MSG_CONNECTION_TIMED_OUT = 9;
+
+        private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
+        private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
+        private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
+        private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
+
+        // The relative order of these constants is important and expresses progress
+        // through the process of connecting to a route.
+        private static final int PHASE_NOT_AVAILABLE = -1;
+        private static final int PHASE_NOT_CONNECTED = 0;
+        private static final int PHASE_CONNECTING = 1;
+        private static final int PHASE_CONNECTED = 2;
+
+        private final MediaRouterService mService;
+        private final UserRecord mUserRecord;
+        private final RemoteDisplayProviderWatcher mWatcher;
+        private final ArrayList<ProviderRecord> mProviderRecords =
+                new ArrayList<ProviderRecord>();
+        private final ArrayList<IMediaRouterClient> mTempClients =
+                new ArrayList<IMediaRouterClient>();
+
+        private boolean mRunning;
+        private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+        private RouteRecord mGloballySelectedRouteRecord;
+        private int mConnectionPhase = PHASE_NOT_AVAILABLE;
+        private int mConnectionTimeoutReason;
+        private long mConnectionTimeoutStartTime;
+        private boolean mClientStateUpdateScheduled;
+
+        public UserHandler(MediaRouterService service, UserRecord userRecord) {
+            super(Looper.getMainLooper(), null, true);
+            mService = service;
+            mUserRecord = userRecord;
+            mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
+                    this, mUserRecord.mUserId);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_START: {
+                    start();
+                    break;
+                }
+                case MSG_STOP: {
+                    stop();
+                    break;
+                }
+                case MSG_UPDATE_DISCOVERY_REQUEST: {
+                    updateDiscoveryRequest();
+                    break;
+                }
+                case MSG_SELECT_ROUTE: {
+                    selectRoute((String)msg.obj);
+                    break;
+                }
+                case MSG_UNSELECT_ROUTE: {
+                    unselectRoute((String)msg.obj);
+                    break;
+                }
+                case MSG_REQUEST_SET_VOLUME: {
+                    requestSetVolume((String)msg.obj, msg.arg1);
+                    break;
+                }
+                case MSG_REQUEST_UPDATE_VOLUME: {
+                    requestUpdateVolume((String)msg.obj, msg.arg1);
+                    break;
+                }
+                case MSG_UPDATE_CLIENT_STATE: {
+                    updateClientState();
+                    break;
+                }
+                case MSG_CONNECTION_TIMED_OUT: {
+                    connectionTimedOut();
+                    break;
+                }
+            }
+        }
+
+        public void dump(PrintWriter pw, String prefix) {
+            pw.println(prefix + "Handler");
+
+            final String indent = prefix + "  ";
+            pw.println(indent + "mRunning=" + mRunning);
+            pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
+            pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord);
+            pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
+            pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
+            pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
+                    TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
+
+            mWatcher.dump(pw, prefix);
+
+            final int providerCount = mProviderRecords.size();
+            if (providerCount != 0) {
+                for (int i = 0; i < providerCount; i++) {
+                    mProviderRecords.get(i).dump(pw, prefix);
+                }
+            } else {
+                pw.println(indent + "<no providers>");
+            }
+        }
+
+        private void start() {
+            if (!mRunning) {
+                mRunning = true;
+                mWatcher.start(); // also starts all providers
+            }
+        }
+
+        private void stop() {
+            if (mRunning) {
+                mRunning = false;
+                unselectGloballySelectedRoute();
+                mWatcher.stop(); // also stops all providers
+            }
+        }
+
+        private void updateDiscoveryRequest() {
+            int routeTypes = 0;
+            boolean activeScan = false;
+            synchronized (mService.mLock) {
+                final int count = mUserRecord.mClientRecords.size();
+                for (int i = 0; i < count; i++) {
+                    ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
+                    routeTypes |= clientRecord.mRouteTypes;
+                    activeScan |= clientRecord.mActiveScan;
+                }
+            }
+
+            final int newDiscoveryMode;
+            if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
+                if (activeScan) {
+                    newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
+                } else {
+                    newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
+                }
+            } else {
+                newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+            }
+
+            if (mDiscoveryMode != newDiscoveryMode) {
+                mDiscoveryMode = newDiscoveryMode;
+                final int count = mProviderRecords.size();
+                for (int i = 0; i < count; i++) {
+                    mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
+                }
+            }
+        }
+
+        private void selectRoute(String routeId) {
+            if (routeId != null
+                    && (mGloballySelectedRouteRecord == null
+                            || !routeId.equals(mGloballySelectedRouteRecord.getUniqueId()))) {
+                RouteRecord routeRecord = findRouteRecord(routeId);
+                if (routeRecord != null) {
+                    unselectGloballySelectedRoute();
+
+                    Slog.i(TAG, "Selected global route:" + routeRecord);
+                    mGloballySelectedRouteRecord = routeRecord;
+                    checkGloballySelectedRouteState();
+                    routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
+
+                    scheduleUpdateClientState();
+                }
+            }
+        }
+
+        private void unselectRoute(String routeId) {
+            if (routeId != null
+                    && mGloballySelectedRouteRecord != null
+                    && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+                unselectGloballySelectedRoute();
+            }
+        }
+
+        private void unselectGloballySelectedRoute() {
+            if (mGloballySelectedRouteRecord != null) {
+                Slog.i(TAG, "Unselected global route:" + mGloballySelectedRouteRecord);
+                mGloballySelectedRouteRecord.getProvider().setSelectedDisplay(null);
+                mGloballySelectedRouteRecord = null;
+                checkGloballySelectedRouteState();
+
+                scheduleUpdateClientState();
+            }
+        }
+
+        private void requestSetVolume(String routeId, int volume) {
+            if (mGloballySelectedRouteRecord != null
+                    && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+                mGloballySelectedRouteRecord.getProvider().setDisplayVolume(volume);
+            }
+        }
+
+        private void requestUpdateVolume(String routeId, int direction) {
+            if (mGloballySelectedRouteRecord != null
+                    && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+                mGloballySelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
+            }
+        }
+
+        @Override
+        public void addProvider(RemoteDisplayProviderProxy provider) {
+            provider.setCallback(this);
+            provider.setDiscoveryMode(mDiscoveryMode);
+            provider.setSelectedDisplay(null); // just to be safe
+
+            ProviderRecord providerRecord = new ProviderRecord(provider);
+            mProviderRecords.add(providerRecord);
+            providerRecord.updateDescriptor(provider.getDisplayState());
+
+            scheduleUpdateClientState();
+        }
+
+        @Override
+        public void removeProvider(RemoteDisplayProviderProxy provider) {
+            int index = findProviderRecord(provider);
+            if (index >= 0) {
+                ProviderRecord providerRecord = mProviderRecords.remove(index);
+                providerRecord.updateDescriptor(null); // mark routes invalid
+                provider.setCallback(null);
+                provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
+
+                checkGloballySelectedRouteState();
+                scheduleUpdateClientState();
+            }
+        }
+
+        @Override
+        public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
+                RemoteDisplayState state) {
+            updateProvider(provider, state);
+        }
+
+        private void updateProvider(RemoteDisplayProviderProxy provider,
+                RemoteDisplayState state) {
+            int index = findProviderRecord(provider);
+            if (index >= 0) {
+                ProviderRecord providerRecord = mProviderRecords.get(index);
+                if (providerRecord.updateDescriptor(state)) {
+                    checkGloballySelectedRouteState();
+                    scheduleUpdateClientState();
+                }
+            }
+        }
+
+        /**
+         * This function is called whenever the state of the globally selected route
+         * may have changed.  It checks the state and updates timeouts or unselects
+         * the route as appropriate.
+         */
+        private void checkGloballySelectedRouteState() {
+            // Unschedule timeouts when the route is unselected.
+            if (mGloballySelectedRouteRecord == null) {
+                mConnectionPhase = PHASE_NOT_AVAILABLE;
+                updateConnectionTimeout(0);
+                return;
+            }
+
+            // Ensure that the route is still present and enabled.
+            if (!mGloballySelectedRouteRecord.isValid()
+                    || !mGloballySelectedRouteRecord.isEnabled()) {
+                updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+                return;
+            }
+
+            // Make sure we haven't lost our connection.
+            final int oldPhase = mConnectionPhase;
+            mConnectionPhase = getConnectionPhase(mGloballySelectedRouteRecord.getStatus());
+            if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
+                updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
+                return;
+            }
+
+            // Check the route status.
+            switch (mConnectionPhase) {
+                case PHASE_CONNECTED:
+                    if (oldPhase != PHASE_CONNECTED) {
+                        Slog.i(TAG, "Connected to global route: "
+                                + mGloballySelectedRouteRecord);
+                    }
+                    updateConnectionTimeout(0);
+                    break;
+                case PHASE_CONNECTING:
+                    if (oldPhase != PHASE_CONNECTING) {
+                        Slog.i(TAG, "Connecting to global route: "
+                                + mGloballySelectedRouteRecord);
+                    }
+                    updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
+                    break;
+                case PHASE_NOT_CONNECTED:
+                    updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
+                    break;
+                case PHASE_NOT_AVAILABLE:
+                default:
+                    updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+                    break;
+            }
+        }
+
+        private void updateConnectionTimeout(int reason) {
+            if (reason != mConnectionTimeoutReason) {
+                if (mConnectionTimeoutReason != 0) {
+                    removeMessages(MSG_CONNECTION_TIMED_OUT);
+                }
+                mConnectionTimeoutReason = reason;
+                mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
+                switch (reason) {
+                    case TIMEOUT_REASON_NOT_AVAILABLE:
+                    case TIMEOUT_REASON_CONNECTION_LOST:
+                        // Route became unavailable or connection lost.
+                        // Unselect it immediately.
+                        sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
+                        break;
+                    case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+                        // Waiting for route to start connecting.
+                        sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
+                        break;
+                    case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+                        // Waiting for route to complete connection.
+                        sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
+                        break;
+                }
+            }
+        }
+
+        private void connectionTimedOut() {
+            if (mConnectionTimeoutReason == 0 || mGloballySelectedRouteRecord == null) {
+                // Shouldn't get here.  There must be a bug somewhere.
+                Log.wtf(TAG, "Handled connection timeout for no reason.");
+                return;
+            }
+
+            switch (mConnectionTimeoutReason) {
+                case TIMEOUT_REASON_NOT_AVAILABLE:
+                    Slog.i(TAG, "Global route no longer available: "
+                            + mGloballySelectedRouteRecord);
+                    break;
+                case TIMEOUT_REASON_CONNECTION_LOST:
+                    Slog.i(TAG, "Global route connection lost: "
+                            + mGloballySelectedRouteRecord);
+                    break;
+                case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+                    Slog.i(TAG, "Global route timed out while waiting for "
+                            + "connection attempt to begin after "
+                            + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+                            + " ms: " + mGloballySelectedRouteRecord);
+                    break;
+                case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+                    Slog.i(TAG, "Global route timed out while connecting after "
+                            + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+                            + " ms: " + mGloballySelectedRouteRecord);
+                    break;
+            }
+            mConnectionTimeoutReason = 0;
+
+            unselectGloballySelectedRoute();
+        }
+
+        private void scheduleUpdateClientState() {
+            if (!mClientStateUpdateScheduled) {
+                mClientStateUpdateScheduled = true;
+                sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
+            }
+        }
+
+        private void updateClientState() {
+            mClientStateUpdateScheduled = false;
+
+            final String globallySelectedRouteId = mGloballySelectedRouteRecord != null ?
+                    mGloballySelectedRouteRecord.getUniqueId() : null;
+
+            // Build a new client state for trusted clients.
+            MediaRouterClientState trustedState = new MediaRouterClientState();
+            trustedState.globallySelectedRouteId = globallySelectedRouteId;
+            final int providerCount = mProviderRecords.size();
+            for (int i = 0; i < providerCount; i++) {
+                mProviderRecords.get(i).appendClientState(trustedState);
+            }
+
+            // Build a new client state for untrusted clients that can only see
+            // the currently selected route.
+            MediaRouterClientState untrustedState = new MediaRouterClientState();
+            untrustedState.globallySelectedRouteId = globallySelectedRouteId;
+            if (globallySelectedRouteId != null) {
+                untrustedState.routes.add(trustedState.getRoute(globallySelectedRouteId));
+            }
+
+            try {
+                synchronized (mService.mLock) {
+                    // Update the UserRecord.
+                    mUserRecord.mTrustedState = trustedState;
+                    mUserRecord.mUntrustedState = untrustedState;
+
+                    // Collect all clients.
+                    final int count = mUserRecord.mClientRecords.size();
+                    for (int i = 0; i < count; i++) {
+                        mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
+                    }
+                }
+
+                // Notify all clients (outside of the lock).
+                final int count = mTempClients.size();
+                for (int i = 0; i < count; i++) {
+                    try {
+                        mTempClients.get(i).onStateChanged();
+                    } catch (RemoteException ex) {
+                        // ignore errors, client probably died
+                    }
+                }
+            } finally {
+                // Clear the list in preparation for the next time.
+                mTempClients.clear();
+            }
+        }
+
+        private int findProviderRecord(RemoteDisplayProviderProxy provider) {
+            final int count = mProviderRecords.size();
+            for (int i = 0; i < count; i++) {
+                ProviderRecord record = mProviderRecords.get(i);
+                if (record.getProvider() == provider) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        private RouteRecord findRouteRecord(String uniqueId) {
+            final int count = mProviderRecords.size();
+            for (int i = 0; i < count; i++) {
+                RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
+                if (record != null) {
+                    return record;
+                }
+            }
+            return null;
+        }
+
+        private static int getConnectionPhase(int status) {
+            switch (status) {
+                case MediaRouter.RouteInfo.STATUS_NONE:
+                case MediaRouter.RouteInfo.STATUS_CONNECTED:
+                    return PHASE_CONNECTED;
+                case MediaRouter.RouteInfo.STATUS_CONNECTING:
+                    return PHASE_CONNECTING;
+                case MediaRouter.RouteInfo.STATUS_SCANNING:
+                case MediaRouter.RouteInfo.STATUS_AVAILABLE:
+                    return PHASE_NOT_CONNECTED;
+                case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
+                case MediaRouter.RouteInfo.STATUS_IN_USE:
+                default:
+                    return PHASE_NOT_AVAILABLE;
+            }
+        }
+
+        static final class ProviderRecord {
+            private final RemoteDisplayProviderProxy mProvider;
+            private final String mUniquePrefix;
+            private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
+            private RemoteDisplayState mDescriptor;
+
+            public ProviderRecord(RemoteDisplayProviderProxy provider) {
+                mProvider = provider;
+                mUniquePrefix = provider.getFlattenedComponentName() + ":";
+            }
+
+            public RemoteDisplayProviderProxy getProvider() {
+                return mProvider;
+            }
+
+            public String getUniquePrefix() {
+                return mUniquePrefix;
+            }
+
+            public boolean updateDescriptor(RemoteDisplayState descriptor) {
+                boolean changed = false;
+                if (mDescriptor != descriptor) {
+                    mDescriptor = descriptor;
+
+                    // Update all existing routes and reorder them to match
+                    // the order of their descriptors.
+                    int targetIndex = 0;
+                    if (descriptor != null) {
+                        if (descriptor.isValid()) {
+                            final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
+                            final int routeCount = routeDescriptors.size();
+                            for (int i = 0; i < routeCount; i++) {
+                                final RemoteDisplayInfo routeDescriptor =
+                                        routeDescriptors.get(i);
+                                final String descriptorId = routeDescriptor.id;
+                                final int sourceIndex = findRouteByDescriptorId(descriptorId);
+                                if (sourceIndex < 0) {
+                                    // Add the route to the provider.
+                                    String uniqueId = assignRouteUniqueId(descriptorId);
+                                    RouteRecord route =
+                                            new RouteRecord(this, descriptorId, uniqueId);
+                                    mRoutes.add(targetIndex++, route);
+                                    route.updateDescriptor(routeDescriptor);
+                                    changed = true;
+                                } else if (sourceIndex < targetIndex) {
+                                    // Ignore route with duplicate id.
+                                    Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
+                                            + routeDescriptor);
+                                } else {
+                                    // Reorder existing route within the list.
+                                    RouteRecord route = mRoutes.get(sourceIndex);
+                                    Collections.swap(mRoutes, sourceIndex, targetIndex++);
+                                    changed |= route.updateDescriptor(routeDescriptor);
+                                }
+                            }
+                        } else {
+                            Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
+                                    + mProvider.getFlattenedComponentName());
+                        }
+                    }
+
+                    // Dispose all remaining routes that do not have matching descriptors.
+                    for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
+                        RouteRecord route = mRoutes.remove(i);
+                        route.updateDescriptor(null); // mark route invalid
+                        changed = true;
+                    }
+                }
+                return changed;
+            }
+
+            public void appendClientState(MediaRouterClientState state) {
+                final int routeCount = mRoutes.size();
+                for (int i = 0; i < routeCount; i++) {
+                    state.routes.add(mRoutes.get(i).getInfo());
+                }
+            }
+
+            public RouteRecord findRouteByUniqueId(String uniqueId) {
+                final int routeCount = mRoutes.size();
+                for (int i = 0; i < routeCount; i++) {
+                    RouteRecord route = mRoutes.get(i);
+                    if (route.getUniqueId().equals(uniqueId)) {
+                        return route;
+                    }
+                }
+                return null;
+            }
+
+            private int findRouteByDescriptorId(String descriptorId) {
+                final int routeCount = mRoutes.size();
+                for (int i = 0; i < routeCount; i++) {
+                    RouteRecord route = mRoutes.get(i);
+                    if (route.getDescriptorId().equals(descriptorId)) {
+                        return i;
+                    }
+                }
+                return -1;
+            }
+
+            public void dump(PrintWriter pw, String prefix) {
+                pw.println(prefix + this);
+
+                final String indent = prefix + "  ";
+                mProvider.dump(pw, indent);
+
+                final int routeCount = mRoutes.size();
+                if (routeCount != 0) {
+                    for (int i = 0; i < routeCount; i++) {
+                        mRoutes.get(i).dump(pw, indent);
+                    }
+                } else {
+                    pw.println(indent + "<no routes>");
+                }
+            }
+
+            @Override
+            public String toString() {
+                return "Provider " + mProvider.getFlattenedComponentName();
+            }
+
+            private String assignRouteUniqueId(String descriptorId) {
+                return mUniquePrefix + descriptorId;
+            }
+        }
+
+        static final class RouteRecord {
+            private final ProviderRecord mProviderRecord;
+            private final String mDescriptorId;
+            private final MediaRouterClientState.RouteInfo mMutableInfo;
+            private MediaRouterClientState.RouteInfo mImmutableInfo;
+            private RemoteDisplayInfo mDescriptor;
+
+            public RouteRecord(ProviderRecord providerRecord,
+                    String descriptorId, String uniqueId) {
+                mProviderRecord = providerRecord;
+                mDescriptorId = descriptorId;
+                mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
+            }
+
+            public RemoteDisplayProviderProxy getProvider() {
+                return mProviderRecord.getProvider();
+            }
+
+            public ProviderRecord getProviderRecord() {
+                return mProviderRecord;
+            }
+
+            public String getDescriptorId() {
+                return mDescriptorId;
+            }
+
+            public String getUniqueId() {
+                return mMutableInfo.id;
+            }
+
+            public MediaRouterClientState.RouteInfo getInfo() {
+                if (mImmutableInfo == null) {
+                    mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
+                }
+                return mImmutableInfo;
+            }
+
+            public boolean isValid() {
+                return mDescriptor != null;
+            }
+
+            public boolean isEnabled() {
+                return mMutableInfo.enabled;
+            }
+
+            public int getStatus() {
+                return mMutableInfo.statusCode;
+            }
+
+            public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
+                boolean changed = false;
+                if (mDescriptor != descriptor) {
+                    mDescriptor = descriptor;
+                    if (descriptor != null) {
+                        final String name = computeName(descriptor);
+                        if (!Objects.equal(mMutableInfo.name, name)) {
+                            mMutableInfo.name = name;
+                            changed = true;
+                        }
+                        final String description = computeDescription(descriptor);
+                        if (!Objects.equal(mMutableInfo.description, description)) {
+                            mMutableInfo.description = description;
+                            changed = true;
+                        }
+                        final int supportedTypes = computeSupportedTypes(descriptor);
+                        if (mMutableInfo.supportedTypes != supportedTypes) {
+                            mMutableInfo.supportedTypes = supportedTypes;
+                            changed = true;
+                        }
+                        final boolean enabled = computeEnabled(descriptor);
+                        if (mMutableInfo.enabled != enabled) {
+                            mMutableInfo.enabled = enabled;
+                            changed = true;
+                        }
+                        final int statusCode = computeStatusCode(descriptor);
+                        if (mMutableInfo.statusCode != statusCode) {
+                            mMutableInfo.statusCode = statusCode;
+                            changed = true;
+                        }
+                        final int playbackType = computePlaybackType(descriptor);
+                        if (mMutableInfo.playbackType != playbackType) {
+                            mMutableInfo.playbackType = playbackType;
+                            changed = true;
+                        }
+                        final int playbackStream = computePlaybackStream(descriptor);
+                        if (mMutableInfo.playbackStream != playbackStream) {
+                            mMutableInfo.playbackStream = playbackStream;
+                            changed = true;
+                        }
+                        final int volume = computeVolume(descriptor);
+                        if (mMutableInfo.volume != volume) {
+                            mMutableInfo.volume = volume;
+                            changed = true;
+                        }
+                        final int volumeMax = computeVolumeMax(descriptor);
+                        if (mMutableInfo.volumeMax != volumeMax) {
+                            mMutableInfo.volumeMax = volumeMax;
+                            changed = true;
+                        }
+                        final int volumeHandling = computeVolumeHandling(descriptor);
+                        if (mMutableInfo.volumeHandling != volumeHandling) {
+                            mMutableInfo.volumeHandling = volumeHandling;
+                            changed = true;
+                        }
+                        final int presentationDisplayId = computePresentationDisplayId(descriptor);
+                        if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
+                            mMutableInfo.presentationDisplayId = presentationDisplayId;
+                            changed = true;
+                        }
+                    }
+                }
+                if (changed) {
+                    mImmutableInfo = null;
+                }
+                return changed;
+            }
+
+            public void dump(PrintWriter pw, String prefix) {
+                pw.println(prefix + this);
+
+                final String indent = prefix + "  ";
+                pw.println(indent + "mMutableInfo=" + mMutableInfo);
+                pw.println(indent + "mDescriptorId=" + mDescriptorId);
+                pw.println(indent + "mDescriptor=" + mDescriptor);
+            }
+
+            @Override
+            public String toString() {
+                return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
+            }
+
+            private static String computeName(RemoteDisplayInfo descriptor) {
+                // Note that isValid() already ensures the name is non-empty.
+                return descriptor.name;
+            }
+
+            private static String computeDescription(RemoteDisplayInfo descriptor) {
+                final String description = descriptor.description;
+                return TextUtils.isEmpty(description) ? null : description;
+            }
+
+            private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
+                return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
+                        | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
+                        | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+            }
+
+            private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
+                switch (descriptor.status) {
+                    case RemoteDisplayInfo.STATUS_CONNECTED:
+                    case RemoteDisplayInfo.STATUS_CONNECTING:
+                    case RemoteDisplayInfo.STATUS_AVAILABLE:
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+
+            private static int computeStatusCode(RemoteDisplayInfo descriptor) {
+                switch (descriptor.status) {
+                    case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
+                        return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
+                    case RemoteDisplayInfo.STATUS_AVAILABLE:
+                        return MediaRouter.RouteInfo.STATUS_AVAILABLE;
+                    case RemoteDisplayInfo.STATUS_IN_USE:
+                        return MediaRouter.RouteInfo.STATUS_IN_USE;
+                    case RemoteDisplayInfo.STATUS_CONNECTING:
+                        return MediaRouter.RouteInfo.STATUS_CONNECTING;
+                    case RemoteDisplayInfo.STATUS_CONNECTED:
+                        return MediaRouter.RouteInfo.STATUS_CONNECTED;
+                    default:
+                        return MediaRouter.RouteInfo.STATUS_NONE;
+                }
+            }
+
+            private static int computePlaybackType(RemoteDisplayInfo descriptor) {
+                return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+            }
+
+            private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
+                return AudioSystem.STREAM_MUSIC;
+            }
+
+            private static int computeVolume(RemoteDisplayInfo descriptor) {
+                final int volume = descriptor.volume;
+                final int volumeMax = descriptor.volumeMax;
+                if (volume < 0) {
+                    return 0;
+                } else if (volume > volumeMax) {
+                    return volumeMax;
+                }
+                return volume;
+            }
+
+            private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
+                final int volumeMax = descriptor.volumeMax;
+                return volumeMax > 0 ? volumeMax : 0;
+            }
+
+            private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
+                final int volumeHandling = descriptor.volumeHandling;
+                switch (volumeHandling) {
+                    case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
+                        return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+                    case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
+                    default:
+                        return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+                }
+            }
+
+            private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
+                // The MediaRouter class validates that the id corresponds to an extant
+                // presentation display.  So all we do here is canonicalize the null case.
+                final int displayId = descriptor.presentationDisplayId;
+                return displayId < 0 ? -1 : displayId;
+            }
+        }
+    }
+}
diff --git a/services/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java
new file mode 100644
index 0000000..b248ee0
--- /dev/null
+++ b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import com.android.internal.util.Objects;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.IRemoteDisplayCallback;
+import android.media.IRemoteDisplayProvider;
+import android.media.RemoteDisplayState;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.IBinder.DeathRecipient;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
+/**
+ * Maintains a connection to a particular remote display provider service.
+ */
+final class RemoteDisplayProviderProxy implements ServiceConnection {
+    private static final String TAG = "RemoteDisplayProvider";  // max. 23 chars
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Context mContext;
+    private final ComponentName mComponentName;
+    private final int mUserId;
+    private final Handler mHandler;
+
+    private Callback mDisplayStateCallback;
+
+    // Connection state
+    private boolean mRunning;
+    private boolean mBound;
+    private Connection mActiveConnection;
+    private boolean mConnectionReady;
+
+    // Logical state
+    private int mDiscoveryMode;
+    private String mSelectedDisplayId;
+    private RemoteDisplayState mDisplayState;
+    private boolean mScheduledDisplayStateChangedCallback;
+
+    public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
+            int userId) {
+        mContext = context;
+        mComponentName = componentName;
+        mUserId = userId;
+        mHandler = new Handler();
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "Proxy");
+        pw.println(prefix + "  mUserId=" + mUserId);
+        pw.println(prefix + "  mRunning=" + mRunning);
+        pw.println(prefix + "  mBound=" + mBound);
+        pw.println(prefix + "  mActiveConnection=" + mActiveConnection);
+        pw.println(prefix + "  mConnectionReady=" + mConnectionReady);
+        pw.println(prefix + "  mDiscoveryMode=" + mDiscoveryMode);
+        pw.println(prefix + "  mSelectedDisplayId=" + mSelectedDisplayId);
+        pw.println(prefix + "  mDisplayState=" + mDisplayState);
+    }
+
+    public void setCallback(Callback callback) {
+        mDisplayStateCallback = callback;
+    }
+
+    public RemoteDisplayState getDisplayState() {
+        return mDisplayState;
+    }
+
+    public void setDiscoveryMode(int mode) {
+        if (mDiscoveryMode != mode) {
+            mDiscoveryMode = mode;
+            if (mConnectionReady) {
+                mActiveConnection.setDiscoveryMode(mode);
+            }
+            updateBinding();
+        }
+    }
+
+    public void setSelectedDisplay(String id) {
+        if (!Objects.equal(mSelectedDisplayId, id)) {
+            if (mConnectionReady && mSelectedDisplayId != null) {
+                mActiveConnection.disconnect(mSelectedDisplayId);
+            }
+            mSelectedDisplayId = id;
+            if (mConnectionReady && id != null) {
+                mActiveConnection.connect(id);
+            }
+            updateBinding();
+        }
+    }
+
+    public void setDisplayVolume(int volume) {
+        if (mConnectionReady && mSelectedDisplayId != null) {
+            mActiveConnection.setVolume(mSelectedDisplayId, volume);
+        }
+    }
+
+    public void adjustDisplayVolume(int delta) {
+        if (mConnectionReady && mSelectedDisplayId != null) {
+            mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
+        }
+    }
+
+    public boolean hasComponentName(String packageName, String className) {
+        return mComponentName.getPackageName().equals(packageName)
+                && mComponentName.getClassName().equals(className);
+    }
+
+    public String getFlattenedComponentName() {
+        return mComponentName.flattenToShortString();
+    }
+
+    public void start() {
+        if (!mRunning) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Starting");
+            }
+
+            mRunning = true;
+            updateBinding();
+        }
+    }
+
+    public void stop() {
+        if (mRunning) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Stopping");
+            }
+
+            mRunning = false;
+            updateBinding();
+        }
+    }
+
+    public void rebindIfDisconnected() {
+        if (mActiveConnection == null && shouldBind()) {
+            unbind();
+            bind();
+        }
+    }
+
+    private void updateBinding() {
+        if (shouldBind()) {
+            bind();
+        } else {
+            unbind();
+        }
+    }
+
+    private boolean shouldBind() {
+        if (mRunning) {
+            // Bind whenever there is a discovery request or selected display.
+            if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE
+                    || mSelectedDisplayId != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void bind() {
+        if (!mBound) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Binding");
+            }
+
+            Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
+            service.setComponent(mComponentName);
+            try {
+                mBound = mContext.bindServiceAsUser(service, this, Context.BIND_AUTO_CREATE,
+                        new UserHandle(mUserId));
+                if (!mBound && DEBUG) {
+                    Slog.d(TAG, this + ": Bind failed");
+                }
+            } catch (SecurityException ex) {
+                if (DEBUG) {
+                    Slog.d(TAG, this + ": Bind failed", ex);
+                }
+            }
+        }
+    }
+
+    private void unbind() {
+        if (mBound) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Unbinding");
+            }
+
+            mBound = false;
+            disconnect();
+            mContext.unbindService(this);
+        }
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        if (DEBUG) {
+            Slog.d(TAG, this + ": Connected");
+        }
+
+        if (mBound) {
+            disconnect();
+
+            IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service);
+            if (provider != null) {
+                Connection connection = new Connection(provider);
+                if (connection.register()) {
+                    mActiveConnection = connection;
+                } else {
+                    if (DEBUG) {
+                        Slog.d(TAG, this + ": Registration failed");
+                    }
+                }
+            } else {
+                Slog.e(TAG, this + ": Service returned invalid remote display provider binder");
+            }
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        if (DEBUG) {
+            Slog.d(TAG, this + ": Service disconnected");
+        }
+        disconnect();
+    }
+
+    private void onConnectionReady(Connection connection) {
+        if (mActiveConnection == connection) {
+            mConnectionReady = true;
+
+            if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) {
+                mActiveConnection.setDiscoveryMode(mDiscoveryMode);
+            }
+            if (mSelectedDisplayId != null) {
+                mActiveConnection.connect(mSelectedDisplayId);
+            }
+        }
+    }
+
+    private void onConnectionDied(Connection connection) {
+        if (mActiveConnection == connection) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Service connection died");
+            }
+            disconnect();
+        }
+    }
+
+    private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) {
+        if (mActiveConnection == connection) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": State changed, state=" + state);
+            }
+            setDisplayState(state);
+        }
+    }
+
+    private void disconnect() {
+        if (mActiveConnection != null) {
+            if (mSelectedDisplayId != null) {
+                mActiveConnection.disconnect(mSelectedDisplayId);
+            }
+            mConnectionReady = false;
+            mActiveConnection.dispose();
+            mActiveConnection = null;
+            setDisplayState(null);
+        }
+    }
+
+    private void setDisplayState(RemoteDisplayState state) {
+        if (!Objects.equal(mDisplayState, state)) {
+            mDisplayState = state;
+            if (!mScheduledDisplayStateChangedCallback) {
+                mScheduledDisplayStateChangedCallback = true;
+                mHandler.post(mDisplayStateChanged);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Service connection " + mComponentName.flattenToShortString();
+    }
+
+    private final Runnable mDisplayStateChanged = new Runnable() {
+        @Override
+        public void run() {
+            mScheduledDisplayStateChangedCallback = false;
+            if (mDisplayStateCallback != null) {
+                mDisplayStateCallback.onDisplayStateChanged(
+                        RemoteDisplayProviderProxy.this, mDisplayState);
+            }
+        }
+    };
+
+    public interface Callback {
+        void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
+    }
+
+    private final class Connection implements DeathRecipient {
+        private final IRemoteDisplayProvider mProvider;
+        private final ProviderCallback mCallback;
+
+        public Connection(IRemoteDisplayProvider provider) {
+            mProvider = provider;
+            mCallback = new ProviderCallback(this);
+        }
+
+        public boolean register() {
+            try {
+                mProvider.asBinder().linkToDeath(this, 0);
+                mProvider.setCallback(mCallback);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        onConnectionReady(Connection.this);
+                    }
+                });
+                return true;
+            } catch (RemoteException ex) {
+                binderDied();
+            }
+            return false;
+        }
+
+        public void dispose() {
+            mProvider.asBinder().unlinkToDeath(this, 0);
+            mCallback.dispose();
+        }
+
+        public void setDiscoveryMode(int mode) {
+            try {
+                mProvider.setDiscoveryMode(mode);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
+            }
+        }
+
+        public void connect(String id) {
+            try {
+                mProvider.connect(id);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to connect to display.", ex);
+            }
+        }
+
+        public void disconnect(String id) {
+            try {
+                mProvider.disconnect(id);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex);
+            }
+        }
+
+        public void setVolume(String id, int volume) {
+            try {
+                mProvider.setVolume(id, volume);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to set display volume.", ex);
+            }
+        }
+
+        public void adjustVolume(String id, int volume) {
+            try {
+                mProvider.adjustVolume(id, volume);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    onConnectionDied(Connection.this);
+                }
+            });
+        }
+
+        void postStateChanged(final RemoteDisplayState state) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    onDisplayStateChanged(Connection.this, state);
+                }
+            });
+        }
+    }
+
+    /**
+     * Receives callbacks from the service.
+     * <p>
+     * This inner class is static and only retains a weak reference to the connection
+     * to prevent the client from being leaked in case the service is holding an
+     * active reference to the client's callback.
+     * </p>
+     */
+    private static final class ProviderCallback extends IRemoteDisplayCallback.Stub {
+        private final WeakReference<Connection> mConnectionRef;
+
+        public ProviderCallback(Connection connection) {
+            mConnectionRef = new WeakReference<Connection>(connection);
+        }
+
+        public void dispose() {
+            mConnectionRef.clear();
+        }
+
+        @Override
+        public void onStateChanged(RemoteDisplayState state) throws RemoteException {
+            Connection connection = mConnectionRef.get();
+            if (connection != null) {
+                connection.postStateChanged(state);
+            }
+        }
+    }
+}
diff --git a/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java
new file mode 100644
index 0000000..6a5f563
--- /dev/null
+++ b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.media.RemoteDisplayState;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Watches for remote display provider services to be installed.
+ * Adds a provider to the media router for each registered service.
+ *
+ * @see RemoteDisplayProviderProxy
+ */
+public final class RemoteDisplayProviderWatcher {
+    private static final String TAG = "RemoteDisplayProvider";  // max. 23 chars
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Context mContext;
+    private final Callback mCallback;
+    private final Handler mHandler;
+    private final int mUserId;
+    private final PackageManager mPackageManager;
+
+    private final ArrayList<RemoteDisplayProviderProxy> mProviders =
+            new ArrayList<RemoteDisplayProviderProxy>();
+    private boolean mRunning;
+
+    public RemoteDisplayProviderWatcher(Context context,
+            Callback callback, Handler handler, int userId) {
+        mContext = context;
+        mCallback = callback;
+        mHandler = handler;
+        mUserId = userId;
+        mPackageManager = context.getPackageManager();
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "Watcher");
+        pw.println(prefix + "  mUserId=" + mUserId);
+        pw.println(prefix + "  mRunning=" + mRunning);
+        pw.println(prefix + "  mProviders.size()=" + mProviders.size());
+    }
+
+    public void start() {
+        if (!mRunning) {
+            mRunning = true;
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+            filter.addDataScheme("package");
+            mContext.registerReceiverAsUser(mScanPackagesReceiver,
+                    new UserHandle(mUserId), filter, null, mHandler);
+
+            // Scan packages.
+            // Also has the side-effect of restarting providers if needed.
+            mHandler.post(mScanPackagesRunnable);
+        }
+    }
+
+    public void stop() {
+        if (mRunning) {
+            mRunning = false;
+
+            mContext.unregisterReceiver(mScanPackagesReceiver);
+            mHandler.removeCallbacks(mScanPackagesRunnable);
+
+            // Stop all providers.
+            for (int i = mProviders.size() - 1; i >= 0; i--) {
+                mProviders.get(i).stop();
+            }
+        }
+    }
+
+    private void scanPackages() {
+        if (!mRunning) {
+            return;
+        }
+
+        // Add providers for all new services.
+        // Reorder the list so that providers left at the end will be the ones to remove.
+        int targetIndex = 0;
+        Intent intent = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
+        for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
+                intent, 0, mUserId)) {
+            ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+            if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) {
+                int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
+                if (sourceIndex < 0) {
+                    RemoteDisplayProviderProxy provider =
+                            new RemoteDisplayProviderProxy(mContext,
+                            new ComponentName(serviceInfo.packageName, serviceInfo.name),
+                            mUserId);
+                    provider.start();
+                    mProviders.add(targetIndex++, provider);
+                    mCallback.addProvider(provider);
+                } else if (sourceIndex >= targetIndex) {
+                    RemoteDisplayProviderProxy provider = mProviders.get(sourceIndex);
+                    provider.start(); // restart the provider if needed
+                    provider.rebindIfDisconnected();
+                    Collections.swap(mProviders, sourceIndex, targetIndex++);
+                }
+            }
+        }
+
+        // Remove providers for missing services.
+        if (targetIndex < mProviders.size()) {
+            for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
+                RemoteDisplayProviderProxy provider = mProviders.get(i);
+                mCallback.removeProvider(provider);
+                mProviders.remove(provider);
+                provider.stop();
+            }
+        }
+    }
+
+    private boolean verifyServiceTrusted(ServiceInfo serviceInfo) {
+        if (serviceInfo.permission == null || !serviceInfo.permission.equals(
+                Manifest.permission.BIND_REMOTE_DISPLAY)) {
+            // If the service does not require this permission then any app could
+            // potentially bind to it and cause the remote display service to
+            // misbehave.  So we only want to trust providers that require the
+            // correct permissions.
+            Slog.w(TAG, "Ignoring remote display provider service because it did not "
+                    + "require the BIND_REMOTE_DISPLAY permission in its manifest: "
+                    + serviceInfo.packageName + "/" + serviceInfo.name);
+            return false;
+        }
+        if (!hasCaptureVideoPermission(serviceInfo.packageName)) {
+            // If the service does not have permission to capture video then it
+            // isn't going to be terribly useful as a remote display, is it?
+            // Kind of makes you wonder what it's doing there in the first place.
+            Slog.w(TAG, "Ignoring remote display provider service because it does not "
+                    + "have the CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT "
+                    + "permission: " + serviceInfo.packageName + "/" + serviceInfo.name);
+            return false;
+        }
+        // Looks good.
+        return true;
+    }
+
+    private boolean hasCaptureVideoPermission(String packageName) {
+        if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT,
+                packageName) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT,
+                packageName) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        return false;
+    }
+
+    private int findProvider(String packageName, String className) {
+        int count = mProviders.size();
+        for (int i = 0; i < count; i++) {
+            RemoteDisplayProviderProxy provider = mProviders.get(i);
+            if (provider.hasComponentName(packageName, className)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Slog.d(TAG, "Received package manager broadcast: " + intent);
+            }
+            scanPackages();
+        }
+    };
+
+    private final Runnable mScanPackagesRunnable = new Runnable() {
+        @Override
+        public void run() {
+            scanPackages();
+        }
+    };
+
+    public interface Callback {
+        void addProvider(RemoteDisplayProviderProxy provider);
+        void removeProvider(RemoteDisplayProviderProxy provider);
+    }
+}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 3d6b3c9..dc63ca2 100755
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -217,6 +217,7 @@
     static final int SCAN_UPDATE_TIME = 1<<6;
     static final int SCAN_DEFER_DEX = 1<<7;
     static final int SCAN_BOOTING = 1<<8;
+    static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<9;
 
     static final int REMOVE_CHATTY = 1<<16;
 
@@ -332,7 +333,6 @@
             new HashMap<String, PackageParser.Package>();
 
     // Information for the parser to write more useful error messages.
-    File mScanningPath;
     int mLastScanError;
 
     // ----------------------------------------------------------------
@@ -867,7 +867,7 @@
                                     int[] uidArray = new int[] { res.pkg.applicationInfo.uid };
                                     ArrayList<String> pkgList = new ArrayList<String>(1);
                                     pkgList.add(res.pkg.applicationInfo.packageName);
-                                    sendResourcesChangedBroadcast(true, false,
+                                    sendResourcesChangedBroadcast(true, true,
                                             pkgList,uidArray, null);
                                 }
                             }
@@ -1278,7 +1278,8 @@
                 frameworkDir.getPath(), OBSERVER_EVENTS, true, false);
             mFrameworkInstallObserver.startWatching();
             scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
+                    | PackageParser.PARSE_IS_SYSTEM_DIR
+                    | PackageParser.PARSE_IS_PRIVILEGED,
                     scanMode | SCAN_NO_DEX, 0);
 
             // Collected privileged system packages.
@@ -1771,6 +1772,24 @@
                 state, userId);
     }
 
+    public boolean isPackageAvailable(String packageName, int userId) {
+        if (!sUserManager.exists(userId)) return false;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "is package available");
+        synchronized (mPackages) {
+            PackageParser.Package p = mPackages.get(packageName);
+            if (p != null) {
+                final PackageSetting ps = (PackageSetting) p.mExtras;
+                if (ps != null) {
+                    final PackageUserState state = ps.readUserState(userId);
+                    if (state != null) {
+                        return PackageParser.isAvailable(state);
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
     @Override
     public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
         if (!sUserManager.exists(userId)) return null;
@@ -3638,6 +3657,12 @@
             // An updated system app will not have the PARSE_IS_SYSTEM flag set
             // initially
             parseFlags |= PackageParser.PARSE_IS_SYSTEM;
+
+            // An updated privileged app will not have the PARSE_IS_PRIVILEGED
+            // flag set initially
+            if ((updatedPkg.pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+                parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+            }
         }
         // Verify certificates against what was last scanned
         if (!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags)) {
@@ -4119,7 +4144,6 @@
             mLastScanError = PackageManager.INSTALL_FAILED_INVALID_APK;
             return null;
         }
-        mScanningPath = scanFile;
 
         if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
             pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
@@ -4139,7 +4163,7 @@
                 if (mAndroidApplication != null) {
                     Slog.w(TAG, "*************************************************");
                     Slog.w(TAG, "Core android package being redefined.  Skipping.");
-                    Slog.w(TAG, " file=" + mScanningPath);
+                    Slog.w(TAG, " file=" + scanFile);
                     Slog.w(TAG, "*************************************************");
                     mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
                     return null;
@@ -4619,6 +4643,10 @@
         if ((scanMode&SCAN_NO_DEX) == 0) {
             if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                     == DEX_OPT_FAILED) {
+                if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
+                    removeDataDirsLI(pkg.packageName);
+                }
+
                 mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                 return null;
             }
@@ -4696,6 +4724,10 @@
                     PackageParser.Package clientPkg = clientLibPkgs.get(i);
                     if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                             == DEX_OPT_FAILED) {
+                        if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
+                            removeDataDirsLI(pkg.packageName);
+                        }
+
                         mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                         return null;
                     }
@@ -4959,6 +4991,18 @@
                         permissionMap.put(p.info.name, bp);
                     }
                     if (bp.perm == null) {
+                        if (bp.sourcePackage != null
+                                && !bp.sourcePackage.equals(p.info.packageName)) {
+                            // If this is a permission that was formerly defined by a non-system
+                            // app, but is now defined by a system app (following an upgrade),
+                            // discard the previous declaration and consider the system's to be
+                            // canonical.
+                            if (isSystemApp(p.owner)) {
+                                Slog.i(TAG, "New decl " + p.owner + " of permission  "
+                                        + p.info.name + " is system");
+                                bp.sourcePackage = null;
+                            }
+                        }
                         if (bp.sourcePackage == null
                                 || bp.sourcePackage.equals(p.info.packageName)) {
                             BasePermission tree = findPermissionTreeLP(p.info.name);
@@ -9038,7 +9082,7 @@
             replacePackageLI(pkg, parseFlags, scanMode, args.user,
                     installerPackageName, res);
         } else {
-            installNewPackageLI(pkg, parseFlags, scanMode, args.user,
+            installNewPackageLI(pkg, parseFlags, scanMode | SCAN_DELETE_DATA_ON_FAILURES, args.user,
                     installerPackageName, res);
         }
         synchronized (mPackages) {
@@ -9999,11 +10043,11 @@
         }
         if (filter.countDataAuthorities() != 0
                 || filter.countDataPaths() != 0
-                || filter.countDataSchemes() != 0
+                || filter.countDataSchemes() > 1
                 || filter.countDataTypes() != 0) {
             throw new IllegalArgumentException(
                     "replacePreferredActivity expects filter to have no data authorities, " +
-                    "paths, schemes or types.");
+                    "paths, or types; and at most one scheme.");
         }
         synchronized (mPackages) {
             if (mContext.checkCallingOrSelfPermission(
@@ -10020,33 +10064,27 @@
             }
 
             final int callingUserId = UserHandle.getCallingUserId();
-            ArrayList<PreferredActivity> removed = null;
             PreferredIntentResolver pir = mSettings.mPreferredActivities.get(callingUserId);
             if (pir != null) {
-                Iterator<PreferredActivity> it = pir.filterIterator();
-                String action = filter.getAction(0);
-                String category = filter.getCategory(0);
-                while (it.hasNext()) {
-                    PreferredActivity pa = it.next();
-                    if ((pa.countActions() == 0) || (pa.countCategories() == 0)
-                            || (pa.getAction(0).equals(action)
-                                    && pa.getCategory(0).equals(category))) {
-                        if (removed == null) {
-                            removed = new ArrayList<PreferredActivity>();
-                        }
-                        removed.add(pa);
-                        if (DEBUG_PREFERRED) {
-                            Slog.i(TAG, "Removing preferred activity "
-                                    + pa.mPref.mComponent + ":");
-                            filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
-                        }
-                    }
+                Intent intent = new Intent(filter.getAction(0)).addCategory(filter.getCategory(0));
+                if (filter.countDataSchemes() == 1) {
+                    Uri.Builder builder = new Uri.Builder();
+                    builder.scheme(filter.getDataScheme(0));
+                    intent.setData(builder.build());
                 }
-                if (removed != null) {
-                    for (int i=0; i<removed.size(); i++) {
-                        PreferredActivity pa = removed.get(i);
-                        pir.removeFilter(pa);
+                List<PreferredActivity> matches = pir.queryIntent(
+                        intent, null, true, callingUserId);
+                if (DEBUG_PREFERRED) {
+                    Slog.i(TAG, matches.size() + " preferred matches for " + intent);
+                }
+                for (int i = 0; i < matches.size(); i++) {
+                    PreferredActivity pa = matches.get(i);
+                    if (DEBUG_PREFERRED) {
+                        Slog.i(TAG, "Removing preferred activity "
+                                + pa.mPref.mComponent + ":");
+                        filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
                     }
+                    pir.removeFilter(pa);
                 }
             }
             addPreferredActivityInternal(filter, match, set, activity, true, callingUserId);
@@ -10551,7 +10589,8 @@
 
         DumpState dumpState = new DumpState();
         boolean fullPreferred = false;
-        
+        boolean checkin = false;
+
         String packageName = null;
         
         int opti = 0;
@@ -10565,7 +10604,8 @@
                 // Right now we only know how to print all.
             } else if ("-h".equals(opt)) {
                 pw.println("Package manager dump options:");
-                pw.println("  [-h] [-f] [cmd] ...");
+                pw.println("  [-h] [-f] [--checkin] [cmd] ...");
+                pw.println("    --checkin: dump for a checkin");
                 pw.println("    -f: print details of intent filters");
                 pw.println("    -h: print this help");
                 pw.println("  cmd may be one of:");
@@ -10583,13 +10623,15 @@
                 pw.println("    <package.name>: info about given package");
                 pw.println("    k[eysets]: print known keysets");
                 return;
+            } else if ("--checkin".equals(opt)) {
+                checkin = true;
             } else if ("-f".equals(opt)) {
                 dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
             } else {
                 pw.println("Unknown argument: " + opt + "; use -h for help");
             }
         }
-        
+
         // Is the caller requesting to dump a particular piece of data?
         if (opti < args.length) {
             String cmd = args[opti];
@@ -10631,17 +10673,26 @@
             }
         }
 
+        if (checkin) {
+            pw.println("vers,1");
+        }
+
         // reader
         synchronized (mPackages) {
             if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) {
-                if (dumpState.onTitlePrinted())
-                    pw.println();
-                pw.println("Verifiers:");
-                pw.print("  Required: ");
-                pw.print(mRequiredVerifierPackage);
-                pw.print(" (uid=");
-                pw.print(getPackageUid(mRequiredVerifierPackage, 0));
-                pw.println(")");
+                if (!checkin) {
+                    if (dumpState.onTitlePrinted())
+                        pw.println();
+                    pw.println("Verifiers:");
+                    pw.print("  Required: ");
+                    pw.print(mRequiredVerifierPackage);
+                    pw.print(" (uid=");
+                    pw.print(getPackageUid(mRequiredVerifierPackage, 0));
+                    pw.println(")");
+                } else if (mRequiredVerifierPackage != null) {
+                    pw.print("vrfy,"); pw.print(mRequiredVerifierPackage);
+                    pw.print(","); pw.println(getPackageUid(mRequiredVerifierPackage, 0));
+                }
             }
 
             if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) {
@@ -10650,21 +10701,37 @@
                 while (it.hasNext()) {
                     String name = it.next();
                     SharedLibraryEntry ent = mSharedLibraries.get(name);
-                    if (!printedHeader) {
-                        if (dumpState.onTitlePrinted())
-                            pw.println();
-                        pw.println("Libraries:");
-                        printedHeader = true;
-                    }
-                    pw.print("  ");
-                    pw.print(name);
-                    pw.print(" -> ");
-                    if (ent.path != null) {
-                        pw.print("(jar) ");
-                        pw.print(ent.path);
+                    if (!checkin) {
+                        if (!printedHeader) {
+                            if (dumpState.onTitlePrinted())
+                                pw.println();
+                            pw.println("Libraries:");
+                            printedHeader = true;
+                        }
+                        pw.print("  ");
                     } else {
-                        pw.print("(apk) ");
-                        pw.print(ent.apk);
+                        pw.print("lib,");
+                    }
+                    pw.print(name);
+                    if (!checkin) {
+                        pw.print(" -> ");
+                    }
+                    if (ent.path != null) {
+                        if (!checkin) {
+                            pw.print("(jar) ");
+                            pw.print(ent.path);
+                        } else {
+                            pw.print(",jar,");
+                            pw.print(ent.path);
+                        }
+                    } else {
+                        if (!checkin) {
+                            pw.print("(apk) ");
+                            pw.print(ent.apk);
+                        } else {
+                            pw.print(",apk,");
+                            pw.print(ent.apk);
+                        }
                     }
                     pw.println();
                 }
@@ -10673,16 +10740,22 @@
             if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) {
                 if (dumpState.onTitlePrinted())
                     pw.println();
-                pw.println("Features:");
+                if (!checkin) {
+                    pw.println("Features:");
+                }
                 Iterator<String> it = mAvailableFeatures.keySet().iterator();
                 while (it.hasNext()) {
                     String name = it.next();
-                    pw.print("  ");
+                    if (!checkin) {
+                        pw.print("  ");
+                    } else {
+                        pw.print("feat,");
+                    }
                     pw.println(name);
                 }
             }
 
-            if (dumpState.isDumping(DumpState.DUMP_RESOLVERS)) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_RESOLVERS)) {
                 if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
                         : "Activity Resolver Table:", "  ", packageName,
                         dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) {
@@ -10705,7 +10778,7 @@
                 }
             }
 
-            if (dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
                 for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
                     PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i);
                     int user = mSettings.mPreferredActivities.keyAt(i);
@@ -10719,7 +10792,7 @@
                 }
             }
 
-            if (dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)) {
                 pw.flush();
                 FileOutputStream fout = new FileOutputStream(fd);
                 BufferedOutputStream str = new BufferedOutputStream(fout);
@@ -10741,11 +10814,11 @@
                 }
             }
 
-            if (dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
                 mSettings.dumpPermissionsLPr(pw, packageName, dumpState);
             }
 
-            if (dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
                 boolean printedSomething = false;
                 for (PackageParser.Provider p : mProviders.mProviders.values()) {
                     if (packageName != null && !packageName.equals(p.info.packageName)) {
@@ -10782,19 +10855,19 @@
                 }
             }
 
-            if (dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
                 mSettings.mKeySetManager.dump(pw, packageName, dumpState);
             }
 
             if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
-                mSettings.dumpPackagesLPr(pw, packageName, dumpState);
+                mSettings.dumpPackagesLPr(pw, packageName, dumpState, checkin);
             }
 
-            if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
                 mSettings.dumpSharedUsersLPr(pw, packageName, dumpState);
             }
 
-            if (dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
                 if (dumpState.onTitlePrinted())
                     pw.println();
                 mSettings.dumpReadMessagesLPr(pw, dumpState);
@@ -11033,7 +11106,7 @@
             if (uidArr != null) {
                 extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr);
             }
-            if (replacing && !mediaStatus) {
+            if (replacing) {
                 extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
             }
             String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index d3ccba6..19bfe01 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -1959,10 +1959,14 @@
         }
 
         boolean doNonData = true;
+        boolean hasSchemes = false;
 
         for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) {
             boolean doScheme = true;
             String scheme = tmpPa.getDataScheme(ischeme);
+            if (scheme != null && !scheme.isEmpty()) {
+                hasSchemes = true;
+            }
             for (int issp=0; issp<tmpPa.countDataSchemeSpecificParts(); issp++) {
                 Uri.Builder builder = new Uri.Builder();
                 builder.scheme(scheme);
@@ -2016,11 +2020,25 @@
         }
 
         for (int idata=0; idata<tmpPa.countDataTypes(); idata++) {
-            Intent finalIntent = new Intent(intent);
             String mimeType = tmpPa.getDataType(idata);
-            finalIntent.setType(mimeType);
-            applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
-                    null, null, null, null, mimeType, userId);
+            if (hasSchemes) {
+                Uri.Builder builder = new Uri.Builder();
+                for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) {
+                    String scheme = tmpPa.getDataScheme(ischeme);
+                    if (scheme != null && !scheme.isEmpty()) {
+                        Intent finalIntent = new Intent(intent);
+                        builder.scheme(scheme);
+                        finalIntent.setDataAndType(builder.build(), mimeType);
+                        applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+                                scheme, null, null, null, mimeType, userId);
+                    }
+                }
+            } else {
+                Intent finalIntent = new Intent(intent);
+                finalIntent.setType(mimeType);
+                applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+                        null, null, null, null, mimeType, userId);
+            }
             doNonData = false;
         }
 
@@ -2873,8 +2891,44 @@
         ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
     };
 
-    void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf,
-            Date date, List<UserInfo> users) {
+    void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag, PackageSetting ps,
+            SimpleDateFormat sdf, Date date, List<UserInfo> users) {
+        if (checkinTag != null) {
+            pw.print(checkinTag);
+            pw.print(",");
+            pw.print(ps.realName != null ? ps.realName : ps.name);
+            pw.print(",");
+            pw.print(ps.appId);
+            pw.print(",");
+            pw.print(ps.versionCode);
+            pw.print(",");
+            pw.print(ps.firstInstallTime);
+            pw.print(",");
+            pw.print(ps.lastUpdateTime);
+            pw.print(",");
+            pw.print(ps.installerPackageName != null ? ps.installerPackageName : "?");
+            pw.println();
+            for (UserInfo user : users) {
+                pw.print(checkinTag);
+                pw.print("-");
+                pw.print("usr");
+                pw.print(",");
+                pw.print(user.id);
+                pw.print(",");
+                pw.print(ps.getInstalled(user.id) ? "I" : "i");
+                pw.print(ps.getBlocked(user.id) ? "B" : "b");
+                pw.print(ps.getStopped(user.id) ? "S" : "s");
+                pw.print(ps.getNotLaunched(user.id) ? "l" : "L");
+                pw.print(",");
+                pw.print(ps.getEnabled(user.id));
+                String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
+                pw.print(",");
+                pw.print(lastDisabledAppCaller != null ? lastDisabledAppCaller : "?");
+                pw.println();
+            }
+            return;
+        }
+
         pw.print(prefix); pw.print("Package [");
             pw.print(ps.realName != null ? ps.realName : ps.name);
             pw.print("] (");
@@ -3036,7 +3090,7 @@
         }
     }
 
-    void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
+    void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState, boolean checkin) {
         final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         final Date date = new Date();
         boolean printedSomething = false;
@@ -3047,35 +3101,39 @@
                 continue;
             }
 
-            if (packageName != null) {
+            if (!checkin && packageName != null) {
                 dumpState.setSharedUser(ps.sharedUser);
             }
 
-            if (!printedSomething) {
+            if (!checkin && !printedSomething) {
                 if (dumpState.onTitlePrinted())
                     pw.println();
                 pw.println("Packages:");
                 printedSomething = true;
             }
-            dumpPackageLPr(pw, "  ", ps, sdf, date, users);
+            dumpPackageLPr(pw, "  ", checkin ? "pkg" : null, ps, sdf, date, users);
         }
 
         printedSomething = false;
-        if (mRenamedPackages.size() > 0) {
+        if (!checkin && mRenamedPackages.size() > 0) {
             for (final Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
                 if (packageName != null && !packageName.equals(e.getKey())
                         && !packageName.equals(e.getValue())) {
                     continue;
                 }
-                if (!printedSomething) {
-                    if (dumpState.onTitlePrinted())
-                        pw.println();
-                    pw.println("Renamed packages:");
-                    printedSomething = true;
+                if (!checkin) {
+                    if (!printedSomething) {
+                        if (dumpState.onTitlePrinted())
+                            pw.println();
+                        pw.println("Renamed packages:");
+                        printedSomething = true;
+                    }
+                    pw.print("  ");
+                } else {
+                    pw.print("ren,");
                 }
-                pw.print("  ");
                 pw.print(e.getKey());
-                pw.print(" -> ");
+                pw.print(checkin ? " -> " : ",");
                 pw.println(e.getValue());
             }
         }
@@ -3087,13 +3145,13 @@
                         && !packageName.equals(ps.name)) {
                     continue;
                 }
-                if (!printedSomething) {
+                if (!checkin && !printedSomething) {
                     if (dumpState.onTitlePrinted())
                         pw.println();
                     pw.println("Hidden system packages:");
                     printedSomething = true;
                 }
-                dumpPackageLPr(pw, "  ", ps, sdf, date, users);
+                dumpPackageLPr(pw, "  ", checkin ? "dis" : null, ps, sdf, date, users);
             }
         }
     }
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
index 976a328..30bc922 100644
--- a/services/java/com/android/server/power/DisplayPowerController.java
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -298,6 +298,10 @@
     // True if mAmbientLux holds a valid value.
     private boolean mAmbientLuxValid;
 
+    // The ambient light level threshold at which to brighten or darken the screen.
+    private float mBrighteningLuxThreshold;
+    private float mDarkeningLuxThreshold;
+
     // The most recent light sample.
     private float mLastObservedLux;
 
@@ -606,7 +610,6 @@
                         && mProximity == PROXIMITY_POSITIVE) {
                     mScreenOffBecauseOfProximity = true;
                     sendOnProximityPositiveWithWakelock();
-                    setScreenOn(false);
                 }
             } else if (mWaitingForNegativeProximity
                     && mScreenOffBecauseOfProximity
@@ -666,59 +669,62 @@
             mUsingScreenAutoBrightness = false;
         }
 
-        // Animate the screen on or off.
-        if (!mScreenOffBecauseOfProximity) {
-            if (wantScreenOn(mPowerRequest.screenState)) {
-                // Want screen on.
-                // Wait for previous off animation to complete beforehand.
-                // It is relatively short but if we cancel it and switch to the
-                // on animation immediately then the results are pretty ugly.
-                if (!mElectronBeamOffAnimator.isStarted()) {
-                    // Turn the screen on.  The contents of the screen may not yet
-                    // be visible if the electron beam has not been dismissed because
-                    // its last frame of animation is solid black.
-                    setScreenOn(true);
+        // Animate the screen on or off unless blocked.
+        if (mScreenOffBecauseOfProximity) {
+            // Screen off due to proximity.
+            setScreenOn(false);
+            unblockScreenOn();
+        } else if (wantScreenOn(mPowerRequest.screenState)) {
+            // Want screen on.
+            // Wait for previous off animation to complete beforehand.
+            // It is relatively short but if we cancel it and switch to the
+            // on animation immediately then the results are pretty ugly.
+            if (!mElectronBeamOffAnimator.isStarted()) {
+                // Turn the screen on.  The contents of the screen may not yet
+                // be visible if the electron beam has not been dismissed because
+                // its last frame of animation is solid black.
+                setScreenOn(true);
 
-                    if (mPowerRequest.blockScreenOn
-                            && mPowerState.getElectronBeamLevel() == 0.0f) {
-                        blockScreenOn();
-                    } else {
-                        unblockScreenOn();
-                        if (USE_ELECTRON_BEAM_ON_ANIMATION) {
-                            if (!mElectronBeamOnAnimator.isStarted()) {
-                                if (mPowerState.getElectronBeamLevel() == 1.0f) {
-                                    mPowerState.dismissElectronBeam();
-                                } else if (mPowerState.prepareElectronBeam(
-                                        mElectronBeamFadesConfig ?
-                                                ElectronBeam.MODE_FADE :
-                                                        ElectronBeam.MODE_WARM_UP)) {
-                                    mElectronBeamOnAnimator.start();
-                                } else {
-                                    mElectronBeamOnAnimator.end();
-                                }
+                if (mPowerRequest.blockScreenOn
+                        && mPowerState.getElectronBeamLevel() == 0.0f) {
+                    blockScreenOn();
+                } else {
+                    unblockScreenOn();
+                    if (USE_ELECTRON_BEAM_ON_ANIMATION) {
+                        if (!mElectronBeamOnAnimator.isStarted()) {
+                            if (mPowerState.getElectronBeamLevel() == 1.0f) {
+                                mPowerState.dismissElectronBeam();
+                            } else if (mPowerState.prepareElectronBeam(
+                                    mElectronBeamFadesConfig ?
+                                            ElectronBeam.MODE_FADE :
+                                                    ElectronBeam.MODE_WARM_UP)) {
+                                mElectronBeamOnAnimator.start();
+                            } else {
+                                mElectronBeamOnAnimator.end();
                             }
-                        } else {
-                            mPowerState.setElectronBeamLevel(1.0f);
-                            mPowerState.dismissElectronBeam();
                         }
+                    } else {
+                        mPowerState.setElectronBeamLevel(1.0f);
+                        mPowerState.dismissElectronBeam();
                     }
                 }
-            } else {
-                // Want screen off.
-                // Wait for previous on animation to complete beforehand.
-                if (!mElectronBeamOnAnimator.isStarted()) {
-                    if (!mElectronBeamOffAnimator.isStarted()) {
-                        if (mPowerState.getElectronBeamLevel() == 0.0f) {
-                            setScreenOn(false);
-                        } else if (mPowerState.prepareElectronBeam(
-                                mElectronBeamFadesConfig ?
-                                        ElectronBeam.MODE_FADE :
-                                                ElectronBeam.MODE_COOL_DOWN)
-                                && mPowerState.isScreenOn()) {
-                            mElectronBeamOffAnimator.start();
-                        } else {
-                            mElectronBeamOffAnimator.end();
-                        }
+            }
+        } else {
+            // Want screen off.
+            // Wait for previous on animation to complete beforehand.
+            unblockScreenOn();
+            if (!mElectronBeamOnAnimator.isStarted()) {
+                if (!mElectronBeamOffAnimator.isStarted()) {
+                    if (mPowerState.getElectronBeamLevel() == 0.0f) {
+                        setScreenOn(false);
+                    } else if (mPowerState.prepareElectronBeam(
+                            mElectronBeamFadesConfig ?
+                                    ElectronBeam.MODE_FADE :
+                                            ElectronBeam.MODE_COOL_DOWN)
+                            && mPowerState.isScreenOn()) {
+                        mElectronBeamOffAnimator.start();
+                    } else {
+                        mElectronBeamOffAnimator.end();
                     }
                 }
             }
@@ -758,15 +764,15 @@
     private void unblockScreenOn() {
         if (mScreenOnWasBlocked) {
             mScreenOnWasBlocked = false;
-            if (DEBUG) {
-                Slog.d(TAG, "Unblocked screen on after " +
-                        (SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime) + " ms");
+            long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime;
+            if (delay > 1000 || DEBUG) {
+                Slog.d(TAG, "Unblocked screen on after " + delay + " ms");
             }
         }
     }
 
     private void setScreenOn(boolean on) {
-        if (!mPowerState.isScreenOn() == on) {
+        if (mPowerState.isScreenOn() != on) {
             mPowerState.setScreenOn(on);
             if (on) {
                 mNotifier.onScreenOn();
@@ -945,12 +951,24 @@
         mLastObservedLuxTime = time;
     }
 
+    private void setAmbientLux(float lux) {
+        mAmbientLux = lux;
+        mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
+        mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
+    }
+
     private void updateAmbientLux(long time) {
         // If the light sensor was just turned on then immediately update our initial
         // estimate of the current ambient light level.
-        if (!mAmbientLuxValid
-                || (time - mLightSensorEnableTime) < mLightSensorWarmUpTimeConfig) {
-            mAmbientLux = mRecentShortTermAverageLux;
+        if (!mAmbientLuxValid) {
+            final long timeWhenSensorWarmedUp =
+                mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
+            if (time < timeWhenSensorWarmedUp) {
+                mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED,
+                        timeWhenSensorWarmedUp);
+                return;
+            }
+            setAmbientLux(mRecentShortTermAverageLux);
             mAmbientLuxValid = true;
             mDebounceLuxDirection = 0;
             mDebounceLuxTime = time;
@@ -961,98 +979,90 @@
                         + ", mAmbientLux=" + mAmbientLux);
             }
             updateAutoBrightness(true);
-            return;
-        }
-
-        // Determine whether the ambient environment appears to be brightening.
-        float brighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
-        if (mRecentShortTermAverageLux > brighteningLuxThreshold
-                && mRecentLongTermAverageLux > brighteningLuxThreshold) {
+        } else if (mRecentShortTermAverageLux > mBrighteningLuxThreshold
+                && mRecentLongTermAverageLux > mBrighteningLuxThreshold) {
+            // The ambient environment appears to be brightening.
             if (mDebounceLuxDirection <= 0) {
                 mDebounceLuxDirection = 1;
                 mDebounceLuxTime = time;
                 if (DEBUG) {
                     Slog.d(TAG, "updateAmbientLux: Possibly brightened, waiting for "
                             + BRIGHTENING_LIGHT_DEBOUNCE + " ms: "
-                            + "brighteningLuxThreshold=" + brighteningLuxThreshold
+                            + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
                             + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
                             + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
                             + ", mAmbientLux=" + mAmbientLux);
                 }
             }
             long debounceTime = mDebounceLuxTime + BRIGHTENING_LIGHT_DEBOUNCE;
-            if (time >= debounceTime) {
-                mAmbientLux = mRecentShortTermAverageLux;
-                if (DEBUG) {
-                    Slog.d(TAG, "updateAmbientLux: Brightened: "
-                            + "brighteningLuxThreshold=" + brighteningLuxThreshold
-                            + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
-                            + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
-                            + ", mAmbientLux=" + mAmbientLux);
-                }
-                updateAutoBrightness(true);
-            } else {
+            if (time < debounceTime) {
                 mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime);
+                return;
             }
-            return;
-        }
-
-        // Determine whether the ambient environment appears to be darkening.
-        float darkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
-        if (mRecentShortTermAverageLux < darkeningLuxThreshold
-                && mRecentLongTermAverageLux < darkeningLuxThreshold) {
+            setAmbientLux(mRecentShortTermAverageLux);
+            if (DEBUG) {
+                Slog.d(TAG, "updateAmbientLux: Brightened: "
+                        + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
+                        + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+                        + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+                        + ", mAmbientLux=" + mAmbientLux);
+            }
+            updateAutoBrightness(true);
+        } else if (mRecentShortTermAverageLux < mDarkeningLuxThreshold
+                && mRecentLongTermAverageLux < mDarkeningLuxThreshold) {
+            // The ambient environment appears to be darkening.
             if (mDebounceLuxDirection >= 0) {
                 mDebounceLuxDirection = -1;
                 mDebounceLuxTime = time;
                 if (DEBUG) {
                     Slog.d(TAG, "updateAmbientLux: Possibly darkened, waiting for "
                             + DARKENING_LIGHT_DEBOUNCE + " ms: "
-                            + "darkeningLuxThreshold=" + darkeningLuxThreshold
+                            + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
                             + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
                             + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
                             + ", mAmbientLux=" + mAmbientLux);
                 }
             }
             long debounceTime = mDebounceLuxTime + DARKENING_LIGHT_DEBOUNCE;
-            if (time >= debounceTime) {
-                // Be conservative about reducing the brightness, only reduce it a little bit
-                // at a time to avoid having to bump it up again soon.
-                mAmbientLux = Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux);
-                if (DEBUG) {
-                    Slog.d(TAG, "updateAmbientLux: Darkened: "
-                            + "darkeningLuxThreshold=" + darkeningLuxThreshold
-                            + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
-                            + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
-                            + ", mAmbientLux=" + mAmbientLux);
-                }
-                updateAutoBrightness(true);
-            } else {
+            if (time < debounceTime) {
                 mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime);
+                return;
             }
-            return;
-        }
-
-        // No change or change is within the hysteresis thresholds.
-        if (mDebounceLuxDirection != 0) {
+            // Be conservative about reducing the brightness, only reduce it a little bit
+            // at a time to avoid having to bump it up again soon.
+            setAmbientLux(Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux));
+            if (DEBUG) {
+                Slog.d(TAG, "updateAmbientLux: Darkened: "
+                        + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
+                        + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+                        + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+                        + ", mAmbientLux=" + mAmbientLux);
+            }
+            updateAutoBrightness(true);
+        } else if (mDebounceLuxDirection != 0) {
+            // No change or change is within the hysteresis thresholds.
             mDebounceLuxDirection = 0;
             mDebounceLuxTime = time;
             if (DEBUG) {
                 Slog.d(TAG, "updateAmbientLux: Canceled debounce: "
-                        + "brighteningLuxThreshold=" + brighteningLuxThreshold
-                        + ", darkeningLuxThreshold=" + darkeningLuxThreshold
+                        + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
+                        + ", mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
                         + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
                         + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
                         + ", mAmbientLux=" + mAmbientLux);
             }
         }
 
-        // If the light level does not change, then the sensor may not report
-        // a new value.  This can cause problems for the auto-brightness algorithm
-        // because the filters might not be updated.  To work around it, we want to
-        // make sure to update the filters whenever the observed light level could
-        // possibly exceed one of the hysteresis thresholds.
-        if (mLastObservedLux > brighteningLuxThreshold
-                || mLastObservedLux < darkeningLuxThreshold) {
+        // Now that we've done all of that, we haven't yet posted a debounce
+        // message. So consider the case where current lux is beyond the
+        // threshold. It's possible that the light sensor may not report values
+        // if the light level does not change, so we need to occasionally
+        // synthesize sensor readings in order to make sure the brightness is
+        // adjusted accordingly. Note these thresholds may have changed since
+        // we entered the function because we called setAmbientLux and
+        // updateAutoBrightness along the way.
+        if (mLastObservedLux > mBrighteningLuxThreshold
+                || mLastObservedLux < mDarkeningLuxThreshold) {
             mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED,
                     time + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS);
         }
diff --git a/services/java/com/android/server/power/DisplayPowerState.java b/services/java/com/android/server/power/DisplayPowerState.java
index fa318f8..5c048f1 100644
--- a/services/java/com/android/server/power/DisplayPowerState.java
+++ b/services/java/com/android/server/power/DisplayPowerState.java
@@ -304,8 +304,15 @@
 
             int brightness = mScreenOn && mElectronBeamLevel > 0f ? mScreenBrightness : 0;
             if (mPhotonicModulator.setState(mScreenOn, brightness)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Screen ready");
+                }
                 mScreenReady = true;
                 invokeCleanListenerIfNeeded();
+            } else {
+                if (DEBUG) {
+                    Slog.d(TAG, "Screen not ready");
+                }
             }
         }
     };
@@ -355,7 +362,7 @@
                         AsyncTask.THREAD_POOL_EXECUTOR.execute(mTask);
                     }
                 }
-                return mChangeInProgress;
+                return !mChangeInProgress;
             }
         }
 
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java
index da9548f..134718b 100644
--- a/services/java/com/android/server/power/PowerManagerService.java
+++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -1135,6 +1135,9 @@
         if (!mSystemReady || mDirty == 0) {
             return;
         }
+        if (!Thread.holdsLock(mLock)) {
+            Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked");
+        }
 
         // Phase 0: Basic state updates.
         updateIsPoweredLocked(mDirty);
diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java
index 8a3997a..98acc27 100644
--- a/services/java/com/android/server/print/PrintManagerService.java
+++ b/services/java/com/android/server/print/PrintManagerService.java
@@ -366,7 +366,7 @@
                 pw.println("PRINT MANAGER STATE (dumpsys print)");
                 final int userStateCount = mUserStates.size();
                 for (int i = 0; i < userStateCount; i++) {
-                    UserState userState = mUserStates.get(i);
+                    UserState userState = mUserStates.valueAt(i);
                     userState.dump(fd, pw, "");
                     pw.println();
                 }
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index 5a60de0..bb61e49 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -98,6 +98,13 @@
     // which need debouncing.
     private static final int UPDATE_DELAY = 1000;
 
+    // Time we received a request to enter USB accessory mode
+    private long mAccessoryModeRequestTime = 0;
+
+    // Timeout for entering USB request mode.
+    // Request is cancelled if host does not configure device within 10 seconds.
+    private static final int ACCESSORY_REQUEST_TIMEOUT = 10 * 1000;
+
     private static final String BOOT_MODE_PROPERTY = "ro.bootmode";
 
     private UsbHandler mHandler;
@@ -205,6 +212,8 @@
     }
 
     private void startAccessoryMode() {
+        if (!mHasUsbAccessory) return;
+
         mAccessoryStrings = nativeGetAccessoryStrings();
         boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
         // don't start accessory mode if our mandatory strings have not been set
@@ -223,6 +232,7 @@
         }
 
         if (functions != null) {
+            mAccessoryModeRequestTime = SystemClock.elapsedRealtime();
             setCurrentFunctions(functions, false);
         }
     }
@@ -456,6 +466,8 @@
         }
 
         private void setEnabledFunctions(String functions, boolean makeDefault) {
+            if (DEBUG) Slog.d(TAG, "setEnabledFunctions " + functions
+                    + " makeDefault: " + makeDefault);
 
             // Do not update persystent.sys.usb.config if the device is booted up
             // with OEM specific mode.
@@ -517,9 +529,16 @@
         }
 
         private void updateCurrentAccessory() {
-            if (!mHasUsbAccessory) return;
+            // We are entering accessory mode if we have received a request from the host
+            // and the request has not timed out yet.
+            boolean enteringAccessoryMode =
+                    mAccessoryModeRequestTime > 0 &&
+                        SystemClock.elapsedRealtime() <
+                            mAccessoryModeRequestTime + ACCESSORY_REQUEST_TIMEOUT;
 
-            if (mConfigured) {
+            if (mConfigured && enteringAccessoryMode) {
+                // successfully entered accessory mode
+
                 if (mAccessoryStrings != null) {
                     mCurrentAccessory = new UsbAccessory(mAccessoryStrings);
                     Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
@@ -530,7 +549,7 @@
                 } else {
                     Slog.e(TAG, "nativeGetAccessoryStrings failed");
                 }
-            } else if (!mConnected) {
+            } else if (!enteringAccessoryMode) {
                 // make sure accessory mode is off
                 // and restore default functions
                 Slog.d(TAG, "exited USB accessory mode");
@@ -560,6 +579,8 @@
                 }
             }
 
+            if (DEBUG) Slog.d(TAG, "broadcasting " + intent + " connected: " + mConnected
+                                    + " configured: " + mConfigured);
             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
         }
 
@@ -599,9 +620,7 @@
                     if (containsFunction(mCurrentFunctions,
                             UsbManager.USB_FUNCTION_ACCESSORY)) {
                         updateCurrentAccessory();
-                    }
-
-                    if (!mConnected) {
+                    } else if (!mConnected) {
                         // restore defaults when USB is disconnected
                         setEnabledFunctions(mDefaultFunctions, false);
                     }
diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java
index a3d514e..bdb0a5e 100644
--- a/services/java/com/android/server/wifi/WifiController.java
+++ b/services/java/com/android/server/wifi/WifiController.java
@@ -152,11 +152,21 @@
             addState(mStaDisabledWithScanState, mDefaultState);
             addState(mApEnabledState, mDefaultState);
             addState(mEcmState, mDefaultState);
-        if (mSettingsStore.isScanAlwaysAvailable()) {
+
+        boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
+        boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
+        boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
+
+        log("isAirplaneModeOn = " + isAirplaneModeOn +
+                ", isWifiEnabled = " + isWifiEnabled +
+                ", isScanningAvailable = " + isScanningAlwaysAvailable);
+
+        if (isWifiEnabled && isScanningAlwaysAvailable) {
             setInitialState(mStaDisabledWithScanState);
         } else {
             setInitialState(mApStaDisabledState);
         }
+
         setLogRecSize(100);
         setLogOnlyTransitions(false);
 
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index d471b57..aecf9ae 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -369,15 +369,17 @@
     }
 
     private class BatchedScanRequest extends DeathRecipient {
-        BatchedScanSettings settings;
-        int uid;
-        int pid;
+        final BatchedScanSettings settings;
+        final int uid;
+        final int pid;
+        final WorkSource workSource;
 
-        BatchedScanRequest(BatchedScanSettings settings, IBinder binder) {
+        BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) {
             super(0, null, binder, null);
             this.settings = settings;
             this.uid = getCallingUid();
             this.pid = getCallingPid();
+            workSource = ws;
         }
         public void binderDied() {
             stopBatchedScan(settings, uid, pid);
@@ -406,12 +408,19 @@
     /**
      * see {@link android.net.wifi.WifiManager#requestBatchedScan()}
      */
-    public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder) {
+    public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder,
+            WorkSource workSource) {
         enforceChangePermission();
+        if (workSource != null) {
+            enforceWorkSourcePermission();
+            // WifiManager currently doesn't use names, so need to clear names out of the
+            // supplied WorkSource to allow future WorkSource combining.
+            workSource.clearNames();
+        }
         if (mBatchedScanSupported == false) return false;
         requested = new BatchedScanSettings(requested);
         if (requested.isInvalid()) return false;
-        BatchedScanRequest r = new BatchedScanRequest(requested, binder);
+        BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource);
         synchronized(mBatchedScanners) {
             mBatchedScanners.add(r);
             resolveBatchedScannersLocked();
@@ -468,18 +477,48 @@
 
     private void resolveBatchedScannersLocked() {
         BatchedScanSettings setting = new BatchedScanSettings();
+        WorkSource responsibleWorkSource = null;
         int responsibleUid = 0;
+        double responsibleCsph = 0; // Channel Scans Per Hour
 
         if (mBatchedScanners.size() == 0) {
-            mWifiStateMachine.setBatchedScanSettings(null, 0);
+            mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null);
             return;
         }
         for (BatchedScanRequest r : mBatchedScanners) {
             BatchedScanSettings s = r.settings;
+
+            // evaluate responsibility
+            int currentChannelCount;
+            int currentScanInterval;
+            double currentCsph;
+
+            if (s.channelSet == null || s.channelSet.isEmpty()) {
+                // all channels - 11 B and 9 A channels roughly.
+                currentChannelCount = 9 + 11;
+            } else {
+                currentChannelCount = s.channelSet.size();
+                // these are rough est - no real need to correct for reg-domain;
+                if (s.channelSet.contains("A")) currentChannelCount += (9 - 1);
+                if (s.channelSet.contains("B")) currentChannelCount += (11 - 1);
+
+            }
+            if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
+                currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
+            } else {
+                currentScanInterval = s.scanIntervalSec;
+            }
+            currentCsph = 60 * 60 * currentChannelCount / currentScanInterval;
+
+            if (currentCsph > responsibleCsph) {
+                responsibleUid = r.uid;
+                responsibleWorkSource = r.workSource;
+                responsibleCsph = currentCsph;
+            }
+
             if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
                     s.maxScansPerBatch < setting.maxScansPerBatch) {
                 setting.maxScansPerBatch = s.maxScansPerBatch;
-                responsibleUid = r.uid;
             }
             if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
                     (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED ||
@@ -489,7 +528,6 @@
             if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
                     s.scanIntervalSec < setting.scanIntervalSec) {
                 setting.scanIntervalSec = s.scanIntervalSec;
-                responsibleUid = r.uid;
             }
             if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
                     (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED ||
@@ -511,7 +549,8 @@
         }
 
         setting.constrain();
-        mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid);
+        mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph,
+                responsibleWorkSource);
     }
 
     private void enforceAccessPermission() {
@@ -831,7 +870,7 @@
     public void setCountryCode(String countryCode, boolean persist) {
         Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
                 " with persist set to " + persist);
-        enforceChangePermission();
+        enforceConnectivityInternalPermission();
         final long token = Binder.clearCallingIdentity();
         try {
             mWifiStateMachine.setCountryCode(countryCode, persist);
diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java
index 8cc1d02..e98014b 100644
--- a/services/java/com/android/server/wm/AppWindowToken.java
+++ b/services/java/com/android/server/wm/AppWindowToken.java
@@ -53,6 +53,7 @@
     int groupId = -1;
     boolean appFullscreen;
     int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+    boolean layoutConfigChanges;
     boolean showWhenLocked;
 
     // The input dispatching timeout for this application token in nanoseconds.
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index 52f2325..d358b4c 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -25,9 +25,11 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Debug;
+import android.util.EventLog;
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayInfo;
+import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -97,9 +99,6 @@
     /** True when the home StackBox is at the top of mStackBoxes, false otherwise. */
     private TaskStack mHomeStack = null;
 
-    /** Sorted most recent at top, oldest at [0]. */
-    ArrayList<TaskStack> mStackHistory = new ArrayList<TaskStack>();
-
     /** Detect user tapping outside of current focused stack bounds .*/
     StackTapPointerEventListener mTapDetector;
 
@@ -107,7 +106,7 @@
     Region mTouchExcludeRegion = new Region();
 
     /** Save allocating when retrieving tasks */
-    ArrayList<Task> mTaskHistory = new ArrayList<Task>();
+    private ArrayList<Task> mTaskHistory = new ArrayList<Task>();
 
     /** Save allocating when calculating rects */
     Rect mTmpRect = new Rect();
@@ -160,12 +159,6 @@
         return mStackBoxes.get(0).mStack != mHomeStack;
     }
 
-    void moveStack(TaskStack stack, boolean toTop) {
-        mStackHistory.remove(stack);
-        mStackHistory.add(toTop ? mStackHistory.size() : 0, stack);
-        mService.moveStackWindowsLocked(this);
-    }
-
     public boolean isPrivate() {
         return (mDisplay.getFlags() & Display.FLAG_PRIVATE) != 0;
     }
@@ -200,6 +193,7 @@
         }
 
         mTaskHistory.add(taskNdx, task);
+        EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.taskId, toTop ? 1 : 0, taskNdx);
     }
 
     void removeTask(Task task) {
@@ -277,6 +271,8 @@
         if (newStack != null) {
             layoutNeeded = true;
         }
+        EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId, relativeStackBoxId, position,
+                (int)(weight * 100 + 0.5));
         return newStack;
     }
 
@@ -345,6 +341,7 @@
     boolean moveHomeStackBox(boolean toTop) {
         if (DEBUG_STACK) Slog.d(TAG, "moveHomeStackBox: toTop=" + toTop + " Callers=" +
                 Debug.getCallers(4));
+        EventLog.writeEvent(EventLogTags.WM_HOME_STACK_MOVED, toTop ? 1 : 0);
         switch (mStackBoxes.size()) {
             case 0: throw new RuntimeException("moveHomeStackBox: No home StackBox!");
             case 1: return false; // Only the home StackBox exists.
diff --git a/services/java/com/android/server/wm/StackBox.java b/services/java/com/android/server/wm/StackBox.java
index d054e9a..d351925 100644
--- a/services/java/com/android/server/wm/StackBox.java
+++ b/services/java/com/android/server/wm/StackBox.java
@@ -243,10 +243,6 @@
     /** Remove this box and propagate its sibling's content up to their parent.
      * @return The first stackId of the resulting StackBox. */
     int remove() {
-        if (mStack != null) {
-            if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing stackId=" + mStack.mStackId);
-            mDisplayContent.mStackHistory.remove(mStack);
-        }
         mDisplayContent.layoutNeeded = true;
 
         if (mParent == null) {
diff --git a/services/java/com/android/server/wm/Task.java b/services/java/com/android/server/wm/Task.java
index d9acbb9..13fdbc8 100644
--- a/services/java/com/android/server/wm/Task.java
+++ b/services/java/com/android/server/wm/Task.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import android.util.EventLog;
+import com.android.server.EventLogTags;
+
 class Task {
 //    private final String TAG = "TaskGroup";
     TaskStack mStack;
@@ -41,6 +44,8 @@
     boolean removeAppToken(AppWindowToken wtoken) {
         mAppTokens.remove(wtoken);
         if (mAppTokens.size() == 0) {
+            EventLog.writeEvent(com.android.server.EventLogTags.WM_TASK_REMOVED, taskId,
+                    "removeAppToken: last token");
             mStack.removeTask(this);
             return true;
         }
diff --git a/services/java/com/android/server/wm/TaskStack.java b/services/java/com/android/server/wm/TaskStack.java
index 34bef68..cb29df4 100644
--- a/services/java/com/android/server/wm/TaskStack.java
+++ b/services/java/com/android/server/wm/TaskStack.java
@@ -21,8 +21,10 @@
 
 import android.graphics.Rect;
 import android.os.Debug;
+import android.util.EventLog;
 import android.util.Slog;
 import android.util.TypedValue;
+import com.android.server.EventLogTags;
 
 import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
 
@@ -45,7 +47,7 @@
 
     /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match
      * mTaskHistory in the ActivityStack with the same mStackId */
-    private ArrayList<Task> mTasks = new ArrayList<Task>();
+    private final ArrayList<Task> mTasks = new ArrayList<Task>();
 
     /** The StackBox this sits in. */
     StackBox mStackBox;
@@ -70,7 +72,6 @@
         mService = service;
         mStackId = stackId;
         mDisplayContent = displayContent;
-        final int displayId = displayContent.getDisplayId();
         mDimLayer = new DimLayer(service, this);
         mAnimationBackgroundSurface = new DimLayer(service, this);
     }
@@ -152,6 +153,7 @@
     int remove() {
         mAnimationBackgroundSurface.destroySurface();
         mDimLayer.destroySurface();
+        EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
         return mStackBox.remove();
     }
 
@@ -269,6 +271,8 @@
                 for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
                     final WindowState win = windows.get(winNdx);
                     if (!resizingWindows.contains(win)) {
+                        if (WindowManagerService.DEBUG_RESIZE) Slog.d(TAG,
+                                "setBounds: Resizing " + win);
                         resizingWindows.add(win);
                     }
                     win.mUnderStatusBar = underStatusBar;
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index cd46bb8..91f15f3 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -245,7 +245,7 @@
                                 mForceHiding = KEYGUARD_ANIMATING_OUT;
                             }
                         } else {
-                            mForceHiding = KEYGUARD_SHOWN;
+                            mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN;
                         }
                     }
                     if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 63e09db..737f384 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -437,8 +437,15 @@
     int mRotation = 0;
     int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
     boolean mAltOrientation = false;
-    ArrayList<IRotationWatcher> mRotationWatchers
-            = new ArrayList<IRotationWatcher>();
+    class RotationWatcher {
+        IRotationWatcher watcher;
+        IBinder.DeathRecipient dr;
+        RotationWatcher(IRotationWatcher w, IBinder.DeathRecipient d) {
+            watcher = w;
+            dr = d;
+        }
+    }
+    ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<RotationWatcher>();
     int mDeferredRotationPauseCount;
 
     int mSystemDecorLayer = 0;
@@ -578,10 +585,13 @@
         private boolean mUpdateRotation = false;
         boolean mWallpaperActionPending = false;
 
-        private static final int DISPLAY_CONTENT_UNKNOWN = 0;
-        private static final int DISPLAY_CONTENT_MIRROR = 1;
-        private static final int DISPLAY_CONTENT_UNIQUE = 2;
-        private int mDisplayHasContent = DISPLAY_CONTENT_UNKNOWN;
+        // Set to true when the display contains content to show the user.
+        // When false, the display manager may choose to mirror or blank the display.
+        boolean mDisplayHasContent = false;
+
+        // Only set while traversing the default display based on its content.
+        // Affects the behavior of mirroring on secondary displays.
+        boolean mObscureApplicationContentOnSecondaryDisplays = false;
     }
     final LayoutFields mInnerFields = new LayoutFields();
 
@@ -2370,6 +2380,11 @@
     }
 
     public void removeWindowLocked(Session session, WindowState win) {
+        removeWindowLocked(session, win, false);
+    }
+
+    private void removeWindowLocked(Session session, WindowState win,
+            boolean forceRemove) {
         if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
             if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win);
             removeStartingWindowTimeout(win.mAppToken);
@@ -2420,7 +2435,7 @@
                     mDisplayMagnifier.onWindowTransitionLocked(win, transit);
                 }
             }
-            if (win.mExiting || win.mWinAnimator.isAnimating()) {
+            if (!forceRemove && (win.mExiting || win.mWinAnimator.isAnimating())) {
                 // The exit animation is running... wait for it!
                 //Slog.i(TAG, "*** Running exit animation...");
                 win.mExiting = true;
@@ -3394,16 +3409,17 @@
         if (stack == null) {
             throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
         }
+        EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
         Task task = new Task(atoken, stack, userId);
         mTaskIdToTask.put(taskId, task);
         stack.addTask(task, true);
-        stack.getDisplayContent().moveStack(stack, true);
         return task;
     }
 
     @Override
     public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
-            int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId) {
+            int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
+            int configChanges) {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "addAppToken()")) {
             throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3435,6 +3451,8 @@
             atoken.appFullscreen = fullscreen;
             atoken.showWhenLocked = showWhenLocked;
             atoken.requestedOrientation = requestedOrientation;
+            atoken.layoutConfigChanges = (configChanges &
+                    (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
             if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken
                     + " to stack=" + stackId + " task=" + taskId + " at " + addPos);
 
@@ -4303,10 +4321,6 @@
             // If we are preparing an app transition, then delay changing
             // the visibility of this token until we execute that transition.
             if (okToDisplay() && mAppTransition.isTransitionSet()) {
-                // Already in requested state, don't do anything more.
-                if (wtoken.hiddenRequested != visible) {
-                    return;
-                }
                 wtoken.hiddenRequested = !visible;
 
                 if (!wtoken.startingDisplayed) {
@@ -4792,7 +4806,6 @@
                     displayContent.moveHomeStackBox(isHomeStackTask);
                 }
                 stack.moveTaskToTop(task);
-                displayContent.moveStack(stack, true);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4846,7 +4859,6 @@
                         weight);
                 if (stack != null) {
                     mStackIdToStack.put(stackId, stack);
-                    displayContent.moveStack(stack, true);
                     performLayoutAndPlaceSurfacesLocked();
                     return;
                 }
@@ -4878,6 +4890,7 @@
                 return;
             }
             final TaskStack stack = task.mStack;
+            EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, taskId, "removeTask");
             stack.removeTask(task);
             stack.getDisplayContent().layoutNeeded = true;
         }
@@ -5992,7 +6005,7 @@
 
         for (int i=mRotationWatchers.size()-1; i>=0; i--) {
             try {
-                mRotationWatchers.get(i).onRotationChanged(rotation);
+                mRotationWatchers.get(i).watcher.onRotationChanged(rotation);
             } catch (RemoteException e) {
             }
         }
@@ -6024,10 +6037,10 @@
             public void binderDied() {
                 synchronized (mWindowMap) {
                     for (int i=0; i<mRotationWatchers.size(); i++) {
-                        if (watcherBinder == mRotationWatchers.get(i).asBinder()) {
-                            IRotationWatcher removed = mRotationWatchers.remove(i);
+                        if (watcherBinder == mRotationWatchers.get(i).watcher.asBinder()) {
+                            RotationWatcher removed = mRotationWatchers.remove(i);
                             if (removed != null) {
-                                removed.asBinder().unlinkToDeath(this, 0);
+                                removed.watcher.asBinder().unlinkToDeath(this, 0);
                             }
                             i--;
                         }
@@ -6039,7 +6052,7 @@
         synchronized (mWindowMap) {
             try {
                 watcher.asBinder().linkToDeath(dr, 0);
-                mRotationWatchers.add(watcher);
+                mRotationWatchers.add(new RotationWatcher(watcher, dr));
             } catch (RemoteException e) {
                 // Client died, no cleanup needed.
             }
@@ -6053,9 +6066,13 @@
         final IBinder watcherBinder = watcher.asBinder();
         synchronized (mWindowMap) {
             for (int i=0; i<mRotationWatchers.size(); i++) {
-                if (watcherBinder == mRotationWatchers.get(i).asBinder()) {
-                    mRotationWatchers.remove(i);
-                    i--;
+                RotationWatcher rotationWatcher = mRotationWatchers.get(i);
+                if (watcherBinder == rotationWatcher.watcher.asBinder()) {
+                    RotationWatcher removed = mRotationWatchers.remove(i);
+                    if (removed != null) {
+                        removed.watcher.asBinder().unlinkToDeath(removed.dr, 0);
+                        i--;
+                    }
                 }
             }
         }
@@ -8269,7 +8286,9 @@
             // windows, since that means "perform layout as normal,
             // just don't display").
             if (!gone || !win.mHaveFrame || win.mLayoutNeeded
-                    || (win.mAttrs.type == TYPE_KEYGUARD && win.isConfigChanged())
+                    || ((win.isConfigChanged() || win.setInsetsChanged()) &&
+                            (win.mAttrs.type == TYPE_KEYGUARD ||
+                            win.mAppToken != null && win.mAppToken.layoutConfigChanges))
                     || win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
                 if (!win.mLayoutAttached) {
                     if (initial) {
@@ -8703,12 +8722,7 @@
     private void updateResizingWindows(final WindowState w) {
         final WindowStateAnimator winAnimator = w.mWinAnimator;
         if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq) {
-            w.mOverscanInsetsChanged |=
-                    !w.mLastOverscanInsets.equals(w.mOverscanInsets);
-            w.mContentInsetsChanged |=
-                    !w.mLastContentInsets.equals(w.mContentInsets);
-            w.mVisibleInsetsChanged |=
-                    !w.mLastVisibleInsets.equals(w.mVisibleInsets);
+            w.setInsetsChanged();
             boolean configChanged = w.isConfigChanged();
             if (DEBUG_CONFIGURATION && configChanged) {
                 Slog.v(TAG, "Win " + w + " config changed: "
@@ -8783,6 +8797,14 @@
         final WindowManager.LayoutParams attrs = w.mAttrs;
         final int attrFlags = attrs.flags;
         final boolean canBeSeen = w.isDisplayedLw();
+        final boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
+
+        if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
+            // This window completely covers everything behind it,
+            // so we want to leave all of them as undimmed (for
+            // performance reasons).
+            mInnerFields.mObscured = true;
+        }
 
         if (w.mHasSurface) {
             if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) {
@@ -8811,22 +8833,24 @@
             }
 
             if (canBeSeen) {
-                if (type == TYPE_DREAM || type == TYPE_KEYGUARD) {
-                    mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_MIRROR;
-                } else if (mInnerFields.mDisplayHasContent
-                        == LayoutFields.DISPLAY_CONTENT_UNKNOWN) {
-                    mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNIQUE;
+                // This function assumes that the contents of the default display are
+                // processed first before secondary displays.
+                if (w.mDisplayContent.isDefaultDisplay) {
+                    // While a dream or keyguard is showing, obscure ordinary application
+                    // content on secondary displays (by forcibly enabling mirroring unless
+                    // there is other content we want to show) but still allow opaque
+                    // keyguard dialogs to be shown.
+                    if (type == TYPE_DREAM || type == TYPE_KEYGUARD) {
+                        mInnerFields.mObscureApplicationContentOnSecondaryDisplays = true;
+                    }
+                    mInnerFields.mDisplayHasContent = true;
+                } else if (!mInnerFields.mObscureApplicationContentOnSecondaryDisplays
+                        || (mInnerFields.mObscured && type == TYPE_KEYGUARD_DIALOG)) {
+                    // Allow full screen keyguard presentation dialogs to be seen.
+                    mInnerFields.mDisplayHasContent = true;
                 }
             }
         }
-
-        boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
-        if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
-            // This window completely covers everything behind it,
-            // so we want to leave all of them as undimmed (for
-            // performance reasons).
-            mInnerFields.mObscured = true;
-        }
     }
 
     private void handleFlagDimBehind(WindowState w, int innerDw, int innerDh) {
@@ -8904,7 +8928,7 @@
         mInnerFields.mScreenBrightness = -1;
         mInnerFields.mButtonBrightness = -1;
         mInnerFields.mUserActivityTimeout = -1;
-        mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN;
+        mInnerFields.mObscureApplicationContentOnSecondaryDisplays = false;
 
         mTransactionSequence++;
 
@@ -8939,10 +8963,8 @@
                 final int innerDh = displayInfo.appHeight;
                 final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
 
-                // Reset for each display unless we are forcing mirroring.
-                if (mInnerFields.mDisplayHasContent != LayoutFields.DISPLAY_CONTENT_MIRROR) {
-                    mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN;
-                }
+                // Reset for each display.
+                mInnerFields.mDisplayHasContent = false;
 
                 int repeats = 0;
                 do {
@@ -9151,20 +9173,8 @@
                     updateResizingWindows(w);
                 }
 
-                final boolean hasUniqueContent;
-                switch (mInnerFields.mDisplayHasContent) {
-                    case LayoutFields.DISPLAY_CONTENT_MIRROR:
-                        hasUniqueContent = isDefaultDisplay;
-                        break;
-                    case LayoutFields.DISPLAY_CONTENT_UNIQUE:
-                        hasUniqueContent = true;
-                        break;
-                    case LayoutFields.DISPLAY_CONTENT_UNKNOWN:
-                    default:
-                        hasUniqueContent = false;
-                        break;
-                }
-                mDisplayManagerService.setDisplayHasContent(displayId, hasUniqueContent,
+                mDisplayManagerService.setDisplayHasContent(displayId,
+                        mInnerFields.mDisplayHasContent,
                         true /* inTraversal, must call performTraversalInTrans... below */);
 
                 getDisplayContentLocked(displayId).stopDimmingIfNeeded();
@@ -10846,7 +10856,7 @@
             WindowList windows = displayContent.getWindowList();
             while (!windows.isEmpty()) {
                 final WindowState win = windows.get(windows.size() - 1);
-                removeWindowLocked(win.mSession, win);
+                removeWindowLocked(win.mSession, win, true);
             }
         }
         mAnimator.removeDisplayLocked(displayId);
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 2d08792..4d53cea 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -701,6 +701,13 @@
         return mAppToken != null ? mAppToken.appToken : null;
     }
 
+    boolean setInsetsChanged() {
+        mOverscanInsetsChanged |= !mLastOverscanInsets.equals(mOverscanInsets);
+        mContentInsetsChanged |= !mLastContentInsets.equals(mContentInsets);
+        mVisibleInsetsChanged |= !mLastVisibleInsets.equals(mVisibleInsets);
+        return mOverscanInsetsChanged || mContentInsetsChanged || mVisibleInsetsChanged;
+    }
+
     public int getDisplayId() {
         return mDisplayContent.getDisplayId();
     }
diff --git a/services/jni/com_android_server_connectivity_Vpn.cpp b/services/jni/com_android_server_connectivity_Vpn.cpp
index ab8c959..bf34a74 100644
--- a/services/jni/com_android_server_connectivity_Vpn.cpp
+++ b/services/jni/com_android_server_connectivity_Vpn.cpp
@@ -302,15 +302,6 @@
     return ifr4.ifr_flags;
 }
 
-static int bind_to_interface(int socket, const char *name)
-{
-    if (setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) {
-        ALOGE("Cannot bind socket to %s: %s", name, strerror(errno));
-        return SYSTEM_ERROR;
-    }
-    return 0;
-}
-
 //------------------------------------------------------------------------------
 
 static void throwException(JNIEnv *env, int error, const char *message)
@@ -433,19 +424,6 @@
     return flags;
 }
 
-static void protect(JNIEnv *env, jobject thiz, jint socket, jstring jName)
-{
-    const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL;
-    if (!name) {
-        jniThrowNullPointerException(env, "name");
-        return;
-    }
-    if (bind_to_interface(socket, name) < 0) {
-        throwException(env, SYSTEM_ERROR, "Cannot protect socket");
-    }
-    env->ReleaseStringUTFChars(jName, name);
-}
-
 //------------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
@@ -455,7 +433,6 @@
     {"jniSetRoutes", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setRoutes},
     {"jniReset", "(Ljava/lang/String;)V", (void *)reset},
     {"jniCheck", "(Ljava/lang/String;)I", (void *)check},
-    {"jniProtect", "(ILjava/lang/String;)V", (void *)protect},
 };
 
 int register_android_server_connectivity_Vpn(JNIEnv *env)
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
index 37176d6..910b685 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -136,10 +136,11 @@
                 "authority1", b1, soon + 100, soonFlex + 100, unimportant, unimportant, true);
 
         assertTrue(op1.compareTo(op2) == -1);
-        assertTrue("less than not transitive.", op2.compareTo(op1) == 1);
-        assertTrue(op1.compareTo(op3) == 1);
-        assertTrue("greater than not transitive. ", op3.compareTo(op1) == -1);
-        assertTrue("overlapping intervals not the same.", op1.compareTo(op4) == 0);
-        assertTrue("equality not transitive.", op4.compareTo(op1) == 0);
+        assertTrue("Less than not transitive.", op2.compareTo(op1) == 1);
+        assertEquals("Expedited not smaller than non-expedited.", -1, op1.compareTo(op3));
+        assertEquals("Greater than not transitive for expedited. ", 1, op3.compareTo(op1));
+        assertTrue("overlapping intervals not compared based on start interval.",
+                op1.compareTo(op4) == -1);
+        assertTrue("overlapping interval comparison not transitive.", op4.compareTo(op1) == 1);
     }
 }
diff --git a/telephony/java/android/telephony/CallStateListener.java b/telephony/java/android/telephony/CallStateListener.java
new file mode 100644
index 0000000..e2ffbfa
--- /dev/null
+++ b/telephony/java/android/telephony/CallStateListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.PrivateApi;
+
+/** @hide */
+@PrivateApi
+public interface CallStateListener {
+    /**
+     * Notify of a new or updated call.
+     * Any time the state of a call is updated, it will alert any listeners. This includes changes
+     * of state such as when a call is put on hold or conferenced.
+     *
+     * @param callId a unique ideCntifier for a given call that can be used to track state changes
+     * @param state the new state of the call.
+     *              {@see com.android.services.telephony.common.Call$State}
+     * @param number the phone number of the call. For some states, this may be blank. However, it
+     *               will be populated for any initial state.
+     */
+    public void onCallStateChanged(int callId, int state, String number);
+}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index ab429fd..e3a1aa6 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1957,6 +1957,27 @@
     }
 
     /**
+     * Process phone number for CDMA, converting plus code using the home network number format.
+     * This is used for outgoing SMS messages.
+     *
+     * @param dialStr the original dial string
+     * @return the converted dial string
+     * @hide for internal use
+     */
+    public static String cdmaCheckAndProcessPlusCodeForSms(String dialStr) {
+        if (!TextUtils.isEmpty(dialStr)) {
+            if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) {
+                String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, "");
+                if (!TextUtils.isEmpty(defaultIso)) {
+                    int format = getFormatTypeFromCountryCode(defaultIso);
+                    return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, format, format);
+                }
+            }
+        }
+        return dialStr;
+    }
+
+    /**
      * This function should be called from checkAndProcessPlusCode only
      * And it is used for test purpose also.
      *
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8f17e72..3ab7000f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16,17 +16,22 @@
 
 package android.telephony;
 
+import android.annotation.PrivateApi;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.telephony.Rlog;
+import android.util.Log;
 
 import com.android.internal.telephony.IPhoneSubInfo;
 import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.ITelephonyListener;
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
@@ -34,6 +39,7 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -60,8 +66,41 @@
     private static final String TAG = "TelephonyManager";
 
     private static ITelephonyRegistry sRegistry;
+
+    private final HashMap<CallStateListener,Listener> mListeners
+            = new HashMap<CallStateListener,Listener>();
     private final Context mContext;
 
+    private static class Listener extends ITelephonyListener.Stub {
+        final CallStateListener mListener;
+        private static final int WHAT = 1;
+
+        private Handler mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                mListener.onCallStateChanged(msg.arg1, msg.arg2, (String)msg.obj);
+            }
+        };
+
+        Listener(CallStateListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void onUpdate(final int callId, final int state, final String number) {
+            if (mHandler != null) {
+                mHandler.sendMessage(mHandler.obtainMessage(WHAT, callId, state, number));
+            }
+        }
+
+        void clearQueue() {
+            mHandler.removeMessages(WHAT);
+
+            // Don't accept more incoming binder calls either.
+            mHandler = null;
+        }
+    }
+
     /** @hide */
     public TelephonyManager(Context context) {
         Context appContext = context.getApplicationContext();
@@ -1439,4 +1478,397 @@
         return mContext.getResources().getString(
                 com.android.internal.R.string.config_mms_user_agent_profile_url);
     }
+
+    /** @hide */
+    @PrivateApi
+    public void dial(String number) {
+        try {
+            getITelephony().dial(number);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#dial", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void call(String callingPackage, String number) {
+        try {
+            getITelephony().call(callingPackage, number);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#call", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean showCallScreen() {
+        try {
+            return getITelephony().showCallScreen();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#showCallScreen", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean showCallScreenWithDialpad(boolean showDialpad) {
+        try {
+            return getITelephony().showCallScreenWithDialpad(showDialpad);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#showCallScreenWithDialpad", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean endCall() {
+        try {
+            return getITelephony().endCall();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#endCall", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void answerRingingCall() {
+        try {
+            getITelephony().answerRingingCall();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#answerRingingCall", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void toggleHold() {
+        try {
+            getITelephony().toggleHold();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#toggleHold", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void merge() {
+        try {
+            getITelephony().merge();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#merge", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void swap() {
+        try {
+            getITelephony().swap();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#swap", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void mute(boolean mute) {
+        try {
+            getITelephony().mute(mute);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#mute", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void silenceRinger() {
+        try {
+            getITelephony().silenceRinger();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#silenceRinger", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean isOffhook() {
+        try {
+            return getITelephony().isOffhook();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isOffhook", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean isRinging() {
+        try {
+            return getITelephony().isRinging();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isRinging", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean isIdle() {
+        try {
+            return getITelephony().isIdle();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isIdle", e);
+        }
+        return true;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean isRadioOn() {
+        try {
+            return getITelephony().isRadioOn();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isRadioOn", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean isSimPinEnabled() {
+        try {
+            return getITelephony().isSimPinEnabled();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isSimPinEnabled", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void cancelMissedCallsNotification() {
+        try {
+            getITelephony().cancelMissedCallsNotification();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#cancelMissedCallsNotification", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean supplyPin(String pin) {
+        try {
+            return getITelephony().supplyPin(pin);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#supplyPin", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean supplyPuk(String puk, String pin) {
+        try {
+            return getITelephony().supplyPuk(puk, pin);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#supplyPuk", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public int[] supplyPinReportResult(String pin) {
+        try {
+            return getITelephony().supplyPinReportResult(pin);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#supplyPinReportResult", e);
+        }
+        return new int[0];
+    }
+
+    /** @hide */
+    @PrivateApi
+    public int[] supplyPukReportResult(String puk, String pin) {
+        try {
+            return getITelephony().supplyPukReportResult(puk, pin);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#]", e);
+        }
+        return new int[0];
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean handlePinMmi(String dialString) {
+        try {
+            return getITelephony().handlePinMmi(dialString);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#handlePinMmi", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void toggleRadioOnOff() {
+        try {
+            getITelephony().toggleRadioOnOff();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#toggleRadioOnOff", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean setRadio(boolean turnOn) {
+        try {
+            return getITelephony().setRadio(turnOn);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#setRadio", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean setRadioPower(boolean turnOn) {
+        try {
+            return getITelephony().setRadioPower(turnOn);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#setRadioPower", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void updateServiceLocation() {
+        try {
+            getITelephony().updateServiceLocation();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#updateServiceLocation", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public int enableApnType(String type) {
+        try {
+            return getITelephony().enableApnType(type);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#enableApnType", e);
+        }
+        return PhoneConstants.APN_REQUEST_FAILED;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public int disableApnType(String type) {
+        try {
+            return getITelephony().disableApnType(type);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#disableApnType", e);
+        }
+        return PhoneConstants.APN_REQUEST_FAILED;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean enableDataConnectivity() {
+        try {
+            return getITelephony().enableDataConnectivity();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#enableDataConnectivity", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean disableDataConnectivity() {
+        try {
+            return getITelephony().disableDataConnectivity();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#disableDataConnectivity", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean isDataConnectivityPossible() {
+        try {
+            return getITelephony().isDataConnectivityPossible();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isDataConnectivityPossible", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public boolean needsOtaServiceProvisioning() {
+        try {
+            return getITelephony().needsOtaServiceProvisioning();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#needsOtaServiceProvisioning", e);
+        }
+        return false;
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void playDtmfTone(char digit, boolean timedShortCode) {
+        try {
+            getITelephony().playDtmfTone(digit, timedShortCode);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#playDtmfTone", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void stopDtmfTone() {
+        try {
+            getITelephony().stopDtmfTone();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#stopDtmfTone", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void addCallStateListener(CallStateListener listener) {
+        try {
+            if (listener == null) {
+                throw new RuntimeException("Listener can't be null");
+            }
+            if (!mListeners.containsKey(listener)) {
+                final Listener l = new Listener(listener);
+                mListeners.put(listener, l);
+                getITelephony().addListener(l);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#addListener", e);
+        }
+    }
+
+    /** @hide */
+    @PrivateApi
+    public void removeCallStateListener(CallStateListener listener) {
+        try {
+            final Listener l = mListeners.remove(listener);
+            if (l != null) {
+                // Make sure that no callbacks that are already in flight come.
+                l.clearQueue();
+                getITelephony().removeListener(l);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#removeListener", e);
+        }
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 7bd2c84..9c73059 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -17,9 +17,12 @@
 package com.android.internal.telephony;
 
 import android.os.Bundle;
-import java.util.List;
-import android.telephony.NeighboringCellInfo;
 import android.telephony.CellInfo;
+import android.telephony.NeighboringCellInfo;
+
+import com.android.internal.telephony.ITelephonyListener;
+
+import java.util.List;
 
 /**
  * Interface used to interact with the phone.  Mostly this is used by the
@@ -324,5 +327,48 @@
      * Sets minimum time in milli-seconds between onCellInfoChanged
      */
     void setCellInfoListRate(int rateInMillis);
+
+    /**
+     * Put a call on hold.
+     */
+     void toggleHold();
+
+     /**
+      * Merge foreground and background calls.
+      */
+     void merge();
+
+     /**
+      * Swap foreground and background calls.
+      */
+     void swap();
+
+     /**
+      * Mute the phone.
+      */
+     void mute(boolean mute);
+
+    /**
+     * Start playing DTMF tone for the specified digit.
+     *
+     * @param digit the digit that corresponds with the desired tone.
+     * @param timedShortcode whether the specified digit should be played as a timed short code.
+     */
+     void playDtmfTone(char digit, boolean timedShortCode);
+
+     /**
+      * Stop playing DTMF tones.
+      */
+     void stopDtmfTone();
+
+     /**
+       * Register a callback.
+       */
+      void addListener(ITelephonyListener listener);
+
+      /**
+       * Unregister a callback.
+       */
+      void removeListener(ITelephonyListener listener);
 }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyListener.aidl b/telephony/java/com/android/internal/telephony/ITelephonyListener.aidl
new file mode 100644
index 0000000..c226217
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ITelephonyListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+/**
+ * Interface used to register a listener that gets more detailed call state information than
+ * {@link android.telephony.PhoneStateListener}
+ *
+ * {@hide}
+ */
+oneway interface ITelephonyListener {
+    void onUpdate(int callId, int state, String number);
+}
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 62f6aff..dfb8070 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -85,11 +85,13 @@
         // do initial app launch, without force stopping
         for (String app : mNameToResultKey.keySet()) {
             long launchTime = startApp(app, false);
-            if (launchTime <=0 ) {
+            if (launchTime <= 0) {
                 mNameToLaunchTime.put(app, -1L);
                 // simply pass the app if launch isn't successful
                 // error should have already been logged by startApp
                 continue;
+            } else {
+                mNameToLaunchTime.put(app, launchTime);
             }
             sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
             closeApp(app, false);
@@ -98,9 +100,9 @@
         // do the real app launch now
         for (int i = 0; i < mLaunchIterations; i++) {
             for (String app : mNameToResultKey.keySet()) {
-                long totalLaunchTime = mNameToLaunchTime.get(app);
+                long prevLaunchTime = mNameToLaunchTime.get(app);
                 long launchTime = 0;
-                if (totalLaunchTime < 0) {
+                if (prevLaunchTime < 0) {
                     // skip if the app has previous failures
                     continue;
                 }
@@ -110,18 +112,19 @@
                     mNameToLaunchTime.put(app, -1L);
                     continue;
                 }
-                totalLaunchTime += launchTime;
-                mNameToLaunchTime.put(app, totalLaunchTime);
+                // keep the min launch time
+                if (launchTime < prevLaunchTime) {
+                    mNameToLaunchTime.put(app, launchTime);
+                }
                 sleep(POST_LAUNCH_IDLE_TIMEOUT);
                 closeApp(app, true);
                 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
             }
         }
         for (String app : mNameToResultKey.keySet()) {
-            long totalLaunchTime = mNameToLaunchTime.get(app);
-            if (totalLaunchTime != -1) {
-                mResult.putDouble(mNameToResultKey.get(app),
-                        ((double) totalLaunchTime) / mLaunchIterations);
+            long launchTime = mNameToLaunchTime.get(app);
+            if (launchTime != -1) {
+                mResult.putLong(mNameToResultKey.get(app), launchTime);
             }
         }
         instrumentation.sendStatus(0, mResult);
diff --git a/tests/LegacyRestoreTest/README b/tests/LegacyRestoreTest/README
new file mode 100644
index 0000000..cdd157e
--- /dev/null
+++ b/tests/LegacyRestoreTest/README
@@ -0,0 +1,18 @@
+The file "jbmr2-encrypted-settings-abcd.ab" in this directory is an encrypted
+"adb backup" archive of the settings provider package.  It was generated on a
+Nexus 4 running Android 4.3 (API 18), and so predates the Android 4.4 changes
+to the PBKDF2 implementation.  The archive's encryption password, entered on-screen,
+is "abcd" (with no quotation marks).
+
+'adb restore' decrypts and applies the restored archive successfully on a device
+running Android 4.3, but fails to restore correctly on a device running Android 4.4,
+reporting an invalid password in logcat.  This is the situation reported in bug
+<https://code.google.com/p/android/issues/detail?id=63880>.
+
+The file "kk-fixed-encrypted-settings-abcd.ab" is a similar encrypted "adb backup"
+archive, using the same key, generated on a Nexus 4 running Android 4.4 with a fix
+to this bug in place.  This archive should be successfully restorable on any
+version of Android which incorporates the fix.
+
+These archives can be used as an ongoing test to verify that historical encrypted
+archives from various points in Android's history can be successfully restored.
diff --git a/tests/LegacyRestoreTest/jbmr2-encrypted-settings-abcd.ab b/tests/LegacyRestoreTest/jbmr2-encrypted-settings-abcd.ab
new file mode 100644
index 0000000..192dcf5
--- /dev/null
+++ b/tests/LegacyRestoreTest/jbmr2-encrypted-settings-abcd.ab
Binary files differ
diff --git a/tests/LegacyRestoreTest/kk-fixed-encrypted-settings-abcd.ab b/tests/LegacyRestoreTest/kk-fixed-encrypted-settings-abcd.ab
new file mode 100644
index 0000000..bf2b558
--- /dev/null
+++ b/tests/LegacyRestoreTest/kk-fixed-encrypted-settings-abcd.ab
Binary files differ
diff --git a/tests/RemoteDisplayProvider/Android.mk b/tests/RemoteDisplayProvider/Android.mk
new file mode 100644
index 0000000..2f4b343
--- /dev/null
+++ b/tests/RemoteDisplayProvider/Android.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Build the application.
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := RemoteDisplayProviderTest
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res
+LOCAL_JAVA_LIBRARIES := com.android.media.remotedisplay
+LOCAL_CERTIFICATE := platform
+include $(BUILD_PACKAGE)
diff --git a/tests/RemoteDisplayProvider/AndroidManifest.xml b/tests/RemoteDisplayProvider/AndroidManifest.xml
new file mode 100644
index 0000000..afb7c78
--- /dev/null
+++ b/tests/RemoteDisplayProvider/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.media.remotedisplay.test" >
+
+    <uses-sdk android:minSdkVersion="19" />
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+
+    <application android:label="@string/app_name"
+            android:icon="@drawable/ic_app">
+        <uses-library android:name="com.android.media.remotedisplay"
+                android:required="true" />
+
+        <service android:name=".RemoteDisplayProviderService"
+                android:label="@string/app_name"
+                android:exported="true"
+                android:permission="android.permission.BIND_REMOTE_DISPLAY">
+            <intent-filter>
+                <action android:name="com.android.media.remotedisplay.RemoteDisplayProvider"/>
+            </intent-filter>
+        </service>
+
+    </application>
+</manifest>
diff --git a/tests/RemoteDisplayProvider/README b/tests/RemoteDisplayProvider/README
new file mode 100644
index 0000000..8bf0130
--- /dev/null
+++ b/tests/RemoteDisplayProvider/README
@@ -0,0 +1,16 @@
+This directory contains sample code to test system integration with
+remote display providers using the API declared by the
+com.android.media.remotedisplay.jar library.
+
+--- DESCRIPTION ---
+
+The application registers a service that publishes a few different
+remote display routes.  Behavior can be controlled by modifying the
+code.
+
+To exercise the provider, use System UI features for connecting to
+wireless displays or launch an activity that uses the MediaRouter,
+such as the PresentationWithMediaRouterActivity in ApiDemos.
+
+This code is mainly intended for development and not meant to be
+used as an example implementation of a robust remote display provider.
diff --git a/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png b/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png
new file mode 100755
index 0000000..66a1984
--- /dev/null
+++ b/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png
Binary files differ
diff --git a/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png b/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png
new file mode 100644
index 0000000..5ae7701
--- /dev/null
+++ b/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png
Binary files differ
diff --git a/core/res/res/values-ja/bools.xml b/tests/RemoteDisplayProvider/res/values/strings.xml
similarity index 83%
rename from core/res/res/values-ja/bools.xml
rename to tests/RemoteDisplayProvider/res/values/strings.xml
index 59cf744..dd82d2c 100644
--- a/core/res/res/values-ja/bools.xml
+++ b/tests/RemoteDisplayProvider/res/values/strings.xml
@@ -14,7 +14,6 @@
      limitations under the License.
 -->
 
-<resources>
-    <bool name="flip_controller_fallback_keys">true</bool>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">Remote Display Provider Test</string>
 </resources>
-
diff --git a/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java b/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java
new file mode 100644
index 0000000..611d7e4
--- /dev/null
+++ b/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.remotedisplay.test;
+
+import com.android.media.remotedisplay.RemoteDisplay;
+import com.android.media.remotedisplay.RemoteDisplayProvider;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * Remote display provider implementation that publishes working routes.
+ */
+public class RemoteDisplayProviderService extends Service {
+    private static final String TAG = "RemoteDisplayProviderTest";
+
+    private Provider mProvider;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (intent.getAction().equals(RemoteDisplayProvider.SERVICE_INTERFACE)) {
+            if (mProvider == null) {
+                mProvider = new Provider();
+                return mProvider.getBinder();
+            }
+        }
+        return null;
+    }
+
+    final class Provider extends RemoteDisplayProvider {
+        private RemoteDisplay mTestDisplay1; // variable volume
+        private RemoteDisplay mTestDisplay2; // fixed volume
+        private RemoteDisplay mTestDisplay3; // not available
+        private RemoteDisplay mTestDisplay4; // in use
+        private RemoteDisplay mTestDisplay5; // available but ignores request to connect
+        private RemoteDisplay mTestDisplay6; // available but never finishes connecting
+        private RemoteDisplay mTestDisplay7; // blinks in and out of existence
+        private RemoteDisplay mTestDisplay8; // available but connecting attempt flakes out
+        private RemoteDisplay mTestDisplay9; // available but connection flakes out
+        private RemoteDisplay mTestDisplay10; // available and reconnects periodically
+
+        private final Handler mHandler;
+        private boolean mBlinking;
+
+        public Provider() {
+            super(RemoteDisplayProviderService.this);
+            mHandler = new Handler(getMainLooper());
+        }
+
+        @Override
+        public void onDiscoveryModeChanged(int mode) {
+            Log.d(TAG, "onDiscoveryModeChanged: mode=" + mode);
+
+            if (mode != DISCOVERY_MODE_NONE) {
+                // When discovery begins, go find all of the routes.
+                if (mTestDisplay1 == null) {
+                    mTestDisplay1 = new RemoteDisplay("testDisplay1",
+                            "Test Display 1 (variable)");
+                    mTestDisplay1.setDescription("Variable volume");
+                    mTestDisplay1.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                    mTestDisplay1.setVolume(10);
+                    mTestDisplay1.setVolumeHandling(RemoteDisplay.PLAYBACK_VOLUME_VARIABLE);
+                    mTestDisplay1.setVolumeMax(15);
+                    addDisplay(mTestDisplay1);
+                }
+                if (mTestDisplay2 == null) {
+                    mTestDisplay2 = new RemoteDisplay("testDisplay2",
+                            "Test Display 2 (fixed)");
+                    mTestDisplay2.setDescription("Fixed volume");
+                    mTestDisplay2.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                    addDisplay(mTestDisplay2);
+                }
+                if (mTestDisplay3 == null) {
+                    mTestDisplay3 = new RemoteDisplay("testDisplay3",
+                            "Test Display 3 (unavailable)");
+                    mTestDisplay3.setDescription("Always unavailable");
+                    mTestDisplay3.setStatus(RemoteDisplay.STATUS_NOT_AVAILABLE);
+                    addDisplay(mTestDisplay3);
+                }
+                if (mTestDisplay4 == null) {
+                    mTestDisplay4 = new RemoteDisplay("testDisplay4",
+                            "Test Display 4 (in-use)");
+                    mTestDisplay4.setDescription("Always in-use");
+                    mTestDisplay4.setStatus(RemoteDisplay.STATUS_IN_USE);
+                    addDisplay(mTestDisplay4);
+                }
+                if (mTestDisplay5 == null) {
+                    mTestDisplay5 = new RemoteDisplay("testDisplay5",
+                            "Test Display 5 (connect ignored)");
+                    mTestDisplay5.setDescription("Ignores connect");
+                    mTestDisplay5.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                    addDisplay(mTestDisplay5);
+                }
+                if (mTestDisplay6 == null) {
+                    mTestDisplay6 = new RemoteDisplay("testDisplay6",
+                            "Test Display 6 (connect hangs)");
+                    mTestDisplay6.setDescription("Never finishes connecting");
+                    mTestDisplay6.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                    addDisplay(mTestDisplay6);
+                }
+                if (mTestDisplay8 == null) {
+                    mTestDisplay8 = new RemoteDisplay("testDisplay8",
+                            "Test Display 8 (flaky when connecting)");
+                    mTestDisplay8.setDescription("Aborts spontaneously while connecting");
+                    mTestDisplay8.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                    addDisplay(mTestDisplay8);
+                }
+                if (mTestDisplay9 == null) {
+                    mTestDisplay9 = new RemoteDisplay("testDisplay9",
+                            "Test Display 9 (flaky when connected)");
+                    mTestDisplay9.setDescription("Aborts spontaneously while connected");
+                    mTestDisplay9.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                    addDisplay(mTestDisplay9);
+                }
+                if (mTestDisplay10 == null) {
+                    mTestDisplay10 = new RemoteDisplay("testDisplay10",
+                            "Test Display 10 (reconnects periodically)");
+                    mTestDisplay10.setDescription("Reconnects spontaneously");
+                    mTestDisplay10.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                    addDisplay(mTestDisplay10);
+                }
+            } else {
+                // When discovery ends, go hide some of the routes we can't actually use.
+                // This isn't something a normal route provider would do though.
+                // The routes will usually stay published.
+                if (mTestDisplay3 != null) {
+                    removeDisplay(mTestDisplay3);
+                    mTestDisplay3 = null;
+                }
+                if (mTestDisplay4 != null) {
+                    removeDisplay(mTestDisplay4);
+                    mTestDisplay4 = null;
+                }
+            }
+
+            // When active discovery is on, pretend there's a route that we can't quite
+            // reach that blinks in and out of existence.
+            if (mode == DISCOVERY_MODE_ACTIVE) {
+                if (!mBlinking) {
+                    mBlinking = true;
+                    mHandler.post(mBlink);
+                }
+            } else {
+                mBlinking = false;
+            }
+        }
+
+        @Override
+        public void onConnect(final RemoteDisplay display) {
+            Log.d(TAG, "onConnect: display.getId()=" + display.getId());
+
+            if (display == mTestDisplay1 || display == mTestDisplay2) {
+                display.setStatus(RemoteDisplay.STATUS_CONNECTING);
+                updateDisplay(display);
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        if ((display == mTestDisplay1 || display == mTestDisplay2)
+                                && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) {
+                            display.setStatus(RemoteDisplay.STATUS_CONNECTED);
+                            updateDisplay(display);
+                        }
+                    }
+                }, 2000);
+            } else if (display == mTestDisplay6 || display == mTestDisplay7) {
+                // never finishes connecting
+                display.setStatus(RemoteDisplay.STATUS_CONNECTING);
+                updateDisplay(display);
+            } else if (display == mTestDisplay8) {
+                // flakes out while connecting
+                display.setStatus(RemoteDisplay.STATUS_CONNECTING);
+                updateDisplay(display);
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        if ((display == mTestDisplay8)
+                                && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) {
+                            display.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                            updateDisplay(display);
+                        }
+                    }
+                }, 2000);
+            } else if (display == mTestDisplay9) {
+                // flakes out when connected
+                display.setStatus(RemoteDisplay.STATUS_CONNECTING);
+                updateDisplay(display);
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        if ((display == mTestDisplay9)
+                                && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) {
+                            display.setStatus(RemoteDisplay.STATUS_CONNECTED);
+                            updateDisplay(display);
+                        }
+                    }
+                }, 2000);
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        if ((display == mTestDisplay9)
+                                && display.getStatus() == RemoteDisplay.STATUS_CONNECTED) {
+                            display.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                            updateDisplay(display);
+                        }
+                    }
+                }, 5000);
+            } else if (display == mTestDisplay10) {
+                display.setStatus(RemoteDisplay.STATUS_CONNECTING);
+                updateDisplay(display);
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (display == mTestDisplay10) {
+                            if (display.getStatus() == RemoteDisplay.STATUS_CONNECTING) {
+                                display.setStatus(RemoteDisplay.STATUS_CONNECTED);
+                                updateDisplay(display);
+                                mHandler.postDelayed(this, 7000);
+                            } else if (display.getStatus() == RemoteDisplay.STATUS_CONNECTED) {
+                                display.setStatus(RemoteDisplay.STATUS_CONNECTING);
+                                updateDisplay(display);
+                                mHandler.postDelayed(this, 2000);
+                            }
+                        }
+                    }
+                }, 2000);
+            }
+        }
+
+        @Override
+        public void onDisconnect(RemoteDisplay display) {
+            Log.d(TAG, "onDisconnect: display.getId()=" + display.getId());
+
+            if (display == mTestDisplay1 || display == mTestDisplay2
+                    || display == mTestDisplay6 || display == mTestDisplay8
+                    || display == mTestDisplay9 || display == mTestDisplay10) {
+                display.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                updateDisplay(display);
+            }
+        }
+
+        @Override
+        public void onSetVolume(RemoteDisplay display, int volume) {
+            Log.d(TAG, "onSetVolume: display.getId()=" + display.getId()
+                    + ", volume=" + volume);
+
+            if (display == mTestDisplay1) {
+                display.setVolume(Math.max(0, Math.min(display.getVolumeMax(), volume)));
+                updateDisplay(display);
+            }
+        }
+
+        @Override
+        public void onAdjustVolume(RemoteDisplay display, int delta) {
+            Log.d(TAG, "onAdjustVolume: display.getId()=" + display.getId()
+                    + ", delta=" + delta);
+
+            if (display == mTestDisplay1) {
+                display.setVolume(Math.max(0, Math.min(display.getVolumeMax(),
+                        display .getVolume() + delta)));
+                updateDisplay(display);
+            }
+        }
+
+        @Override
+        public void addDisplay(RemoteDisplay display) {
+            Log.d(TAG, "addDisplay: display=" + display);
+            super.addDisplay(display);
+        }
+
+        @Override
+        public void removeDisplay(RemoteDisplay display) {
+            Log.d(TAG, "removeDisplay: display=" + display);
+            super.removeDisplay(display);
+        }
+
+        @Override
+        public void updateDisplay(RemoteDisplay display) {
+            Log.d(TAG, "updateDisplay: display=" + display);
+            super.updateDisplay(display);
+        }
+
+        private final Runnable mBlink = new Runnable() {
+            @Override
+            public void run() {
+                if (mTestDisplay7 == null) {
+                    if (mBlinking) {
+                        mTestDisplay7 = new RemoteDisplay("testDisplay7",
+                                "Test Display 7 (blinky)");
+                        mTestDisplay7.setDescription("Comes and goes but can't connect");
+                        mTestDisplay7.setStatus(RemoteDisplay.STATUS_AVAILABLE);
+                        addDisplay(mTestDisplay7);
+                        mHandler.postDelayed(this, 7000);
+                    }
+                } else {
+                    removeDisplay(mTestDisplay7);
+                    mTestDisplay7 = null;
+                    if (mBlinking) {
+                        mHandler.postDelayed(this, 4000);
+                    }
+                }
+            }
+        };
+    }
+}
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index e4c4214..df32ee1 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -93,7 +93,7 @@
         }
         
         try {
-            mWm.addAppToken(0, null, 0, 0, 0, false, false, 0);
+            mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0);
             fail("IWindowManager.addAppToken did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 4e73568..1fa9615 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -31,6 +31,11 @@
 built_core_dep := $(call java-lib-deps,core)
 built_core_classes := $(call java-lib-files,core)
 
+built_ext_dep := $(call java-lib-deps,ext)
+built_ext_classes := $(call java-lib-files,ext)
+built_ext_data := $(call intermediates-dir-for, \
+			JAVA_LIBRARIES,ext,,COMMON)/javalib.jar
+
 built_layoutlib_create_jar := $(call intermediates-dir-for, \
 			JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar
 
@@ -47,6 +52,7 @@
 
 $(LOCAL_BUILT_MODULE): $(built_core_dep) \
                        $(built_framework_dep) \
+                       $(built_ext_dep) \
                        $(built_layoutlib_create_jar)
 	$(hide) echo "host layoutlib_create: $@"
 	$(hide) mkdir -p $(dir $@)
@@ -55,7 +61,9 @@
 	$(hide) java -jar $(built_layoutlib_create_jar) \
 	             $@ \
 	             $(built_core_classes) \
-	             $(built_framework_classes)
+	             $(built_framework_classes) \
+	             $(built_ext_classes) \
+	             $(built_ext_data)
 	$(hide) ls -l $(built_framework_classes)
 
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
index 62d0a0d..802cf1c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
@@ -20,6 +20,7 @@
 import java.awt.Graphics2D;
 import java.awt.font.FontRenderContext;
 import java.awt.font.GlyphVector;
+import java.awt.geom.Rectangle2D;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -50,9 +51,12 @@
         }
     }
 
-    /* package */ Graphics2D graphics;
-    /* package */ Paint_Delegate paint;
-    /* package */ char[] text;
+    private Graphics2D mGraphics;
+    private Paint_Delegate mPaint;
+    private char[] mText;
+    // Bounds of the text drawn so far.
+    private RectF mBounds;
+    private float mBaseline;
 
     /**
      * @param graphics May be null.
@@ -61,9 +65,9 @@
      */
     /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) {
         assert (paint != null);
-        this.graphics = graphics;
-        this.paint = paint;
-        this.text = text;
+        mGraphics = graphics;
+        mPaint = paint;
+        mText = text;
     }
 
     /**
@@ -77,61 +81,62 @@
      * @param advances If not null, then advances for each character to be rendered are returned
      *            here.
      * @param advancesIndex index into advances from where the advances need to be filled.
-     * @param draw If true and {@link graphics} is not null, draw the rendered text on the graphics
+     * @param draw If true and {@code graphics} is not null, draw the rendered text on the graphics
      *            at the given co-ordinates
      * @param x The x-coordinate of the left edge of where the text should be drawn on the given
      *            graphics.
-     * @param y The y-coordinate at which to draw the text on the given graphics.
-     * @return The x-coordinate of the right edge of the drawn text. In other words,
-     *            x + the width of the text.
+     * @param y The y-coordinate at which to draw the text on the given mGraphics.
+     * @return A rectangle specifying the bounds of the text drawn.
      */
-    /* package */ float renderText(int start, int limit, boolean isRtl, float advances[],
+    /* package */ RectF renderText(int start, int limit, boolean isRtl, float[] advances,
             int advancesIndex, boolean draw, float x, float y) {
         // We break the text into scripts and then select font based on it and then render each of
         // the script runs.
-        for (ScriptRun run : getScriptRuns(text, start, limit, isRtl, paint.getFonts())) {
+        mBounds = new RectF(x, y, x, y);
+        mBaseline = y;
+        for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mPaint.getFonts())) {
             int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT;
             flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT;
-            x = renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw,
-                    x, y);
+            renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw);
             advancesIndex += run.limit - run.start;
         }
-        return x;
+        return mBounds;
     }
 
     /**
-     * Render a script run. Use the preferred font to render as much as possible. This also
-     * implements a fallback mechanism to render characters that cannot be drawn using the
-     * preferred font.
-     *
-     * @return x + width of the text drawn.
+     * Render a script run to the right of the bounds passed. Use the preferred font to render as
+     * much as possible. This also implements a fallback mechanism to render characters that cannot
+     * be drawn using the preferred font.
      */
-    private float renderScript(int start, int limit, FontInfo preferredFont, int flag,
-            float advances[], int advancesIndex, boolean draw, float x, float y) {
-        List<FontInfo> fonts = paint.getFonts();
+    private void renderScript(int start, int limit, FontInfo preferredFont, int flag,
+            float[] advances, int advancesIndex, boolean draw) {
+        List<FontInfo> fonts = mPaint.getFonts();
         if (fonts == null || preferredFont == null) {
-            return x;
+            return;
         }
 
         while (start < limit) {
             boolean foundFont = false;
-            int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(text, start, limit);
+            int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(mText, start, limit);
             if (canDisplayUpTo == -1) {
-                return render(start, limit, preferredFont, flag, advances, advancesIndex, draw,
-                        x, y);
-            } else if (canDisplayUpTo > start) { // can draw something
-                x = render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex,
-                        draw, x, y);
+                // We can draw all characters in the text.
+                render(start, limit, preferredFont, flag, advances, advancesIndex, draw);
+                return;
+            }
+            if (canDisplayUpTo > start) {
+                // We can draw something.
+                render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, draw);
                 advancesIndex += canDisplayUpTo - start;
                 start = canDisplayUpTo;
             }
 
-            int charCount = Character.isHighSurrogate(text[start]) ? 2 : 1;
+            // The current character cannot be drawn with the preferred font. Cycle through all the
+            // fonts to check which one can draw it.
+            int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1;
             for (FontInfo font : fonts) {
-                canDisplayUpTo = font.mFont.canDisplayUpTo(text, start, start + charCount);
+                canDisplayUpTo = font.mFont.canDisplayUpTo(mText, start, start + charCount);
                 if (canDisplayUpTo == -1) {
-                    x = render(start, start+charCount, font, flag, advances, advancesIndex, draw,
-                            x, y);
+                    render(start, start+charCount, font, flag, advances, advancesIndex, draw);
                     start += charCount;
                     advancesIndex += charCount;
                     foundFont = true;
@@ -143,46 +148,63 @@
                 // probably appear as a box or a blank space. We could, probably, use some
                 // heuristics and break the character into the base character and diacritics and
                 // then draw it, but it's probably not worth the effort.
-                x = render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
-                        draw, x, y);
+                render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
+                        draw);
                 start += charCount;
                 advancesIndex += charCount;
             }
         }
-        return x;
     }
 
     /**
-     * Render the text with the given font.
+     * Renders the text to the right of the bounds with the given font.
+     * @param font The font to render the text with.
      */
-    private float render(int start, int limit, FontInfo font, int flag, float advances[],
-            int advancesIndex, boolean draw, float x, float y) {
+    private void render(int start, int limit, FontInfo font, int flag, float[] advances,
+            int advancesIndex, boolean draw) {
 
-        float totalAdvance = 0;
         // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with
         // the anti-aliasing set.
         FontRenderContext f = font.mMetrics.getFontRenderContext();
-        FontRenderContext frc = new FontRenderContext(f.getTransform(), paint.isAntiAliased(),
+        FontRenderContext frc = new FontRenderContext(f.getTransform(), mPaint.isAntiAliased(),
                 f.usesFractionalMetrics());
-        GlyphVector gv = font.mFont.layoutGlyphVector(frc, text, start, limit, flag);
+        GlyphVector gv = font.mFont.layoutGlyphVector(frc, mText, start, limit, flag);
         int ng = gv.getNumGlyphs();
         int[] ci = gv.getGlyphCharIndices(0, ng, null);
-        for (int i = 0; i < ng; i++) {
-            float adv = gv.getGlyphMetrics(i).getAdvanceX();
-            if (advances != null) {
+        if (advances != null) {
+            for (int i = 0; i < ng; i++) {
                 int adv_idx = advancesIndex + ci[i];
-                advances[adv_idx] += adv;
+                advances[adv_idx] += gv.getGlyphMetrics(i).getAdvanceX();
             }
-            totalAdvance += adv;
         }
-        if (draw && graphics != null) {
-            graphics.drawGlyphVector(gv, x, y);
+        if (draw && mGraphics != null) {
+            mGraphics.drawGlyphVector(gv, mBounds.right, mBaseline);
         }
-        return x + totalAdvance;
+
+        // Update the bounds.
+        Rectangle2D awtBounds = gv.getLogicalBounds();
+        RectF bounds = awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline);
+        // If the width of the bounds is zero, no text had been drawn earlier. Hence, use the
+        // coordinates from the bounds as an offset.
+        if (Math.abs(mBounds.right - mBounds.left) == 0) {
+            mBounds = bounds;
+        } else {
+            mBounds.union(bounds);
+        }
     }
 
     // --- Static helper methods ---
 
+    private static RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY) {
+        float left = (float) awtRec.getX();
+        float top = (float) awtRec.getY();
+        float right = (float) (left + awtRec.getWidth());
+        float bottom = (float) (top + awtRec.getHeight());
+        RectF androidRect = new RectF(left, top, right, bottom);
+        androidRect.offset(offsetX, offsetY);
+        return androidRect;
+    }
+
     /* package */  static List<ScriptRun> getScriptRuns(char[] text, int start, int limit,
             boolean isRtl, List<FontInfo> fonts) {
         LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>();
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
index 04ce9d0..d85c3d1 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -44,62 +44,13 @@
  */
 /*package*/ class BitmapFactory_Delegate {
 
-    // ------ Java delegates ------
-
-    @LayoutlibDelegate
-    /*package*/ static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
-        if (bm == null || opts == null) {
-            return bm;
-        }
-
-        final int density = opts.inDensity;
-        if (density == 0) {
-            return bm;
-        }
-
-        bm.setDensity(density);
-        final int targetDensity = opts.inTargetDensity;
-        if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
-            return bm;
-        }
-
-        byte[] np = bm.getNinePatchChunk();
-        final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
-        // DELEGATE CHANGE: never scale 9-patch
-        if (opts.inScaled && isNinePatch == false) {
-            float scale = targetDensity / (float)density;
-            // TODO: This is very inefficient and should be done in native by Skia
-            final Bitmap oldBitmap = bm;
-            bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
-                    (int) (bm.getHeight() * scale + 0.5f), true);
-            oldBitmap.recycle();
-
-            if (isNinePatch) {
-                np = nativeScaleNinePatch(np, scale, outPadding);
-                bm.setNinePatchChunk(np);
-            }
-            bm.setDensity(targetDensity);
-        }
-
-        return bm;
-    }
-
-
     // ------ Native Delegates ------
 
     @LayoutlibDelegate
     /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage,
             Rect padding, Options opts) {
-        return nativeDecodeStream(is, storage, padding, opts, false, 1.f);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static  Bitmap nativeDecodeStream(InputStream is, byte[] storage,
-            Rect padding, Options opts, boolean applyScale, float scale) {
         Bitmap bm = null;
 
-        //TODO support rescaling
-
         Density density = Density.MEDIUM;
         Set<BitmapCreateFlags> bitmapCreateFlags = EnumSet.of(BitmapCreateFlags.MUTABLE);
         if (opts != null) {
@@ -157,13 +108,6 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts,
-            boolean applyScale, float scale) {
-        opts.inBitmap = null;
-        return null;
-    }
-
-    @LayoutlibDelegate
     /*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset,
             int length, Options opts) {
         opts.inBitmap = null;
@@ -171,13 +115,6 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad) {
-        // don't scale for now. This should not be called anyway since we re-implement
-        // BitmapFactory.finishDecode();
-        return chunk;
-    }
-
-    @LayoutlibDelegate
     /*package*/ static boolean nativeIsSeekable(FileDescriptor fd) {
         return true;
     }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index ec284ac..13d2ba1 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -308,8 +308,16 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nativeRecycle(int nativeBitmap) {
+    /*package*/ static boolean nativeRecycle(int nativeBitmap) {
         sManager.removeJavaReferenceFor(nativeBitmap);
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeReconfigure(int nativeBitmap, int width, int height,
+            int config, int allocSize) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "Bitmap.reconfigure() is not supported", null /*data*/);
     }
 
     @LayoutlibDelegate
@@ -341,28 +349,6 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nativeWidth(int nativeBitmap) {
-        // get the delegate from the native int.
-        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
-        if (delegate == null) {
-            return 0;
-        }
-
-        return delegate.mImage.getWidth();
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static int nativeHeight(int nativeBitmap) {
-        // get the delegate from the native int.
-        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
-        if (delegate == null) {
-            return 0;
-        }
-
-        return delegate.mImage.getHeight();
-    }
-
-    @LayoutlibDelegate
     /*package*/ static int nativeRowBytes(int nativeBitmap) {
         // get the delegate from the native int.
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
@@ -407,19 +393,21 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) {
+    /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y,
+            boolean isPremultiplied) {
         // get the delegate from the native int.
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
         if (delegate == null) {
             return 0;
         }
 
+        // TODO: Support isPremultiplied.
         return delegate.mImage.getRGB(x, y);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset,
-            int stride, int x, int y, int width, int height) {
+            int stride, int x, int y, int width, int height, boolean isPremultiplied) {
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
         if (delegate == null) {
             return;
@@ -430,7 +418,8 @@
 
 
     @LayoutlibDelegate
-    /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) {
+    /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color,
+            boolean isPremultiplied) {
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
         if (delegate == null) {
             return;
@@ -441,7 +430,7 @@
 
     @LayoutlibDelegate
     /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset,
-            int stride, int x, int y, int width, int height) {
+            int stride, int x, int y, int width, int height, boolean isPremultiplied) {
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
         if (delegate == null) {
             return;
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 62b47bd..10ad0a3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -990,7 +990,8 @@
                 int limit = index + count;
                 boolean isRtl = flags == Canvas.DIRECTION_RTL;
                 if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
-                    float m = paintDelegate.measureText(text, index, count, isRtl);
+                    RectF bounds = paintDelegate.measureText(text, index, count, isRtl);
+                    float m = bounds.right - bounds.left;
                     if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
                         x -= m / 2;
                     } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
index fa68796..a79ec8f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -167,6 +167,7 @@
         return sManager.addNewDelegate(newDelegate);
     }
 
+    @LayoutlibDelegate
     /*package*/ static void nativeFinalize(int chunk) {
         sManager.removeJavaReferenceFor(chunk);
     }
@@ -175,7 +176,7 @@
     /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance,
             int chunk, int paint_instance_or_null, int destDensity, int srcDensity) {
         draw(canvas_instance,
-                (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(),
+                (int) loc.left, (int) loc.top, (int) loc.right, (int) loc.bottom,
                 bitmap_instance, chunk, paint_instance_or_null,
                 destDensity, srcDensity);
     }
@@ -184,7 +185,7 @@
     /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance,
             int chunk, int paint_instance_or_null, int destDensity, int srcDensity) {
         draw(canvas_instance,
-                loc.left, loc.top, loc.width(), loc.height(),
+                loc.left, loc.top, loc.right, loc.bottom,
                 bitmap_instance, chunk, paint_instance_or_null,
                 destDensity, srcDensity);
     }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index 41953ed..4ad1a17 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -575,7 +575,8 @@
             return 0;
         }
 
-        return delegate.measureText(text, index, count, isRtl(bidiFlags));
+        RectF bounds = delegate.measureText(text, index, count, isRtl(bidiFlags));
+        return bounds.right - bounds.left;
     }
 
     @LayoutlibDelegate
@@ -614,7 +615,8 @@
             }
 
             // measure from start to end
-            float res = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags));
+            RectF bounds = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags));
+            float res = bounds.right - bounds.left;
 
             if (measuredWidth != null) {
                 measuredWidth[measureIndex] = res;
@@ -991,8 +993,9 @@
         boolean isRtl = isRtl(flags);
 
         int limit = index + count;
-        return new BidiRenderer(null, delegate, text).renderText(
+        RectF bounds = new BidiRenderer(null, delegate, text).renderText(
                 index, limit, isRtl, advances, advancesIndex, false, 0, 0);
+        return bounds.right - bounds.left;
     }
 
     @LayoutlibDelegate
@@ -1058,9 +1061,7 @@
         if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) {
             return;
         }
-        int w = (int) delegate.measureText(text, index, count, isRtl(bidiFlags));
-        int h= delegate.getFonts().get(0).mMetrics.getHeight();
-        bounds.set(0, 0, w, h);
+        delegate.measureText(text, index, count, isRtl(bidiFlags)).roundOut(bounds);
     }
 
     @LayoutlibDelegate
@@ -1154,7 +1155,7 @@
         }
     }
 
-    /*package*/ float measureText(char[] text, int index, int count, boolean isRtl) {
+    /*package*/ RectF measureText(char[] text, int index, int count, boolean isRtl) {
         return new BidiRenderer(null, this, text).renderText(
                 index, index + count, isRtl, null, 0, false, 0, 0);
     }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index 64f19d3..9d80be9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -474,6 +474,12 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static boolean native_op(int nPath1, int nPath2, int op, int result) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null);
+        return false;
+    }
+
+    @LayoutlibDelegate
     /*package*/ static void finalizer(int nPath) {
         sManager.removeJavaReferenceFor(nPath);
     }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 8701cc8..b50e98b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -103,6 +103,9 @@
         if (familyName == null) {
             familyName = DEFAULT_FAMILY;
         }
+        if (style < 0) {
+            style = Typeface.NORMAL;
+        }
 
         Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style);
         if (sFontLoader != null) {
diff --git a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java
index 15cd687..320dd0d 100644
--- a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java
@@ -17,6 +17,7 @@
 package android.text.format;
 
 import java.util.Calendar;
+import java.util.TimeZone;
 import java.util.UnknownFormatConversionException;
 import java.util.regex.Pattern;
 
@@ -35,6 +36,28 @@
     // Regex to match odd number of '%'.
     private static final Pattern p = Pattern.compile("(?<!%)(%%)*%(?!%)");
 
+    // Format used by toString()
+    private static final String FORMAT = "%1$tY%1$tm%1$tdT%1$tH%1$tM%1$tS<%1$tZ>";
+
+    @LayoutlibDelegate
+    /*package*/ static long normalize(Time thisTime, boolean ignoreDst) {
+        long millis = toMillis(thisTime, ignoreDst);
+        set(thisTime, millis);
+        return millis;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void switchTimezone(Time thisTime, String timezone) {
+        Calendar c = timeToCalendar(thisTime);
+        c.setTimeZone(TimeZone.getTimeZone(timezone));
+        calendarToTime(c, thisTime);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nativeCompare(Time a, Time b) {
+      return timeToCalendar(a).compareTo(timeToCalendar(b));
+    }
+
     @LayoutlibDelegate
     /*package*/ static String format1(Time thisTime, String format) {
 
@@ -46,16 +69,92 @@
             // of $.
             return String.format(
                     p.matcher(format).replaceAll("$0\\1\\$t"),
-                    timeToCalendar(thisTime, Calendar.getInstance()));
+                    timeToCalendar(thisTime));
         } catch (UnknownFormatConversionException e) {
             Bridge.getLog().fidelityWarning(LayoutLog.TAG_STRFTIME, "Unrecognized format", e, format);
             return format;
         }
     }
 
-    private static Calendar timeToCalendar(Time time, Calendar calendar) {
+    /**
+     * Return the current time in YYYYMMDDTHHMMSS<tz> format
+     */
+    @LayoutlibDelegate
+    /*package*/ static String toString(Time thisTime) {
+        Calendar c = timeToCalendar(thisTime);
+        return String.format(FORMAT, c);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeParse(Time thisTime, String s) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "android.text.format.Time.parse() not supported.", null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeParse3339(Time thisTime, String s) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "android.text.format.Time.parse3339() not supported.", null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void setToNow(Time thisTime) {
+        calendarToTime(getCalendarInstance(thisTime), thisTime);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long toMillis(Time thisTime, boolean ignoreDst) {
+        // TODO: Respect ignoreDst.
+        return timeToCalendar(thisTime).getTimeInMillis();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void set(Time thisTime, long millis) {
+        Calendar c = getCalendarInstance(thisTime);
+        c.setTimeInMillis(millis);
+        calendarToTime(c,thisTime);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static String format2445(Time thisTime) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "android.text.format.Time.format2445() not supported.", null);
+        return "";
+    }
+
+    // ---- private helper methods ----
+
+    private static Calendar timeToCalendar(Time time) {
+        Calendar calendar = getCalendarInstance(time);
         calendar.set(time.year, time.month, time.monthDay, time.hour, time.minute, time.second);
         return calendar;
     }
 
+    private static void calendarToTime(Calendar c, Time time) {
+        time.timezone = c.getTimeZone().getID();
+        time.set(c.get(Calendar.SECOND), c.get(Calendar.MINUTE), c.get(Calendar.HOUR_OF_DAY),
+                c.get(Calendar.DATE), c.get(Calendar.MONTH), c.get(Calendar.YEAR));
+        time.weekDay = c.get(Calendar.DAY_OF_WEEK);
+        time.yearDay = c.get(Calendar.DAY_OF_YEAR);
+        time.isDst = c.getTimeZone().inDaylightTime(c.getTime()) ? 1 : 0;
+        // gmtoff is in seconds and TimeZone.getOffset() returns milliseconds.
+        time.gmtoff = c.getTimeZone().getOffset(c.getTimeInMillis()) / DateUtils.SECOND_IN_MILLIS;
+    }
+
+    /**
+     * Return a calendar instance with the correct timezone.
+     *
+     * @param time Time to obtain the timezone from.
+     */
+    private static Calendar getCalendarInstance(Time time) {
+        // TODO: Check platform code to make sure the behavior is same for null/invalid timezone.
+        if (time == null || time.timezone == null) {
+            // Default to local timezone.
+            return Calendar.getInstance();
+        }
+        // If timezone is invalid, use GMT.
+        return Calendar.getInstance(TimeZone.getTimeZone(time.timezone));
+    }
 }
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index fd153af..dd2cbc1 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -81,7 +81,7 @@
 
     @Override
     public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
-            boolean arg5, boolean arg6, int arg7)
+            boolean arg5, boolean arg6, int arg7, int arg8)
             throws RemoteException {
         // TODO Auto-generated method stub
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
index 17b0eb6..bcd08eb4 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -290,7 +290,7 @@
                     TypedValue out = new TypedValue();
                     if (ResourceHelper.parseFloatAttribute("textSize", textSize.getValue(), out,
                             true /*requireUnit*/)) {
-                        textView.setTextSize(
+                        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                                 out.getDimension(bridgeContext.getResources().getDisplayMetrics()));
                     }
                 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
index 108b651..cc7338a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
@@ -57,7 +57,9 @@
     private static final String FONT_SUFFIX_NONE = ".ttf";
     private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf";
     private static final String FONT_SUFFIX_BOLD = "-Bold.ttf";
-    private static final String FONT_SUFFIX_ITALIC = "-Italic.ttf";
+    // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked
+    // separately.
+    private static final String FONT_SUFFIX_ITALIC = "Italic.ttf";
     private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf";
 
     // This must match the values of Typeface styles so that we can use them for indices in this
@@ -285,10 +287,10 @@
                             mFontInfo.font[Typeface.NORMAL] = font;
                         } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) {
                             mFontInfo.font[Typeface.BOLD] = font;
-                        } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) {
-                            mFontInfo.font[Typeface.ITALIC] = font;
                         } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) {
                             mFontInfo.font[Typeface.BOLD_ITALIC] = font;
+                        } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) {
+                            mFontInfo.font[Typeface.ITALIC] = font;
                         } else if (fileName.endsWith(FONT_SUFFIX_NONE)) {
                             mFontInfo.font[Typeface.NORMAL] = font;
                         }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index 23d08e3..60f5331 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -273,7 +273,6 @@
             mContext.getRenderResources().setLogger(null);
         }
 
-        mContext = null;
     }
 
     public static BridgeContext getCurrentContext() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 57771e3..377d996 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -334,7 +334,7 @@
                 backgroundView = backgroundLayout;
                 backgroundLayout.setOrientation(LinearLayout.VERTICAL);
                 LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                        LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+                        LayoutParams.MATCH_PARENT, 0);
                 layoutParams.weight = 1;
                 backgroundLayout.setLayoutParams(layoutParams);
                 topLayout.addView(backgroundLayout);
@@ -369,7 +369,7 @@
                 // content frame
                 mContentRoot = new FrameLayout(context);
                 layoutParams = new LinearLayout.LayoutParams(
-                        LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+                        LayoutParams.MATCH_PARENT, 0);
                 layoutParams.weight = 1;
                 mContentRoot.setLayoutParams(layoutParams);
                 backgroundLayout.addView(mContentRoot);
diff --git a/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java
index a773d93..d94c205 100644
--- a/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java
+++ b/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java
@@ -21,6 +21,7 @@
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 import com.ibm.icu.text.DateIntervalFormat;
 import com.ibm.icu.util.DateInterval;
 import com.ibm.icu.util.TimeZone;
@@ -38,6 +39,7 @@
 
     // ---- native methods ----
 
+    @LayoutlibDelegate
     /*package*/static String formatDateInterval(long address, long fromDate, long toDate) {
         DateIntervalFormat_Delegate delegate = sManager.getDelegate((int)address);
         if (delegate == null) {
@@ -52,6 +54,7 @@
         return sb.toString();
     }
 
+    @LayoutlibDelegate
     /*package*/ static long createDateIntervalFormat(String skeleton, String localeName,
             String tzName) {
         TimeZone prevDefaultTz = TimeZone.getDefault();
@@ -63,6 +66,7 @@
         return sManager.addNewDelegate(newDelegate);
     }
 
+    @LayoutlibDelegate
     /*package*/ static void destroyDateIntervalFormat(long address) {
         sManager.removeJavaReferenceFor((int)address);
     }
diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
index 06ae804..ad4103b 100644
--- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
+++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
@@ -46,7 +46,7 @@
     // --- Native methods accessing ICU's database.
 
     @LayoutlibDelegate
-    /*package*/ static String getBestDateTimePattern(String skeleton, String localeName) {
+    /*package*/ static String getBestDateTimePatternNative(String skeleton, String localeName) {
         return DateTimePatternGenerator.getInstance(new ULocale(localeName))
                 .getBestPattern(skeleton);
     }
@@ -167,7 +167,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean initLocaleDataImpl(String locale, LocaleData result) {
+    /*package*/ static boolean initLocaleDataNative(String locale, LocaleData result) {
 
         // Used by Calendar.
         result.firstDayOfWeek = Integer.valueOf(1);
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index 1572a40..9a31705 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -29,6 +29,7 @@
 import org.objectweb.asm.signature.SignatureVisitor;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
@@ -60,6 +61,9 @@
     private final String[] mIncludeGlobs;
     /** The set of classes to exclude.*/
     private final Set<String> mExcludedClasses;
+    /** Glob patterns of files to keep as is. */
+    private final String[] mIncludeFileGlobs;
+    /** Copy these files into the output as is. */
 
     /**
      * Creates a new analyzer.
@@ -70,15 +74,19 @@
      * @param deriveFrom Keep all classes that derive from these one (these included).
      * @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*"
      *        ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
+     * @param includeFileGlobs Glob patterns of files which are kept as is. This is only for files
+     *        not ending in .class.
      */
     public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
-            String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses) {
+            String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses,
+            String[] includeFileGlobs) {
         mLog = log;
         mGen = gen;
         mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
         mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
         mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
         mExcludedClasses = excludeClasses;
+        mIncludeFileGlobs = includeFileGlobs != null ? includeFileGlobs : new String[0];
     }
 
     /**
@@ -86,7 +94,11 @@
      * Fills the generator with classes & dependencies found.
      */
     public void analyze() throws IOException, LogAbortException {
-        Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
+
+        TreeMap<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+        Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+        parseZip(mOsSourceJar, zipClasses, filesFound);
         mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
                 mOsSourceJar.size() > 1 ? "s" : "");
 
@@ -96,15 +108,29 @@
         if (mGen != null) {
             mGen.setKeep(found);
             mGen.setDeps(deps);
+            mGen.setCopyFiles(filesFound);
         }
     }
 
     /**
-     * Parses a JAR file and returns a list of all classes founds using a map
-     * class name => ASM ClassReader. Class names are in the form "android.view.View".
+     * Parses a JAR file and adds all the classes found to <code>classes</code>
+     * and all other files to <code>filesFound</code>.
+     *
+     * @param classes The map of class name => ASM ClassReader. Class names are
+     *                in the form "android.view.View".
+     * @param fileFound The map of file name => InputStream. The file name is
+     *                  in the form "android/data/dataFile".
      */
-    Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
-        TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+    void parseZip(List<String> jarPathList, Map<String, ClassReader> classes,
+            Map<String, InputStream> filesFound) throws IOException {
+        if (classes == null || filesFound == null) {
+            return;
+        }
+
+        Pattern[] includeFilePatterns = new Pattern[mIncludeFileGlobs.length];
+        for (int i = 0; i < mIncludeFileGlobs.length; ++i) {
+            includeFilePatterns[i] = getPatternFromGlob(mIncludeFileGlobs[i]);
+        }
 
         for (String jarPath : jarPathList) {
             ZipFile zip = new ZipFile(jarPath);
@@ -116,11 +142,17 @@
                     ClassReader cr = new ClassReader(zip.getInputStream(entry));
                     String className = classReaderToClassName(cr);
                     classes.put(className, cr);
+                } else {
+                    for (int i = 0; i < includeFilePatterns.length; ++i) {
+                        if (includeFilePatterns[i].matcher(entry.getName()).matches()) {
+                            filesFound.put(entry.getName(), zip.getInputStream(entry));
+                            break;
+                        }
+                    }
                 }
             }
         }
 
-        return classes;
     }
 
     /**
@@ -202,7 +234,19 @@
      */
     void findGlobs(String globPattern, Map<String, ClassReader> zipClasses,
             Map<String, ClassReader> inOutFound) throws LogAbortException {
-        // transforms the glob pattern in a regexp:
+
+        Pattern regexp = getPatternFromGlob(globPattern);
+
+        for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
+            String class_name = entry.getKey();
+            if (regexp.matcher(class_name).matches()) {
+                findClass(class_name, zipClasses, inOutFound);
+            }
+        }
+    }
+
+    Pattern getPatternFromGlob(String globPattern) {
+     // transforms the glob pattern in a regexp:
         // - escape "." with "\."
         // - replace "*" by "[^.]*"
         // - escape "$" with "\$"
@@ -216,14 +260,7 @@
         globPattern = globPattern.replaceAll("@", ".*");
         globPattern += "$";
 
-        Pattern regexp = Pattern.compile(globPattern);
-
-        for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
-            String class_name = entry.getKey();
-            if (regexp.matcher(class_name).matches()) {
-                findClass(class_name, zipClasses, inOutFound);
-            }
-        }
+        return Pattern.compile(globPattern);
     }
 
     /**
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index b102561..207d8ae 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -20,6 +20,7 @@
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.ClassWriter;
 
+import java.io.ByteArrayOutputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -52,6 +53,8 @@
     private Map<String, ClassReader> mKeep;
     /** All dependencies that must be completely stubbed. */
     private Map<String, ClassReader> mDeps;
+    /** All files that are to be copied as-is. */
+    private Map<String, InputStream> mCopyFiles;
     /** Counter of number of classes renamed during transform. */
     private int mRenameCount;
     /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */
@@ -195,6 +198,11 @@
         mDeps = deps;
     }
 
+    /** Sets the map of files to output as-is. */
+    public void setCopyFiles(Map<String, InputStream> copyFiles) {
+        mCopyFiles = copyFiles;
+    }
+
     /** Gets the map of classes to output as-is, except if they have native methods */
     public Map<String, ClassReader> getKeep() {
         return mKeep;
@@ -205,6 +213,11 @@
         return mDeps;
     }
 
+    /** Gets the map of files to output as-is. */
+    public Map<String, InputStream> getCopyFiles() {
+        return mCopyFiles;
+    }
+
     /** Generates the final JAR */
     public void generate() throws FileNotFoundException, IOException {
         TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
@@ -232,6 +245,15 @@
             all.put(name, b);
         }
 
+        for (Entry<String, InputStream> entry : mCopyFiles.entrySet()) {
+            try {
+                byte[] b = inputStreamToByteArray(entry.getValue());
+                all.put(entry.getKey(), b);
+            } catch (IOException e) {
+                // Ignore.
+            }
+
+        }
         mLog.info("# deps classes: %d", mDeps.size());
         mLog.info("# keep classes: %d", mKeep.size());
         mLog.info("# renamed     : %d", mRenameCount);
@@ -381,4 +403,13 @@
         return cv.hasNativeMethods();
     }
 
+    private byte[] inputStreamToByteArray(InputStream is) throws IOException {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        byte[] data = new byte[8192];  // 8KB
+        int n;
+        while ((n = is.read(data, 0, data.length)) != -1) {
+            buffer.write(data, 0, n);
+        }
+        return buffer.toByteArray();
+    }
 }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index f6779e3..79aa642 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -131,7 +131,6 @@
         "android.os.HandlerThread#run",
         "android.os.Build#getString",
         "android.text.format.DateFormat#is24HourFormat",
-        "android.text.format.Time#format1",
         "android.view.Choreographer#getRefreshRate",
         "android.view.Display#updateDisplayInfoLocked",
         "android.view.LayoutInflater#rInflate",
@@ -188,6 +187,7 @@
         "android.graphics.Xfermode",
         "android.os.SystemClock",
         "android.text.AndroidBidi",
+        "android.text.format.Time",
         "android.util.FloatMath",
         "android.view.Display",
         "libcore.icu.DateIntervalFormat",
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index ba23048..a79fba1 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -113,8 +113,12 @@
                         "android.pim.*", // for datepicker
                         "android.os.*",  // for android.os.Handler
                         "android.database.ContentObserver", // for Digital clock
+                        "com.android.i18n.phonenumbers.*",  // for TextView with autolink attribute
                     },
-                    excludeClasses);
+                    excludeClasses,
+                    new String[] {
+                        "com/android/i18n/phonenumbers/data/*",
+                    });
             aa.analyze();
             agen.generate();
 
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index 005fc9d..7ec0d38 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -29,6 +29,7 @@
 import org.objectweb.asm.ClassReader;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -55,8 +56,10 @@
 
         Set<String> excludeClasses = new HashSet<String>(1);
         excludeClasses.add("java.lang.JavaClass");
-        mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */,
-                null /* deriveFrom */, null /* includeGlobs */, excludeClasses);
+
+        String[] includeFiles = new String[]{"mock_android/data/data*"};
+        mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, null /* deriveFrom */,
+                null /* includeGlobs */, excludeClasses, includeFiles);
     }
 
     @After
@@ -65,7 +68,11 @@
 
     @Test
     public void testParseZip() throws IOException {
-        Map<String, ClassReader> map = mAa.parseZip(mOsJarPath);
+
+        Map<String, ClassReader> map = new TreeMap<String, ClassReader>();
+        Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+        mAa.parseZip(mOsJarPath, map, filesFound);
 
         assertArrayEquals(new String[] {
                 "java.lang.JavaClass",
@@ -86,11 +93,17 @@
                 "mock_android.widget.TableLayout$LayoutParams"
             },
             map.keySet().toArray());
+        assertArrayEquals(new String[] {"mock_android/data/dataFile"},
+            filesFound.keySet().toArray());
     }
 
     @Test
     public void testFindClass() throws IOException, LogAbortException {
-        Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+
+        Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+        Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+        mAa.parseZip(mOsJarPath, zipClasses, filesFound);
         TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
 
         ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
@@ -105,7 +118,11 @@
 
     @Test
     public void testFindGlobs() throws IOException, LogAbortException {
-        Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+
+        Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+        Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+        mAa.parseZip(mOsJarPath, zipClasses, filesFound);
         TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
 
         // this matches classes, a package match returns nothing
@@ -164,7 +181,11 @@
 
     @Test
     public void testFindClassesDerivingFrom() throws LogAbortException, IOException {
-        Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+
+        Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+        Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+        mAa.parseZip(mOsJarPath, zipClasses, filesFound);
         TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
 
         mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
@@ -186,7 +207,11 @@
 
     @Test
     public void testDependencyVisitor() throws IOException, LogAbortException {
-        Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+
+        Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
+        Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+
+        mAa.parseZip(mOsJarPath, zipClasses, filesFound);
         TreeMap<String, ClassReader> keep = new TreeMap<String, ClassReader>();
         TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
         TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>();
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 8a27173..0dbc238 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -33,6 +33,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -131,7 +132,8 @@
                 new String[] {        // include classes
                     "**"
                 },
-                new HashSet<String>(0) /* excluded classes */);
+                new HashSet<String>(0) /* excluded classes */,
+                new String[]{} /* include files */);
         aa.analyze();
         agen.generate();
 
@@ -195,10 +197,15 @@
                 new String[] {        // include classes
                     "**"
                 },
-                new HashSet<String>(1));
+                new HashSet<String>(1),
+                new String[] {        /* include files */
+                    "mock_android/data/data*"
+                });
         aa.analyze();
         agen.generate();
-        Map<String, ClassReader> output = parseZip(mOsDestJar);
+        Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
+        Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+        parseZip(mOsDestJar, output, filesFound);
         boolean injectedClassFound = false;
         for (ClassReader cr: output.values()) {
             TestClassVisitor cv = new TestClassVisitor();
@@ -206,10 +213,13 @@
             injectedClassFound |= cv.mInjectedClassFound;
         }
         assertTrue(injectedClassFound);
+        assertArrayEquals(new String[] {"mock_android/data/dataFile"},
+                filesFound.keySet().toArray());
     }
 
-    private Map<String,ClassReader> parseZip(String jarPath) throws IOException {
-        TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+    private void parseZip(String jarPath,
+            Map<String, ClassReader> classes,
+            Map<String, InputStream> filesFound) throws IOException {
 
             ZipFile zip = new ZipFile(jarPath);
             Enumeration<? extends ZipEntry> entries = zip.entries();
@@ -220,10 +230,11 @@
                     ClassReader cr = new ClassReader(zip.getInputStream(entry));
                     String className = classReaderToClassName(cr);
                     classes.put(className, cr);
+                } else {
+                    filesFound.put(entry.getName(), zip.getInputStream(entry));
                 }
             }
 
-        return classes;
     }
 
     private String classReaderToClassName(ClassReader classReader) {
diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar
index 60d8efb..8dd0481 100644
--- a/tools/layoutlib/create/tests/data/mock_android.jar
+++ b/tools/layoutlib/create/tests/data/mock_android.jar
Binary files differ
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile
new file mode 100644
index 0000000..ab29fbe
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile
@@ -0,0 +1 @@
+A simple data file that should *not* be copied to the output jar.
\ No newline at end of file
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile
new file mode 100644
index 0000000..9b01893
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile
@@ -0,0 +1 @@
+A simple data file that should be copied to the output jar unchanged.
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 5a1928c..149bda6 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -117,7 +117,7 @@
 
     void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable);
 
-    boolean requestBatchedScan(in BatchedScanSettings requested, IBinder binder);
+    boolean requestBatchedScan(in BatchedScanSettings requested, IBinder binder, in WorkSource ws);
 
     void stopBatchedScan(in BatchedScanSettings requested);
 
diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java
index f6a621f..e76eb17 100644
--- a/wifi/java/android/net/wifi/SupplicantStateTracker.java
+++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java
@@ -55,7 +55,7 @@
     private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
 
     /* Maximum retries on assoc rejection events */
-    private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 4;
+    private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 16;
 
     /* Tracks if networks have been disabled during a connection */
     private boolean mNetworksDisabledDuringConnect = false;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7fc8bef..3b47e63 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -799,7 +799,13 @@
      */
     public boolean requestBatchedScan(BatchedScanSettings requested) {
         try {
-            return mService.requestBatchedScan(requested, new Binder());
+            return mService.requestBatchedScan(requested, new Binder(), null);
+        } catch (RemoteException e) { return false; }
+    }
+    /** @hide */
+    public boolean requestBatchedScan(BatchedScanSettings requested, WorkSource workSource) {
+        try {
+            return mService.requestBatchedScan(requested, new Binder(), workSource);
         } catch (RemoteException e) { return false; }
     }
 
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 520668e..c2f278a 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -39,7 +39,6 @@
 public class WifiNative {
 
     private static final boolean DBG = false;
-    private static final boolean VDBG = false;
     private final String mTAG;
     private static final int DEFAULT_GROUP_OWNER_INTENT     = 6;
 
@@ -118,12 +117,12 @@
 
     public boolean connectToSupplicant() {
         // No synchronization necessary .. it is implemented in WifiMonitor
-        if (VDBG) localLog(mInterfacePrefix + "connectToSupplicant");
+        localLog(mInterfacePrefix + "connectToSupplicant");
         return connectToSupplicantNative();
     }
 
     public void closeSupplicantConnection() {
-        if (VDBG) localLog(mInterfacePrefix + "closeSupplicantConnection");
+        localLog(mInterfacePrefix + "closeSupplicantConnection");
         closeSupplicantConnectionNative();
     }
 
@@ -136,9 +135,9 @@
         if (DBG) Log.d(mTAG, "doBoolean: " + command);
         synchronized (mLock) {
             int cmdId = getNewCmdIdLocked();
-            if (VDBG) localLog(cmdId + "->" + mInterfacePrefix + command);
+            localLog(cmdId + "->" + mInterfacePrefix + command);
             boolean result = doBooleanCommandNative(mInterfacePrefix + command);
-            if (VDBG) localLog(cmdId + "<-" + result);
+            localLog(cmdId + "<-" + result);
             if (DBG) Log.d(mTAG, "   returned " + result);
             return result;
         }
@@ -148,9 +147,9 @@
         if (DBG) Log.d(mTAG, "doInt: " + command);
         synchronized (mLock) {
             int cmdId = getNewCmdIdLocked();
-            if (VDBG) localLog(cmdId + "->" + mInterfacePrefix + command);
+            localLog(cmdId + "->" + mInterfacePrefix + command);
             int result = doIntCommandNative(mInterfacePrefix + command);
-            if (VDBG) localLog(cmdId + "<-" + result);
+            localLog(cmdId + "<-" + result);
             if (DBG) Log.d(mTAG, "   returned " + result);
             return result;
         }
@@ -160,9 +159,9 @@
         if (DBG) Log.d(mTAG, "doString: " + command);
         synchronized (mLock) {
             int cmdId = getNewCmdIdLocked();
-            if (VDBG) localLog(cmdId + "->" + mInterfacePrefix + command);
+            localLog(cmdId + "->" + mInterfacePrefix + command);
             String result = doStringCommandNative(mInterfacePrefix + command);
-            if (VDBG) localLog(cmdId + "<-" + result);
+            localLog(cmdId + "<-" + result);
             if (DBG) Log.d(mTAG, "   returned " + result);
             return result;
         }
@@ -298,7 +297,9 @@
      * the firmware can remember before it runs out of buffer space or -1 on error
      */
     public String setBatchedScanSettings(BatchedScanSettings settings) {
-        if (settings == null) return doStringCommand("DRIVER WLS_BATCHING STOP");
+        if (settings == null) {
+            return doStringCommand("DRIVER WLS_BATCHING STOP");
+        }
         String cmd = "DRIVER WLS_BATCHING SET SCANFREQ=" + settings.scanIntervalSec;
         cmd += " MSCAN=" + settings.maxScansPerBatch;
         if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) {
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 78da7e7..d6b23ae 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -57,6 +57,7 @@
 import android.net.wifi.p2p.WifiP2pService;
 import android.os.BatteryStats;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Message;
@@ -232,6 +233,10 @@
     private DhcpStateMachine mDhcpStateMachine;
     private boolean mDhcpActive = false;
 
+    // Delay in switching to null country code (non-null has no delay)
+    private final int COUNTRY_CODE_DELAY_MS = 15000;
+    private final AtomicInteger mCountryCodeSequence = new AtomicInteger();
+
     private class InterfaceObserver extends BaseNetworkObserver {
         private WifiStateMachine mWifiStateMachine;
 
@@ -416,7 +421,8 @@
 
     /* change the batch scan settings.
      * arg1 = responsible UID
-     * obj = the new settings
+     * arg2 = csph (channel scans per hour)
+     * obj = bundle with the new settings and the optional worksource
      */
     public static final int CMD_SET_BATCHED_SCAN          = BASE + 135;
     public static final int CMD_START_NEXT_BATCHED_SCAN   = BASE + 136;
@@ -628,6 +634,15 @@
 
     private BatchedScanSettings mBatchedScanSettings = null;
 
+    /**
+     * Track the worksource/cost of the current settings and track what's been noted
+     * to the battery stats, so we can mark the end of the previous when changing.
+     */
+    private WorkSource mBatchedScanWorkSource = null;
+    private int mBatchedScanCsph = 0;
+    private WorkSource mNotedBatchedScanWorkSource = null;
+    private int mNotedBatchedScanCsph = 0;
+
 
     public WifiStateMachine(Context context, String wlanInterface) {
         super("WifiStateMachine");
@@ -841,8 +856,14 @@
     /**
      * start or stop batched scanning using the given settings
      */
-    public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid) {
-        sendMessage(CMD_SET_BATCHED_SCAN, callingUid, 0, settings);
+    private static final String BATCHED_SETTING = "batched_settings";
+    private static final String BATCHED_WORKSOURCE = "batched_worksource";
+    public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid, int csph,
+            WorkSource workSource) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(BATCHED_SETTING, settings);
+        bundle.putParcelable(BATCHED_WORKSOURCE, workSource);
+        sendMessage(CMD_SET_BATCHED_SCAN, callingUid, csph, bundle);
     }
 
     public List<BatchedScanResult> syncGetBatchedScanResultsList() {
@@ -861,6 +882,8 @@
     }
 
     private void startBatchedScan() {
+        if (mBatchedScanSettings == null) return;
+
         if (mDhcpActive) {
             if (DBG) log("not starting Batched Scans due to DHCP");
             return;
@@ -872,10 +895,10 @@
         mAlarmManager.cancel(mBatchedScanIntervalIntent);
 
         String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings);
-
         try {
             mExpectedBatchedScans = Integer.parseInt(scansExpected);
             setNextBatchedAlarm(mExpectedBatchedScans);
+            if (mExpectedBatchedScans > 0) noteBatchedScanStart();
         } catch (NumberFormatException e) {
             stopBatchedScan();
             loge("Exception parsing WifiNative.setBatchedScanSettings response " + e);
@@ -918,25 +941,31 @@
     }
 
     // return true if new/different
-    private boolean recordBatchedScanSettings(BatchedScanSettings settings) {
-        if (DBG) log("set batched scan to " + settings);
+    private boolean recordBatchedScanSettings(int responsibleUid, int csph, Bundle bundle) {
+        BatchedScanSettings settings = bundle.getParcelable(BATCHED_SETTING);
+        WorkSource responsibleWorkSource = bundle.getParcelable(BATCHED_WORKSOURCE);
+
+        if (DBG) {
+            log("set batched scan to " + settings + " for uid=" + responsibleUid +
+                    ", worksource=" + responsibleWorkSource);
+        }
         if (settings != null) {
-            // TODO - noteBatchedScanStart(message.arg1);
             if (settings.equals(mBatchedScanSettings)) return false;
         } else {
             if (mBatchedScanSettings == null) return false;
-            // TODO - noteBatchedScanStop(message.arg1);
         }
         mBatchedScanSettings = settings;
+        if (responsibleWorkSource == null) responsibleWorkSource = new WorkSource(responsibleUid);
+        mBatchedScanWorkSource = responsibleWorkSource;
+        mBatchedScanCsph = csph;
         return true;
     }
 
     private void stopBatchedScan() {
         mAlarmManager.cancel(mBatchedScanIntervalIntent);
-        if (mBatchedScanSettings != null) {
-            retrieveBatchedScanData();
-            mWifiNative.setBatchedScanSettings(null);
-        }
+        retrieveBatchedScanData();
+        mWifiNative.setBatchedScanSettings(null);
+        noteBatchedScanStop();
     }
 
     private void setNextBatchedAlarm(int scansExpected) {
@@ -1164,6 +1193,44 @@
         }
     }
 
+    private void noteBatchedScanStart() {
+        // note the end of a previous scan set
+        if (mNotedBatchedScanWorkSource != null &&
+                (mNotedBatchedScanWorkSource.equals(mBatchedScanWorkSource) == false ||
+                 mNotedBatchedScanCsph != mBatchedScanCsph)) {
+            try {
+                mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource);
+            } catch (RemoteException e) {
+                log(e.toString());
+            } finally {
+                mNotedBatchedScanWorkSource = null;
+                mNotedBatchedScanCsph = 0;
+            }
+        }
+        // note the start of the new
+        try {
+            mBatteryStats.noteWifiBatchedScanStartedFromSource(mBatchedScanWorkSource,
+                    mBatchedScanCsph);
+            mNotedBatchedScanWorkSource = mBatchedScanWorkSource;
+            mNotedBatchedScanCsph = mBatchedScanCsph;
+        } catch (RemoteException e) {
+            log(e.toString());
+        }
+    }
+
+    private void noteBatchedScanStop() {
+        if (mNotedBatchedScanWorkSource != null) {
+            try {
+                mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource);
+            } catch (RemoteException e) {
+                log(e.toString());
+            } finally {
+                mNotedBatchedScanWorkSource = null;
+                mNotedBatchedScanCsph = 0;
+            }
+        }
+    }
+
     private void startScanNative(int type) {
         mWifiNative.scan(type);
         mScanResultIsPending = true;
@@ -1471,14 +1538,18 @@
      * @param persist {@code true} if the setting should be remembered.
      */
     public void setCountryCode(String countryCode, boolean persist) {
-        if (persist) {
-            mPersistedCountryCode = countryCode;
-            Settings.Global.putString(mContext.getContentResolver(),
-                    Settings.Global.WIFI_COUNTRY_CODE,
-                    countryCode);
+        // If it's a country code, apply immediately,
+        // If it's empty, delay it in case it's a momentary dropout
+        int countryCodeSequence = mCountryCodeSequence.incrementAndGet();
+        if (TextUtils.isEmpty(countryCode)) {
+            String defaultCountryCode = mContext.getResources().getString(
+                    R.string.config_wifi_unknown_country_code);
+
+            sendMessageDelayed(CMD_SET_COUNTRY_CODE, countryCodeSequence, persist ? 1 : 0,
+                    defaultCountryCode, COUNTRY_CODE_DELAY_MS);
+        } else {
+            sendMessage(CMD_SET_COUNTRY_CODE, countryCodeSequence, persist ? 1 : 0, countryCode);
         }
-        sendMessage(CMD_SET_COUNTRY_CODE, countryCode);
-        mWifiP2pChannel.sendMessage(WifiP2pService.SET_COUNTRY_CODE, countryCode);
     }
 
     /**
@@ -1868,6 +1939,9 @@
            return;
         }
 
+        // note that all these splits and substrings keep references to the original
+        // huge string buffer while the amount we really want is generally pretty small
+        // so make copies instead (one example b/11087956 wasted 400k of heap here).
         synchronized(mScanResultCache) {
             mScanResults = new ArrayList<ScanResult>();
             String[] lines = scanResults.split("\n");
@@ -2307,9 +2381,7 @@
 
         mDhcpActive = false;
 
-        if (mBatchedScanSettings != null) {
-            startBatchedScan();
-        }
+        startBatchedScan();
     }
 
     private void handleSuccessfulIpConfiguration(DhcpResults dhcpResults) {
@@ -2443,11 +2515,13 @@
                         // to the driver happened between mPersistedCountryCode getting set
                         // and now, so simply persisting it here would mean we have sent
                         // nothing to the driver.  Send the cmd so it might be set now.
-                        sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE, countryCode);
+                        int sequenceNum = mCountryCodeSequence.incrementAndGet();
+                        sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE,
+                                sequenceNum, 0, countryCode);
                     }
                     break;
                 case CMD_SET_BATCHED_SCAN:
-                    recordBatchedScanSettings((BatchedScanSettings)message.obj);
+                    recordBatchedScanSettings(message.arg1, message.arg2, (Bundle)message.obj);
                     break;
                 case CMD_POLL_BATCHED_SCAN:
                     handleBatchedScanPollRequest();
@@ -2960,9 +3034,7 @@
 
             mDhcpActive = false;
 
-            if (mBatchedScanSettings != null) {
-                startBatchedScan();
-            }
+            startBatchedScan();
 
             if (mOperationalMode != CONNECT_MODE) {
                 mWifiNative.disconnect();
@@ -3021,23 +3093,40 @@
                     startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);
                     break;
                 case CMD_SET_BATCHED_SCAN:
-                    recordBatchedScanSettings((BatchedScanSettings)message.obj);
-                    startBatchedScan();
+                    if (recordBatchedScanSettings(message.arg1, message.arg2,
+                            (Bundle)message.obj)) {
+                        if (mBatchedScanSettings != null) {
+                            startBatchedScan();
+                        } else {
+                            stopBatchedScan();
+                        }
+                    }
                     break;
                 case CMD_SET_COUNTRY_CODE:
                     String country = (String) message.obj;
+                    final boolean persist = (message.arg2 == 1);
+                    final int sequence = message.arg1;
+                    if (sequence != mCountryCodeSequence.get()) {
+                        if (DBG) log("set country code ignored due to sequence num");
+                        break;
+                    }
                     if (DBG) log("set country code " + country);
-                    if (country != null) {
-                        country = country.toUpperCase(Locale.ROOT);
-                        if (mLastSetCountryCode == null
-                                || country.equals(mLastSetCountryCode) == false) {
-                            if (mWifiNative.setCountryCode(country)) {
-                                mLastSetCountryCode = country;
-                            } else {
-                                loge("Failed to set country code " + country);
-                            }
+                    if (persist) {
+                        mPersistedCountryCode = country;
+                        Settings.Global.putString(mContext.getContentResolver(),
+                                Settings.Global.WIFI_COUNTRY_CODE,
+                                country);
+                    }
+                    country = country.toUpperCase(Locale.ROOT);
+                    if (mLastSetCountryCode == null
+                            || country.equals(mLastSetCountryCode) == false) {
+                        if (mWifiNative.setCountryCode(country)) {
+                            mLastSetCountryCode = country;
+                        } else {
+                            loge("Failed to set country code " + country);
                         }
                     }
+                    mWifiP2pChannel.sendMessage(WifiP2pService.SET_COUNTRY_CODE, country);
                     break;
                 case CMD_SET_FREQUENCY_BAND:
                     int band =  message.arg1;
@@ -3160,9 +3249,7 @@
             updateBatteryWorkSource(null);
             mScanResults = new ArrayList<ScanResult>();
 
-            if (mBatchedScanSettings != null) {
-                stopBatchedScan();
-            }
+            stopBatchedScan();
 
             final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);