Merge "Revert "Fix NPE for invalid target exception""
diff --git a/Android.mk b/Android.mk
index c3e679b..6ec434c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -520,7 +520,7 @@
 	frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl \
 	frameworks/base/wifi/java/android/net/wifi/WpsInfo.aidl \
 	frameworks/base/wifi/java/android/net/wifi/ScanResult.aidl \
-        frameworks/base/wifi/java/android/net/wifi/ScanInfo.aidl \
+	frameworks/base/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl \
 	frameworks/base/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl \
 	frameworks/base/wifi/java/android/net/wifi/WifiConfiguration.aidl \
 	frameworks/base/wifi/java/android/net/wifi/WifiInfo.aidl \
@@ -1193,6 +1193,11 @@
 
 LOCAL_DX_FLAGS := --core-library
 
+ifneq ($(INCREMENTAL_BUILDS),)
+    LOCAL_PROGUARD_ENABLED := disabled
+    LOCAL_JACK_ENABLED := incremental
+endif
+
 include $(BUILD_JAVA_LIBRARY)
 
 
diff --git a/api/current.txt b/api/current.txt
index ba73ca2..5361d0a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -306,6 +306,7 @@
     field public static final int baselineAlignBottom = 16843042; // 0x1010122
     field public static final int baselineAligned = 16843046; // 0x1010126
     field public static final int baselineAlignedChildIndex = 16843047; // 0x1010127
+    field public static final int bitmap = 16844055; // 0x1010517
     field public static final int borderlessButtonStyle = 16843563; // 0x101032b
     field public static final int bottom = 16843184; // 0x10101b0
     field public static final int bottomBright = 16842957; // 0x10100cd
@@ -647,6 +648,8 @@
     field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
     field public static final int horizontalSpacing = 16843028; // 0x1010114
     field public static final int host = 16842792; // 0x1010028
+    field public static final int hotSpotX = 16844056; // 0x1010518
+    field public static final int hotSpotY = 16844057; // 0x1010519
     field public static final int hyphenationFrequency = 16843998; // 0x10104de
     field public static final int icon = 16842754; // 0x1010002
     field public static final int iconPreview = 16843337; // 0x1010249
@@ -6320,6 +6323,22 @@
     field public static final android.os.Parcelable.Creator<android.app.usage.ConfigurationStats> CREATOR;
   }
 
+  public class DataUsagePolicy {
+    field public final int networkType;
+    field public final java.lang.String[] subscriberIds;
+    field public final long thresholdInBytes;
+    field public final int[] uids;
+  }
+
+  public static class DataUsagePolicy.Builder {
+    ctor public DataUsagePolicy.Builder();
+    method public android.app.usage.DataUsagePolicy.Builder addSubscriberId(java.lang.String);
+    method public android.app.usage.DataUsagePolicy.Builder addUid(int);
+    method public android.app.usage.DataUsagePolicy build();
+    method public android.app.usage.DataUsagePolicy.Builder setNetworkType(int);
+    method public android.app.usage.DataUsagePolicy.Builder setThreshold(long);
+  }
+
   public final class NetworkStats implements java.lang.AutoCloseable {
     method public void close();
     method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
@@ -6360,6 +6379,14 @@
     method public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
     method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
     method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long, boolean) throws android.os.RemoteException, java.lang.SecurityException;
+    method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback);
+    method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback, android.os.Handler);
+    method public void unregisterDataUsageCallback(android.app.usage.NetworkStatsManager.DataUsageCallback);
+  }
+
+  public static class NetworkStatsManager.DataUsageCallback {
+    ctor public NetworkStatsManager.DataUsageCallback();
+    method public void onLimitReached();
   }
 
   public final class UsageEvents implements android.os.Parcelable {
@@ -8061,6 +8088,7 @@
     field public static final int BIND_ALLOW_OOM_MANAGEMENT = 16; // 0x10
     field public static final int BIND_AUTO_CREATE = 1; // 0x1
     field public static final int BIND_DEBUG_UNBIND = 2; // 0x2
+    field public static final int BIND_EXTERNAL_SERVICE = -2147483648; // 0x80000000
     field public static final int BIND_IMPORTANT = 64; // 0x40
     field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
     field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20
@@ -9916,6 +9944,7 @@
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
     field public static final android.os.Parcelable.Creator<android.content.pm.ServiceInfo> CREATOR;
+    field public static final int FLAG_EXTERNAL_SERVICE = 4; // 0x4
     field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
     field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
@@ -23445,6 +23474,12 @@
     method public int getUid();
   }
 
+  public class DataUsageRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.DataUsageRequest> CREATOR;
+  }
+
   public class DhcpInfo implements android.os.Parcelable {
     ctor public DhcpInfo();
     method public int describeContents();
@@ -24311,22 +24346,6 @@
 
 package android.net.wifi {
 
-  public class ScanInfo implements android.os.Parcelable {
-    ctor public ScanInfo(android.net.wifi.ScanResult);
-    ctor public ScanInfo(long, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int);
-    method public int describeContents();
-    method public long getBssid();
-    method public byte[] getIconData();
-    method public java.lang.String getIconType();
-    method public java.lang.String getName();
-    method public int getOsuIdentity();
-    method public int getRssi();
-    method public android.net.wifi.ScanResult getScanResult();
-    method public java.lang.String getServiceDescription();
-    method public java.lang.String getSsid();
-    method public void writeToParcel(android.os.Parcel, int);
-  }
-
   public class ScanResult implements android.os.Parcelable {
     method public int describeContents();
     method public boolean is80211mcResponder();
@@ -24487,6 +24506,7 @@
     field public static final int SIM = 4; // 0x4
     field public static final int TLS = 1; // 0x1
     field public static final int TTLS = 2; // 0x2
+    field public static final int UNAUTH_TLS = 7; // 0x7
   }
 
   public static final class WifiEnterpriseConfig.Phase2 {
@@ -24529,7 +24549,6 @@
     method public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
     method public android.net.wifi.WifiInfo getConnectionInfo();
     method public android.net.DhcpInfo getDhcpInfo();
-    method public java.util.List<android.net.wifi.ScanInfo> getScanInfos();
     method public java.util.List<android.net.wifi.ScanResult> getScanResults();
     method public int getWifiState();
     method public boolean is5GHzBandSupported();
@@ -24545,7 +24564,6 @@
     method public boolean reconnect();
     method public boolean removeNetwork(int);
     method public boolean saveConfiguration();
-    method public void setOsuSelection(int);
     method public void setTdlsEnabled(java.net.InetAddress, boolean);
     method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
     method public boolean setWifiEnabled(boolean);
@@ -32094,6 +32112,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
+    field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
@@ -36335,6 +36354,7 @@
     method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
     method public boolean isInCall();
     method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
+    method public void launchManageBlockedNumbersActivity();
     method public void placeCall(android.net.Uri, android.os.Bundle);
     method public void registerPhoneAccount(android.telecom.PhoneAccount);
     method public void showInCallScreen(boolean);
@@ -50188,13 +50208,16 @@
     method public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException;
     method public A getAnnotation(java.lang.Class<A>);
     method public java.lang.annotation.Annotation[] getAnnotations();
+    method public T[] getAnnotationsByType(java.lang.Class<T>);
     method public java.lang.String getCanonicalName();
     method public java.lang.ClassLoader getClassLoader();
     method public java.lang.Class<?>[] getClasses();
     method public java.lang.Class<?> getComponentType();
     method public java.lang.reflect.Constructor<T> getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
     method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException;
+    method public T getDeclaredAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public java.lang.Class<?>[] getDeclaredClasses();
     method public java.lang.reflect.Constructor<T> getDeclaredConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
     method public java.lang.reflect.Constructor<?>[] getDeclaredConstructors() throws java.lang.SecurityException;
@@ -50725,7 +50748,10 @@
   public class Package implements java.lang.reflect.AnnotatedElement {
     method public A getAnnotation(java.lang.Class<A>);
     method public java.lang.annotation.Annotation[] getAnnotations();
+    method public T[] getAnnotationsByType(java.lang.Class<T>);
+    method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public java.lang.String getImplementationTitle();
     method public java.lang.String getImplementationVendor();
     method public java.lang.String getImplementationVersion();
@@ -51419,7 +51445,10 @@
     ctor protected AccessibleObject();
     method public T getAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getAnnotations();
+    method public T[] getAnnotationsByType(java.lang.Class<T>);
+    method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public boolean isAccessible();
     method public boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
     method public static void setAccessible(java.lang.reflect.AccessibleObject[], boolean) throws java.lang.SecurityException;
@@ -51429,7 +51458,10 @@
   public abstract interface AnnotatedElement {
     method public abstract T getAnnotation(java.lang.Class<T>);
     method public abstract java.lang.annotation.Annotation[] getAnnotations();
+    method public abstract T[] getAnnotationsByType(java.lang.Class<T>);
+    method public abstract java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
     method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public abstract T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public abstract boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index 10d1b04..a43e3f6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -91,6 +91,7 @@
     field public static final java.lang.String DEVICE_POWER = "android.permission.DEVICE_POWER";
     field public static final java.lang.String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
     field public static final java.lang.String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
+    field public static final java.lang.String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
     field public static final java.lang.String DUMP = "android.permission.DUMP";
     field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
     field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST";
@@ -401,6 +402,7 @@
     field public static final int baselineAlignBottom = 16843042; // 0x1010122
     field public static final int baselineAligned = 16843046; // 0x1010126
     field public static final int baselineAlignedChildIndex = 16843047; // 0x1010127
+    field public static final int bitmap = 16844055; // 0x1010517
     field public static final int borderlessButtonStyle = 16843563; // 0x101032b
     field public static final int bottom = 16843184; // 0x10101b0
     field public static final int bottomBright = 16842957; // 0x10100cd
@@ -742,6 +744,8 @@
     field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
     field public static final int horizontalSpacing = 16843028; // 0x1010114
     field public static final int host = 16842792; // 0x1010028
+    field public static final int hotSpotX = 16844056; // 0x1010518
+    field public static final int hotSpotY = 16844057; // 0x1010519
     field public static final int hyphenationFrequency = 16843998; // 0x10104de
     field public static final int icon = 16842754; // 0x1010002
     field public static final int iconPreview = 16843337; // 0x1010249
@@ -6100,6 +6104,7 @@
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE";
+    field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
     field public static final java.lang.String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD";
     field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
@@ -6577,6 +6582,22 @@
     field public static final android.os.Parcelable.Creator<android.app.usage.ConfigurationStats> CREATOR;
   }
 
+  public class DataUsagePolicy {
+    field public final int networkType;
+    field public final java.lang.String[] subscriberIds;
+    field public final long thresholdInBytes;
+    field public final int[] uids;
+  }
+
+  public static class DataUsagePolicy.Builder {
+    ctor public DataUsagePolicy.Builder();
+    method public android.app.usage.DataUsagePolicy.Builder addSubscriberId(java.lang.String);
+    method public android.app.usage.DataUsagePolicy.Builder addUid(int);
+    method public android.app.usage.DataUsagePolicy build();
+    method public android.app.usage.DataUsagePolicy.Builder setNetworkType(int);
+    method public android.app.usage.DataUsagePolicy.Builder setThreshold(long);
+  }
+
   public final class NetworkStats implements java.lang.AutoCloseable {
     method public void close();
     method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
@@ -6617,6 +6638,14 @@
     method public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
     method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
     method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long, boolean) throws android.os.RemoteException, java.lang.SecurityException;
+    method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback);
+    method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback, android.os.Handler);
+    method public void unregisterDataUsageCallback(android.app.usage.NetworkStatsManager.DataUsageCallback);
+  }
+
+  public static class NetworkStatsManager.DataUsageCallback {
+    ctor public NetworkStatsManager.DataUsageCallback();
+    method public void onLimitReached();
   }
 
   public final class UsageEvents implements android.os.Parcelable {
@@ -10317,6 +10346,7 @@
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
     field public static final android.os.Parcelable.Creator<android.content.pm.ServiceInfo> CREATOR;
+    field public static final int FLAG_EXTERNAL_SERVICE = 4; // 0x4
     field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
     field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
@@ -25250,6 +25280,12 @@
     method public int getUid();
   }
 
+  public class DataUsageRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.DataUsageRequest> CREATOR;
+  }
+
   public class DhcpInfo implements android.os.Parcelable {
     ctor public DhcpInfo();
     method public int describeContents();
@@ -26359,22 +26395,6 @@
     field public byte id;
   }
 
-  public class ScanInfo implements android.os.Parcelable {
-    ctor public ScanInfo(android.net.wifi.ScanResult);
-    ctor public ScanInfo(long, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int);
-    method public int describeContents();
-    method public long getBssid();
-    method public byte[] getIconData();
-    method public java.lang.String getIconType();
-    method public java.lang.String getName();
-    method public int getOsuIdentity();
-    method public int getRssi();
-    method public android.net.wifi.ScanResult getScanResult();
-    method public java.lang.String getServiceDescription();
-    method public java.lang.String getSsid();
-    method public void writeToParcel(android.os.Parcel, int);
-  }
-
   public class ScanResult implements android.os.Parcelable {
     method public int describeContents();
     method public boolean is80211mcResponder();
@@ -26558,6 +26578,7 @@
     field public static final int SIM = 4; // 0x4
     field public static final int TLS = 1; // 0x1
     field public static final int TTLS = 2; // 0x2
+    field public static final int UNAUTH_TLS = 7; // 0x7
   }
 
   public static final class WifiEnterpriseConfig.Phase2 {
@@ -26603,7 +26624,6 @@
     method public android.net.wifi.WifiConnectionStatistics getConnectionStatistics();
     method public android.net.DhcpInfo getDhcpInfo();
     method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
-    method public java.util.List<android.net.wifi.ScanInfo> getScanInfos();
     method public java.util.List<android.net.wifi.ScanResult> getScanResults();
     method public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method public int getWifiApState();
@@ -26626,7 +26646,6 @@
     method public boolean reconnect();
     method public boolean removeNetwork(int);
     method public boolean saveConfiguration();
-    method public void setOsuSelection(int);
     method public void setTdlsEnabled(java.net.InetAddress, boolean);
     method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
     method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
@@ -34502,6 +34521,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
+    field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
@@ -38911,6 +38931,7 @@
     method public boolean isRinging();
     method public boolean isTtySupported();
     method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
+    method public void launchManageBlockedNumbersActivity();
     method public void placeCall(android.net.Uri, android.os.Bundle);
     method public void registerPhoneAccount(android.telecom.PhoneAccount);
     method public void showInCallScreen(boolean);
@@ -53171,13 +53192,16 @@
     method public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException;
     method public A getAnnotation(java.lang.Class<A>);
     method public java.lang.annotation.Annotation[] getAnnotations();
+    method public T[] getAnnotationsByType(java.lang.Class<T>);
     method public java.lang.String getCanonicalName();
     method public java.lang.ClassLoader getClassLoader();
     method public java.lang.Class<?>[] getClasses();
     method public java.lang.Class<?> getComponentType();
     method public java.lang.reflect.Constructor<T> getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
     method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException;
+    method public T getDeclaredAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public java.lang.Class<?>[] getDeclaredClasses();
     method public java.lang.reflect.Constructor<T> getDeclaredConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
     method public java.lang.reflect.Constructor<?>[] getDeclaredConstructors() throws java.lang.SecurityException;
@@ -53708,7 +53732,10 @@
   public class Package implements java.lang.reflect.AnnotatedElement {
     method public A getAnnotation(java.lang.Class<A>);
     method public java.lang.annotation.Annotation[] getAnnotations();
+    method public T[] getAnnotationsByType(java.lang.Class<T>);
+    method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public java.lang.String getImplementationTitle();
     method public java.lang.String getImplementationVendor();
     method public java.lang.String getImplementationVersion();
@@ -54402,7 +54429,10 @@
     ctor protected AccessibleObject();
     method public T getAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getAnnotations();
+    method public T[] getAnnotationsByType(java.lang.Class<T>);
+    method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public boolean isAccessible();
     method public boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
     method public static void setAccessible(java.lang.reflect.AccessibleObject[], boolean) throws java.lang.SecurityException;
@@ -54412,7 +54442,10 @@
   public abstract interface AnnotatedElement {
     method public abstract T getAnnotation(java.lang.Class<T>);
     method public abstract java.lang.annotation.Annotation[] getAnnotations();
+    method public abstract T[] getAnnotationsByType(java.lang.Class<T>);
+    method public abstract java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
     method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public abstract T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public abstract boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index a667a50..278a47d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -306,6 +306,7 @@
     field public static final int baselineAlignBottom = 16843042; // 0x1010122
     field public static final int baselineAligned = 16843046; // 0x1010126
     field public static final int baselineAlignedChildIndex = 16843047; // 0x1010127
+    field public static final int bitmap = 16844055; // 0x1010517
     field public static final int borderlessButtonStyle = 16843563; // 0x101032b
     field public static final int bottom = 16843184; // 0x10101b0
     field public static final int bottomBright = 16842957; // 0x10100cd
@@ -647,6 +648,8 @@
     field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
     field public static final int horizontalSpacing = 16843028; // 0x1010114
     field public static final int host = 16842792; // 0x1010028
+    field public static final int hotSpotX = 16844056; // 0x1010518
+    field public static final int hotSpotY = 16844057; // 0x1010519
     field public static final int hyphenationFrequency = 16843998; // 0x10104de
     field public static final int icon = 16842754; // 0x1010002
     field public static final int iconPreview = 16843337; // 0x1010249
@@ -6322,6 +6325,22 @@
     field public static final android.os.Parcelable.Creator<android.app.usage.ConfigurationStats> CREATOR;
   }
 
+  public class DataUsagePolicy {
+    field public final int networkType;
+    field public final java.lang.String[] subscriberIds;
+    field public final long thresholdInBytes;
+    field public final int[] uids;
+  }
+
+  public static class DataUsagePolicy.Builder {
+    ctor public DataUsagePolicy.Builder();
+    method public android.app.usage.DataUsagePolicy.Builder addSubscriberId(java.lang.String);
+    method public android.app.usage.DataUsagePolicy.Builder addUid(int);
+    method public android.app.usage.DataUsagePolicy build();
+    method public android.app.usage.DataUsagePolicy.Builder setNetworkType(int);
+    method public android.app.usage.DataUsagePolicy.Builder setThreshold(long);
+  }
+
   public final class NetworkStats implements java.lang.AutoCloseable {
     method public void close();
     method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
@@ -6362,6 +6381,14 @@
     method public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
     method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
     method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, java.lang.String, long, long, boolean) throws android.os.RemoteException, java.lang.SecurityException;
+    method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback);
+    method public void registerDataUsageCallback(android.app.usage.DataUsagePolicy, android.app.usage.NetworkStatsManager.DataUsageCallback, android.os.Handler);
+    method public void unregisterDataUsageCallback(android.app.usage.NetworkStatsManager.DataUsageCallback);
+  }
+
+  public static class NetworkStatsManager.DataUsageCallback {
+    ctor public NetworkStatsManager.DataUsageCallback();
+    method public void onLimitReached();
   }
 
   public final class UsageEvents implements android.os.Parcelable {
@@ -8065,6 +8092,7 @@
     field public static final int BIND_ALLOW_OOM_MANAGEMENT = 16; // 0x10
     field public static final int BIND_AUTO_CREATE = 1; // 0x1
     field public static final int BIND_DEBUG_UNBIND = 2; // 0x2
+    field public static final int BIND_EXTERNAL_SERVICE = -2147483648; // 0x80000000
     field public static final int BIND_IMPORTANT = 64; // 0x40
     field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
     field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20
@@ -9924,6 +9952,7 @@
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
     field public static final android.os.Parcelable.Creator<android.content.pm.ServiceInfo> CREATOR;
+    field public static final int FLAG_EXTERNAL_SERVICE = 4; // 0x4
     field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
     field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
@@ -23454,6 +23483,12 @@
     method public int getUid();
   }
 
+  public class DataUsageRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.DataUsageRequest> CREATOR;
+  }
+
   public class DhcpInfo implements android.os.Parcelable {
     ctor public DhcpInfo();
     method public int describeContents();
@@ -24320,22 +24355,6 @@
 
 package android.net.wifi {
 
-  public class ScanInfo implements android.os.Parcelable {
-    ctor public ScanInfo(android.net.wifi.ScanResult);
-    ctor public ScanInfo(long, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int);
-    method public int describeContents();
-    method public long getBssid();
-    method public byte[] getIconData();
-    method public java.lang.String getIconType();
-    method public java.lang.String getName();
-    method public int getOsuIdentity();
-    method public int getRssi();
-    method public android.net.wifi.ScanResult getScanResult();
-    method public java.lang.String getServiceDescription();
-    method public java.lang.String getSsid();
-    method public void writeToParcel(android.os.Parcel, int);
-  }
-
   public class ScanResult implements android.os.Parcelable {
     method public int describeContents();
     method public boolean is80211mcResponder();
@@ -24496,6 +24515,7 @@
     field public static final int SIM = 4; // 0x4
     field public static final int TLS = 1; // 0x1
     field public static final int TTLS = 2; // 0x2
+    field public static final int UNAUTH_TLS = 7; // 0x7
   }
 
   public static final class WifiEnterpriseConfig.Phase2 {
@@ -24538,7 +24558,6 @@
     method public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
     method public android.net.wifi.WifiInfo getConnectionInfo();
     method public android.net.DhcpInfo getDhcpInfo();
-    method public java.util.List<android.net.wifi.ScanInfo> getScanInfos();
     method public java.util.List<android.net.wifi.ScanResult> getScanResults();
     method public int getWifiState();
     method public boolean is5GHzBandSupported();
@@ -24554,7 +24573,6 @@
     method public boolean reconnect();
     method public boolean removeNetwork(int);
     method public boolean saveConfiguration();
-    method public void setOsuSelection(int);
     method public void setTdlsEnabled(java.net.InetAddress, boolean);
     method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
     method public boolean setWifiEnabled(boolean);
@@ -32107,6 +32125,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
+    field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
@@ -36350,6 +36369,7 @@
     method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
     method public boolean isInCall();
     method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
+    method public void launchManageBlockedNumbersActivity();
     method public void placeCall(android.net.Uri, android.os.Bundle);
     method public void registerPhoneAccount(android.telecom.PhoneAccount);
     method public void showInCallScreen(boolean);
@@ -50205,13 +50225,16 @@
     method public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException;
     method public A getAnnotation(java.lang.Class<A>);
     method public java.lang.annotation.Annotation[] getAnnotations();
+    method public T[] getAnnotationsByType(java.lang.Class<T>);
     method public java.lang.String getCanonicalName();
     method public java.lang.ClassLoader getClassLoader();
     method public java.lang.Class<?>[] getClasses();
     method public java.lang.Class<?> getComponentType();
     method public java.lang.reflect.Constructor<T> getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
     method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException;
+    method public T getDeclaredAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public java.lang.Class<?>[] getDeclaredClasses();
     method public java.lang.reflect.Constructor<T> getDeclaredConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
     method public java.lang.reflect.Constructor<?>[] getDeclaredConstructors() throws java.lang.SecurityException;
@@ -50742,7 +50765,10 @@
   public class Package implements java.lang.reflect.AnnotatedElement {
     method public A getAnnotation(java.lang.Class<A>);
     method public java.lang.annotation.Annotation[] getAnnotations();
+    method public T[] getAnnotationsByType(java.lang.Class<T>);
+    method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public java.lang.String getImplementationTitle();
     method public java.lang.String getImplementationVendor();
     method public java.lang.String getImplementationVersion();
@@ -51436,7 +51462,10 @@
     ctor protected AccessibleObject();
     method public T getAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getAnnotations();
+    method public T[] getAnnotationsByType(java.lang.Class<T>);
+    method public java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
     method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public boolean isAccessible();
     method public boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
     method public static void setAccessible(java.lang.reflect.AccessibleObject[], boolean) throws java.lang.SecurityException;
@@ -51446,7 +51475,10 @@
   public abstract interface AnnotatedElement {
     method public abstract T getAnnotation(java.lang.Class<T>);
     method public abstract java.lang.annotation.Annotation[] getAnnotations();
+    method public abstract T[] getAnnotationsByType(java.lang.Class<T>);
+    method public abstract java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
     method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
+    method public abstract T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
     method public abstract boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
   }
 
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 9d81c43..a74a1ca 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -1774,18 +1774,33 @@
             System.err.println("Error: invalid input bounds");
             return;
         }
-        resizeStack(stackId, bounds, 0, false);
+        resizeStack(stackId, bounds, 0);
     }
 
     private void runStackResizeAnimated() throws Exception {
         String stackIdStr = nextArgRequired();
         int stackId = Integer.valueOf(stackIdStr);
-        final Rect bounds = getBounds();
-        if (bounds == null) {
-            System.err.println("Error: invalid input bounds");
-            return;
+        final Rect bounds;
+        if ("null".equals(mArgs.peekNextArg())) {
+            bounds = null;
+        } else {
+            bounds = getBounds();
+            if (bounds == null) {
+                System.err.println("Error: invalid input bounds");
+                return;
+            }
         }
-        resizeStack(stackId, bounds, 0, true);
+        resizeStackUnchecked(stackId, bounds, 0, true);
+    }
+
+    private void resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate) {
+        try {
+            mAm.resizeStack(stackId, bounds, false, false, animate);
+            Thread.sleep(delayMs);
+        } catch (RemoteException e) {
+            showError("Error: resizing stack " + e);
+        } catch (InterruptedException e) {
+        }
     }
 
     private void runStackResizeDocked() throws Exception {
@@ -1802,20 +1817,13 @@
         }
     }
 
-    private void resizeStack(int stackId, Rect bounds, int delayMs, boolean animate)
+    private void resizeStack(int stackId, Rect bounds, int delayMs)
             throws Exception {
         if (bounds == null) {
             showError("Error: invalid input bounds");
             return;
         }
-
-        try {
-            mAm.resizeStack(stackId, bounds, false, false, animate);
-            Thread.sleep(delayMs);
-        } catch (RemoteException e) {
-            showError("Error: resizing stack " + e);
-        } catch (InterruptedException e) {
-        }
+        resizeStackUnchecked(stackId, bounds, delayMs, false);
     }
 
     private void runStackPositionTask() throws Exception {
@@ -1924,7 +1932,7 @@
             maxChange = Math.min(stepSize, currentPoint - minPoint);
             currentPoint -= maxChange;
             setBoundsSide(bounds, side, currentPoint);
-            resizeStack(DOCKED_STACK_ID, bounds, delayMs, false);
+            resizeStack(DOCKED_STACK_ID, bounds, delayMs);
         }
 
         System.out.println("Growing docked stack side=" + side);
@@ -1932,7 +1940,7 @@
             maxChange = Math.min(stepSize, maxPoint - currentPoint);
             currentPoint += maxChange;
             setBoundsSide(bounds, side, currentPoint);
-            resizeStack(DOCKED_STACK_ID, bounds, delayMs, false);
+            resizeStack(DOCKED_STACK_ID, bounds, delayMs);
         }
 
         System.out.println("Back to Original size side=" + side);
@@ -1940,7 +1948,7 @@
             maxChange = Math.min(stepSize, currentPoint - startPoint);
             currentPoint -= maxChange;
             setBoundsSide(bounds, side, currentPoint);
-            resizeStack(DOCKED_STACK_ID, bounds, delayMs, false);
+            resizeStack(DOCKED_STACK_ID, bounds, delayMs);
         }
     }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9bad2f9..02eb115 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -241,6 +241,48 @@
 
     /**
      * Activity action: Starts the provisioning flow which sets up a managed device.
+     *
+     * <p>During device owner provisioning, a device admin app is downloaded and set as the owner of
+     * the device. A device owner has full control over the device. The device owner can not be
+     * modified by the user and the only way of resetting the device is via factory reset.
+     *
+     * <p>A typical use case would be a device that is owned by a company, but used by either an
+     * employee or client.
+     *
+     * <p>The provisioning message should be sent to an unprovisioned device.
+     *
+     * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent
+     * by a privileged app with the permission
+     * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}.
+     *
+     * <p>The provisioning intent contains the following properties:
+     * <ul>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_LOCAL_TIME} (convert to String), optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_TIME_ZONE}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_LOCALE}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_WIFI_SSID}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_WIFI_HIDDEN} (convert to String), optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_WIFI_PASSWORD}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT} (convert to String), optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li></ul>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE =
+            "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+
+    /**
+     * Activity action: Starts the provisioning flow which sets up a managed device.
      * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
      *
      * <p>NOTE: This is only supported on split system user devices, and puts the device into a
@@ -4067,6 +4109,29 @@
     }
 
     /**
+     * Called by the system to check if a specific accessibility service is disabled by admin.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param packageName Accessibility service package name that needs to be checked.
+     * @param userHandle user id the admin is running as.
+     * @return true if the accessibility service is permitted, otherwise false.
+     *
+     * @hide
+     */
+    public boolean isAccessibilityServicePermittedByAdmin(@NonNull ComponentName admin,
+            @NonNull String packageName, int userHandle) {
+        if (mService != null) {
+            try {
+                return mService.isAccessibilityServicePermittedByAdmin(admin, packageName,
+                        userHandle);
+            } catch (RemoteException e) {
+                Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+            }
+        }
+        return false;
+    }
+
+    /**
      * Returns the list of accessibility services permitted by the device or profiles
      * owners of this user.
      *
@@ -4146,6 +4211,28 @@
     }
 
     /**
+     * Called by the system to check if a specific input method is disabled by admin.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param packageName Input method package name that needs to be checked.
+     * @param userHandle user id the admin is running as.
+     * @return true if the input method is permitted, otherwise false.
+     *
+     * @hide
+     */
+    public boolean isInputMethodPermittedByAdmin(@NonNull ComponentName admin,
+            @NonNull String packageName, int userHandle) {
+        if (mService != null) {
+            try {
+                return mService.isInputMethodPermittedByAdmin(admin, packageName, userHandle);
+            } catch (RemoteException e) {
+                Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+            }
+        }
+        return false;
+    }
+
+    /**
      * Returns the list of input methods permitted by the device or profiles
      * owners of the current user.  (*Not* calling user, due to a limitation in InputMethodManager.)
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index b57e1b7..c6a5344 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -174,10 +174,12 @@
     boolean setPermittedAccessibilityServices(in ComponentName admin,in List packageList);
     List getPermittedAccessibilityServices(in ComponentName admin);
     List getPermittedAccessibilityServicesForUser(int userId);
+    boolean isAccessibilityServicePermittedByAdmin(in ComponentName admin, String packageName, int userId);
 
     boolean setPermittedInputMethods(in ComponentName admin,in List packageList);
     List getPermittedInputMethods(in ComponentName admin);
     List getPermittedInputMethodsForCurrentUser();
+    boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId);
 
     boolean setApplicationHidden(in ComponentName admin, in String packageName, boolean hidden);
     boolean isApplicationHidden(in ComponentName admin, in String packageName);
diff --git a/core/java/android/app/usage/DataUsagePolicy.java b/core/java/android/app/usage/DataUsagePolicy.java
new file mode 100644
index 0000000..5a5dcbc
--- /dev/null
+++ b/core/java/android/app/usage/DataUsagePolicy.java
@@ -0,0 +1,174 @@
+/**
+ * Copyright (C) 2016 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.app.usage;
+
+import android.net.ConnectivityManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.IntArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Defines a policy for data usage callbacks, made through {@link DataUsagePolicy.Builder} and used
+ * to be notified on data usage via {@link NetworkStatsManager#registerDataUsageCallback}.
+ */
+public class DataUsagePolicy {
+
+    /**
+     * Network type to be monitored, as defined in {@link ConnectivityManager}, e.g.
+     * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} etc.
+     */
+    public final int networkType;
+
+    /**
+     * Set of subscriber ids to be monitored for the given network type. May be empty if not
+     * applicable.
+     * <p>Should not be modified once created.
+     */
+    public final String[] subscriberIds;
+
+    /**
+     * Set of UIDs of which to monitor data usage.
+     *
+     * <p>If not {@code null}, the caller will be notified when any of the uids exceed
+     * the given threshold. If empty all uids for which the calling process has access
+     * to stats will be monitored.
+     * <p>Should not be modified once created.
+     */
+    public final int[] uids;
+
+    /**
+     * Threshold in bytes to be notified on.
+     */
+    public final long thresholdInBytes;
+
+    /**
+     * @hide
+     */
+    DataUsagePolicy(int networkType, String[] subscriberIds, int[] uids,
+                long thresholdInBytes) {
+        this.networkType = networkType;
+        this.subscriberIds = subscriberIds;
+        this.uids = uids;
+        this.thresholdInBytes = thresholdInBytes;
+    }
+
+    /**
+     * Builder used to create {@link DataUsagePolicy} objects.
+     */
+    public static class Builder {
+        private static final int INVALID_NETWORK_TYPE = -1;
+        private int mNetworkType = INVALID_NETWORK_TYPE;
+        private List<String> mSubscriberList = new ArrayList<>();
+        private IntArray mUids = new IntArray();
+        private long mThresholdInBytes;
+
+        /**
+         * Default constructor for Builder.
+         */
+        public Builder() {}
+
+        /**
+         * Build {@link DataUsagePolicy} given the current policies.
+         */
+        public DataUsagePolicy build() {
+            if (mNetworkType == INVALID_NETWORK_TYPE) {
+                throw new IllegalArgumentException(
+                        "DataUsagePolicy requires a valid network type to be set");
+            }
+            return new DataUsagePolicy(mNetworkType,
+                    mSubscriberList.toArray(new String[mSubscriberList.size()]),
+                    mUids.toArray(), mThresholdInBytes);
+        }
+
+        /**
+         * Specifies that the given {@code subscriberId} should be monitored.
+         *
+         * @param subscriberId the subscriber id of the network interface.
+         */
+        public Builder addSubscriberId(String subscriberId) {
+            mSubscriberList.add(subscriberId);
+            return this;
+        }
+
+        /**
+         * Specifies that the given {@code uid} should be monitored.
+         */
+        public Builder addUid(int uid) {
+            mUids.add(uid);
+            return this;
+        }
+
+        /**
+         * Specifies that the callback should monitor the given network. It is mandatory
+         * to set one.
+         *
+         * @param networkType As defined in {@link ConnectivityManager}, e.g.
+         *            {@link ConnectivityManager#TYPE_MOBILE},
+         *            {@link ConnectivityManager#TYPE_WIFI}, etc.
+         */
+        public Builder setNetworkType(int networkType) {
+            mNetworkType = networkType;
+            return this;
+        }
+
+        /**
+         * Sets the threshold in bytes on which the listener should be called. The framework may
+         * impose a minimum threshold to avoid too many notifications to be triggered.
+         */
+        public Builder setThreshold(long thresholdInBytes) {
+            mThresholdInBytes = thresholdInBytes;
+            return this;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "DataUsagePolicy [ networkType=" + networkType
+                + ", subscriberIds=" + Arrays.toString(subscriberIds)
+                + ", uids=" + Arrays.toString(uids)
+                + ", thresholdInBytes=" + thresholdInBytes + " ]";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof DataUsagePolicy == false) return false;
+        DataUsagePolicy that = (DataUsagePolicy) obj;
+        return that.networkType == this.networkType
+                && Arrays.deepEquals(that.subscriberIds, this.subscriberIds)
+                && Arrays.equals(that.uids, this.uids)
+                && that.thresholdInBytes == this.thresholdInBytes;
+    }
+
+    @Override
+    public int hashCode() {
+        // Start with a non-zero constant.
+        int result = 17;
+
+        // Include a hash for each field.
+        result = 31 * result + networkType;
+        result = 31 * result + Arrays.deepHashCode(subscriberIds);
+        result = 31 * result + Arrays.hashCode(uids);
+        result = 31 * result + (int) (thresholdInBytes ^ (thresholdInBytes >>> 32));
+
+        return result;
+   }
+}
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 4dc636b..13aeef0 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -16,12 +16,17 @@
 
 package android.app.usage;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.Nullable;
 import android.app.usage.NetworkStats.Bucket;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.DataUsageRequest;
 import android.net.NetworkIdentity;
 import android.net.NetworkTemplate;
 import android.os.Build;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -288,6 +293,62 @@
         return result;
     }
 
+    /**
+     * Registers to receive notifications about data usage on specified networks and uids.
+     * The callbacks will continue to be called as long as the process is live or
+     * {@link #unregisterDataUsageCallback} is called.
+     *
+     * @param policy {@link DataUsagePolicy} describing this request.
+     * @param callback The {@link DataUsageCallback} that the system will call when data usage
+     *            has exceeded the specified threshold.
+     */
+    public void registerDataUsageCallback(DataUsagePolicy policy, DataUsageCallback callback) {
+        registerDataUsageCallback(policy, callback, null /* handler */);
+    }
+
+    /**
+     * Registers to receive notifications about data usage on specified networks and uids.
+     * The callbacks will continue to be called as long as the process is live or
+     * {@link #unregisterDataUsageCallback} is called.
+     *
+     * @param policy {@link DataUsagePolicy} describing this request.
+     * @param callback The {@link DataUsageCallback} that the system will call when data usage
+     *            has exceeded the specified threshold.
+     * @param handler to dispatch callback events through, otherwise if {@code null} it uses
+     *            the calling thread.
+     */
+    public void registerDataUsageCallback(DataUsagePolicy policy, DataUsageCallback callback,
+                @Nullable Handler handler) {
+        checkNotNull(policy, "DataUsagePolicy cannot be null");
+        checkNotNull(callback, "DataUsageCallback cannot be null");
+
+        // TODO: Implement stub.
+    }
+
+    /**
+     * Unregisters callbacks on data usage.
+     *
+     * @param callback The {@link DataUsageCallback} used when registering.
+     */
+    public void unregisterDataUsageCallback(DataUsageCallback callback) {
+        checkNotNull(callback, "DataUsageCallback cannot be null");
+
+        // TODO: Implement stub.
+    }
+
+    /**
+     * Base class for data usage callbacks. Should be extended by applications wanting
+     * notifications.
+     */
+    public static class DataUsageCallback {
+        /**
+         * Called when data usage has reached the given policy threshold.
+         */
+        public void onLimitReached() {}
+
+        private DataUsageRequest request;
+    }
+
     private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
         NetworkTemplate template = null;
         switch (networkType) {
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 55b8f03..bed91ec 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -29,6 +29,7 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -49,6 +50,8 @@
 import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
 import android.widget.TextView;
 
+import java.util.concurrent.Executor;
+
 /**
  * Provides the glue to show AppWidget views. This class offers automatic animation
  * between updates, and will try recycling old views for each incoming
@@ -87,6 +90,9 @@
     Paint mOldPaint = new Paint();
     private OnClickHandler mOnClickHandler;
 
+    private Executor mAsyncExecutor;
+    private CancellationSignal mLastExecutionSignal;
+
     /**
      * Create a host view.  Uses default fade animations.
      */
@@ -340,6 +346,22 @@
     }
 
     /**
+     * Sets an executor which can be used for asynchronously inflating and applying the remoteviews.
+     * @see {@link RemoteViews#applyAsync(Context, ViewGroup, RemoteViews.OnViewAppliedListener, Executor)}
+     *
+     * @param executor the executor to use or null.
+     * @hide
+     */
+    public void setAsyncExecutor(Executor executor) {
+        if (mLastExecutionSignal != null) {
+            mLastExecutionSignal.cancel();
+            mLastExecutionSignal = null;
+        }
+
+        mAsyncExecutor = executor;
+    }
+
+    /**
      * Update the AppWidgetProviderInfo for this view, and reset it to the
      * initial layout.
      */
@@ -380,6 +402,11 @@
             }
         }
 
+        if (mLastExecutionSignal != null) {
+            mLastExecutionSignal.cancel();
+            mLastExecutionSignal = null;
+        }
+
         if (remoteViews == null) {
             if (mViewMode == VIEW_MODE_DEFAULT) {
                 // We've already done this -- nothing to do.
@@ -389,6 +416,10 @@
             mLayoutId = -1;
             mViewMode = VIEW_MODE_DEFAULT;
         } else {
+            if (mAsyncExecutor != null) {
+                inflateAsync(remoteViews);
+                return;
+            }
             // Prepare a local reference to the remote Context so we're ready to
             // inflate any requested LayoutParams.
             mRemoteContext = getRemoteContext();
@@ -421,6 +452,10 @@
             mViewMode = VIEW_MODE_CONTENT;
         }
 
+        applyContent(content, recycled, exception);
+    }
+
+    private void applyContent(View content, boolean recycled, Exception exception) {
         if (content == null) {
             if (mViewMode == VIEW_MODE_ERROR) {
                 // We've already done this -- nothing to do.
@@ -452,6 +487,68 @@
         }
     }
 
+    private void inflateAsync(RemoteViews remoteViews) {
+        // Prepare a local reference to the remote Context so we're ready to
+        // inflate any requested LayoutParams.
+        mRemoteContext = getRemoteContext();
+        int layoutId = remoteViews.getLayoutId();
+
+        // If our stale view has been prepared to match active, and the new
+        // layout matches, try recycling it
+        if (layoutId == mLayoutId && mView != null) {
+            try {
+                mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
+                        mView,
+                        mAsyncExecutor,
+                        new ViewApplyListener(remoteViews, layoutId, true),
+                        mOnClickHandler);
+            } catch (Exception e) {
+                // Reapply failed. Try apply
+            }
+        }
+        if (mLastExecutionSignal == null) {
+            mLastExecutionSignal = remoteViews.applyAsync(mContext,
+                    this,
+                    mAsyncExecutor,
+                    new ViewApplyListener(remoteViews, layoutId, false),
+                    mOnClickHandler);
+        }
+    }
+
+    private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
+        private final RemoteViews mViews;
+        private final boolean mIsReapply;
+        private final int mLayoutId;
+
+        public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
+            mViews = views;
+            mLayoutId = layoutId;
+            mIsReapply = isReapply;
+        }
+
+        @Override
+        public void onViewApplied(View v) {
+            AppWidgetHostView.this.mLayoutId = mLayoutId;
+            mViewMode = VIEW_MODE_CONTENT;
+
+            applyContent(v, mIsReapply, null);
+        }
+
+        @Override
+        public void onError(Exception e) {
+            if (mIsReapply) {
+                // Try a fresh replay
+                mLastExecutionSignal = mViews.applyAsync(mContext,
+                        AppWidgetHostView.this,
+                        mAsyncExecutor,
+                        new ViewApplyListener(mViews, mLayoutId, false),
+                        mOnClickHandler);
+            } else {
+                applyContent(null, false, e);
+            }
+        }
+    }
+
     /**
      * Process data-changed notifications for the specified view in the specified
      * set of {@link RemoteViews} views.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 622aad9..4918914 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -342,9 +342,7 @@
      * {@link android.R.attr#isolatedProcess isolated},
      * {@link android.R.attr#externalService external} service.  This binds the service into the
      * calling application's package, rather than the package in which the service is declared.
-     * @hide
      */
-    @SystemApi
     public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
 
     /**
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index eecf0de..6bd285a 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -52,7 +52,6 @@
      * Bit in {@link #flags}: If set, the service can be bound and run in the
      * calling application's package, rather than the package in which it is
      * declared.  Set from {@link android.R.attr#externalService} attribute.
-     * @hide
      */
     public static final int FLAG_EXTERNAL_SERVICE = 0x0004;
 
diff --git a/wifi/java/android/net/wifi/ScanInfo.aidl b/core/java/android/net/DataUsageRequest.aidl
similarity index 83%
copy from wifi/java/android/net/wifi/ScanInfo.aidl
copy to core/java/android/net/DataUsageRequest.aidl
index 18ae508..d1937c7 100644
--- a/wifi/java/android/net/wifi/ScanInfo.aidl
+++ b/core/java/android/net/DataUsageRequest.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2015, The Android Open Source Project
+ * Copyright (c) 2016, 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.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.wifi;
+package android.net;
 
-parcelable ScanInfo;
+parcelable DataUsageRequest;
diff --git a/core/java/android/net/DataUsageRequest.java b/core/java/android/net/DataUsageRequest.java
new file mode 100644
index 0000000..0e46f4c
--- /dev/null
+++ b/core/java/android/net/DataUsageRequest.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright (C) 2016 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.net;
+
+import android.net.NetworkTemplate;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Defines a request to register a callbacks. Used to be notified on data usage via
+ * {@link android.app.usage.NetworkStatsManager#registerDataUsageCallback}.
+ * If no {@code uid}s are set, callbacks are restricted to device-owners,
+ * carrier-privileged apps, or system apps.
+ */
+public class DataUsageRequest implements Parcelable {
+
+    /**
+     * @hide
+     */
+    public static final int REQUEST_ID_UNSET = 0;
+
+    /**
+     * Identifies the request.  {@link DataUsageRequest}s should only be constructed by
+     * the Framework and it is used internally to identify the request.
+     * @hide
+     */
+    public final int requestId;
+
+    /**
+     * Set of {@link NetworkTemplate}s describing the networks to monitor.
+     * @hide
+     */
+    public final NetworkTemplate[] templates;
+
+    /**
+     * Set of UIDs of which to monitor data usage.
+     *
+     * <p>If not {@code null}, the caller will be notified when any of the uids exceed
+     * the given threshold. If {@code null} all uids for which the calling process has access
+     * to stats will be monitored.
+     * @hide
+     */
+    public final int[] uids;
+
+    /**
+     * Threshold in bytes to be notified on.
+     * @hide
+     */
+    public final long thresholdInBytes;
+
+    /**
+     * @hide
+     */
+    public DataUsageRequest(int requestId, NetworkTemplate[] templates, int[] uids,
+                long thresholdInBytes) {
+        this.requestId = requestId;
+        this.templates = templates;
+        this.uids = uids;
+        this.thresholdInBytes = thresholdInBytes;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(requestId);
+        dest.writeTypedArray(templates, flags);
+        dest.writeIntArray(uids);
+        dest.writeLong(thresholdInBytes);
+    }
+
+    public static final Creator<DataUsageRequest> CREATOR =
+            new Creator<DataUsageRequest>() {
+                @Override
+                public DataUsageRequest createFromParcel(Parcel in) {
+                    int requestId = in.readInt();
+                    NetworkTemplate[] templates = in.createTypedArray(NetworkTemplate.CREATOR);
+                    int[] uids = in.createIntArray();
+                    long thresholdInBytes = in.readLong();
+                    DataUsageRequest result = new DataUsageRequest(requestId,
+                            templates, uids, thresholdInBytes);
+                    return result;
+                }
+
+                @Override
+                public DataUsageRequest[] newArray(int size) {
+                    return new DataUsageRequest[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "DataUsageRequest [ requestId=" + requestId
+                + ", networkTemplates=" + Arrays.toString(templates)
+                + ", uids=" + Arrays.toString(uids)
+                + ", thresholdInBytes=" + thresholdInBytes + " ]";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof DataUsageRequest == false) return false;
+        DataUsageRequest that = (DataUsageRequest) obj;
+        return that.requestId == this.requestId
+                && Arrays.deepEquals(that.templates, this.templates)
+                && Arrays.equals(that.uids, this.uids)
+                && that.thresholdInBytes == this.thresholdInBytes;
+    }
+
+    @Override
+    public int hashCode() {
+        // Start with a non-zero constant.
+        int result = 17;
+
+        // Include a hash for each field.
+        result = 31 * result + requestId;
+        result = 31 * result + Arrays.deepHashCode(templates);
+        result = 31 * result + Arrays.hashCode(uids);
+        result = 31 * result + (int) (thresholdInBytes ^ (thresholdInBytes >>> 32));
+
+        return result;
+   }
+
+}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index 54d1090..fc804e5 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -234,6 +234,16 @@
         }
     }
 
+    public String peekNextArg() {
+        if (mCurArgData != null) {
+            return mCurArgData;
+        } else if (mArgPos < mArgs.length) {
+            return mArgs[mArgPos];
+        } else {
+            return null;
+        }
+    }
+
     /**
      * Return the next argument on the command line, whatever it is; if there are
      * no arguments left, throws an IllegalArgumentException to report this to the user.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5535eaa..dfc8601 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -670,14 +670,14 @@
             "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
 
     /**
-     * Activity Action: Ask the user to allow an to ignore battery optimizations (that is,
+     * Activity Action: Ask the user to allow an app to ignore battery optimizations (that is,
      * put them on the whitelist of apps shown by
      * {@link #ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}).  For an app to use this, it also
      * must hold the {@link android.Manifest.permission#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS}
      * permission.
      * <p><b>Note:</b> most applications should <em>not</em> use this; there are many facilities
      * provided by the platform for applications to operate correctly in the various power
-     * saving mode.  This is only for unusual applications that need to deeply control their own
+     * saving modes.  This is only for unusual applications that need to deeply control their own
      * execution, at the potential expense of the user's battery life.  Note that these applications
      * greatly run the risk of showing to the user as high power consumers on their device.</p>
      * <p>
@@ -695,6 +695,24 @@
             "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
 
     /**
+     * Activity Action: Show screen for controlling which apps can ignore background data
+     * restrictions.
+     * <p>
+     * Input: if the Intent's data URI is set with an application name (using the "package" schema,
+     * like "package:com.my.app"), then when the screen is displayed it will focus on such app. If
+     * the data is not set, it will just open the screen.
+     * <p>
+     * Output: Nothing.
+     * <p>
+     * Applications can also use {@link android.net.ConnectivityManager#getRestrictBackgroundStatus
+     * ConnectivityManager#getRestrictBackgroundStatus()} to determine the status of the background
+     * data restrictions for them.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS =
+            "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
+
+    /**
      * @hide
      * Activity Action: Show the "app ops" settings screen.
      * <p>
diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java
index a535480..675803c 100644
--- a/core/java/android/text/BidiFormatter.java
+++ b/core/java/android/text/BidiFormatter.java
@@ -293,7 +293,7 @@
      * directionality is determined by scanning the end of the string, the overall directionality is
      * given explicitly by a heuristic to estimate the {@code str}'s directionality.
      *
-     * @param str String after which the mark may need to appear.
+     * @param str CharSequence after which the mark may need to appear.
      * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
      *                  directionality.
      * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
@@ -301,7 +301,7 @@
      *
      * @hide
      */
-    public String markAfter(String str, TextDirectionHeuristic heuristic) {
+    public String markAfter(CharSequence str, TextDirectionHeuristic heuristic) {
         final boolean isRtl = heuristic.isRtl(str, 0, str.length());
         // getExitDir() is called only if needed (short-circuit).
         if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
@@ -322,7 +322,7 @@
      * entry directionality is determined by scanning the beginning of the string, the overall
      * directionality is given explicitly by a heuristic to estimate the {@code str}'s directionality.
      *
-     * @param str String before which the mark may need to appear.
+     * @param str CharSequence before which the mark may need to appear.
      * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
      *                  directionality.
      * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
@@ -330,7 +330,7 @@
      *
      * @hide
      */
-    public String markBefore(String str, TextDirectionHeuristic heuristic) {
+    public String markBefore(CharSequence str, TextDirectionHeuristic heuristic) {
         final boolean isRtl = heuristic.isRtl(str, 0, str.length());
         // getEntryDir() is called only if needed (short-circuit).
         if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
@@ -350,6 +350,13 @@
      *          false.
      */
     public boolean isRtl(String str) {
+        return isRtl((CharSequence) str);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isRtl(CharSequence str) {
         return mDefaultTextDirectionHeuristic.isRtl(str, 0, str.length());
     }
 
@@ -384,9 +391,16 @@
      *     {@code null}.
      */
     public String unicodeWrap(String str, TextDirectionHeuristic heuristic, boolean isolate) {
+        return unicodeWrap((CharSequence) str, heuristic, isolate).toString();
+    }
+
+    /**
+     * @hide
+     */
+    public CharSequence unicodeWrap(CharSequence str, TextDirectionHeuristic heuristic, boolean isolate) {
         if (str == null) return null;
         final boolean isRtl = heuristic.isRtl(str, 0, str.length());
-        StringBuilder result = new StringBuilder();
+        SpannableStringBuilder result = new SpannableStringBuilder();
         if (getStereoReset() && isolate) {
             result.append(markBefore(str,
                     isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
@@ -402,7 +416,7 @@
             result.append(markAfter(str,
                     isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
         }
-        return result.toString();
+        return result;
     }
 
     /**
@@ -419,6 +433,14 @@
     }
 
     /**
+     * @hide
+     */
+    public CharSequence unicodeWrap(CharSequence str, TextDirectionHeuristic heuristic) {
+        return unicodeWrap(str, heuristic, true /* isolate */);
+    }
+
+
+    /**
      * Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but uses the
      * formatter's default direction estimation algorithm.
      *
@@ -432,6 +454,13 @@
     }
 
     /**
+     * @hide
+     */
+    public CharSequence unicodeWrap(CharSequence str, boolean isolate) {
+        return unicodeWrap(str, mDefaultTextDirectionHeuristic, isolate);
+    }
+
+    /**
      * Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but uses the
      * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
      *
@@ -442,6 +471,13 @@
         return unicodeWrap(str, mDefaultTextDirectionHeuristic, true /* isolate */);
     }
 
+    /**
+     * @hide
+     */
+    public CharSequence unicodeWrap(CharSequence str) {
+        return unicodeWrap(str, mDefaultTextDirectionHeuristic, true /* isolate */);
+    }
+
     private static BidiFormatter getDefaultInstanceFromContext(boolean isRtlContext) {
         return isRtlContext ? DEFAULT_RTL_INSTANCE : DEFAULT_LTR_INSTANCE;
     }
@@ -477,7 +513,7 @@
      *
      * @param str the string to check.
      */
-    private static int getExitDir(String str) {
+    private static int getExitDir(CharSequence str) {
         return new DirectionalityEstimator(str, false /* isHtml */).getExitDir();
     }
 
@@ -494,7 +530,7 @@
      *
      * @param str the string to check.
      */
-    private static int getEntryDir(String str) {
+    private static int getEntryDir(CharSequence str) {
         return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir();
     }
 
@@ -532,7 +568,7 @@
         /**
          * The text to be scanned.
          */
-        private final String text;
+        private final CharSequence text;
 
         /**
          * Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and
@@ -565,7 +601,7 @@
          * @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over
          *     tags and entities.
          */
-        DirectionalityEstimator(String text, boolean isHtml) {
+        DirectionalityEstimator(CharSequence text, boolean isHtml) {
             this.text = text;
             this.isHtml = isHtml;
             length = text.length();
@@ -896,4 +932,4 @@
             return Character.DIRECTIONALITY_OTHER_NEUTRALS;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 501c6e9..b9a7421 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -21,7 +21,6 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
@@ -35,7 +34,7 @@
 @RemoteViews.RemoteView
 public class NotificationHeaderView extends ViewGroup {
     public static final int NO_COLOR = -1;
-    private final int mHeaderMinWidth;
+    private final int mChildMinWidth;
     private final int mExpandTopPadding;
     private final int mContentEndMargin;
     private View mAppName;
@@ -46,6 +45,7 @@
     private View mIcon;
     private TextView mChildCount;
     private View mProfileBadge;
+    private View mInfo;
     private int mIconColor;
     private int mOriginalNotificationColor;
     private boolean mGroupHeader;
@@ -66,7 +66,7 @@
 
     public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mHeaderMinWidth = getResources().getDimensionPixelSize(
+        mChildMinWidth = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.notification_header_shrink_min_width);
         mContentEndMargin = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.notification_content_margin_end);
@@ -82,6 +82,7 @@
         mIcon = findViewById(com.android.internal.R.id.icon);
         mChildCount = (TextView) findViewById(com.android.internal.R.id.number_of_children);
         mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
+        mInfo = findViewById(com.android.internal.R.id.header_content_info);
     }
 
     @Override
@@ -109,14 +110,23 @@
         }
         if (totalWidth > givenWidth) {
             int overFlow = totalWidth - givenWidth;
-            // We are overflowing, lets shrink
+            // We are overflowing, lets shrink the info first
+            final int infoWidth = mInfo.getMeasuredWidth();
+            if (mInfo.getVisibility() != GONE && infoWidth > mChildMinWidth) {
+                int newSize = infoWidth - Math.min(infoWidth - mChildMinWidth, overFlow);
+                int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+                mInfo.measure(childWidthSpec, wrapContentHeightSpec);
+                overFlow -= infoWidth - newSize;
+            }
+            // still overflowing, lets shrink the app name now
             final int appWidth = mAppName.getMeasuredWidth();
-            if (mAppName.getVisibility() != GONE && appWidth > mHeaderMinWidth) {
-                int newSize = appWidth - Math.min(appWidth - mHeaderMinWidth, overFlow);
+            if (overFlow > 0 && mAppName.getVisibility() != GONE && appWidth > mChildMinWidth) {
+                int newSize = appWidth - Math.min(appWidth - mChildMinWidth, overFlow);
                 int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
                 mAppName.measure(childWidthSpec, wrapContentHeightSpec);
                 overFlow -= appWidth - newSize;
             }
+            // still overflowing, finaly we shrink the subtext
             if (overFlow > 0 && mSubTextView.getVisibility() != GONE) {
                 // we're still too big
                 final int subTextWidth = mSubTextView.getMeasuredWidth();
@@ -124,12 +134,8 @@
                 int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
                 mSubTextView.measure(childWidthSpec, wrapContentHeightSpec);
             }
-            totalWidth = givenWidth;
         }
-        if (mProfileBadge.getVisibility() != View.GONE) {
-            totalWidth = givenWidth;
-        }
-        setMeasuredDimension(totalWidth, givenHeight);
+        setMeasuredDimension(givenWidth, givenHeight);
     }
 
     @Override
@@ -275,18 +281,16 @@
             mTouchRects.clear();
             addRectAroundViewView(mIcon);
             addRectAroundViewView(mExpandButton);
-            addInBetweenRect();
+            addWidthRect();
             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
         }
 
-        private void addInBetweenRect() {
-            final Rect r = new Rect();
+        private void addWidthRect() {
+            Rect r = new Rect();
             r.top = 0;
             r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
-            Rect leftRect = mTouchRects.get(0);
-            r.left = leftRect.right;
-            Rect rightRect = mTouchRects.get(1);
-            r.right = rightRect.left;
+            r.left = 0;
+            r.right = getWidth();
             mTouchRects.add(r);
         }
 
diff --git a/core/java/android/view/RemotableViewMethod.java b/core/java/android/view/RemotableViewMethod.java
index 4318290..e5cae84 100644
--- a/core/java/android/view/RemotableViewMethod.java
+++ b/core/java/android/view/RemotableViewMethod.java
@@ -29,6 +29,12 @@
 @Target({ ElementType.METHOD })
 @Retention(RetentionPolicy.RUNTIME)
 public @interface RemotableViewMethod {
+    /**
+     * @return Method name which can be called on a background thread. It should have the
+     * same arguments as the original method and should return a {@link Runnable} (or null)
+     * which will be called on the UI thread.
+     */
+    String asyncImpl() default "";
 }
 
 
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 2aace0f..7017ff5 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -136,6 +136,9 @@
     private RenderNode(String name, View owningView) {
         mNativeRenderNode = nCreate(name);
         mOwningView = owningView;
+        if (mOwningView instanceof SurfaceView) {
+            nRequestPositionUpdates(mNativeRenderNode, (SurfaceView) mOwningView);
+        }
     }
 
     /**
@@ -863,6 +866,8 @@
     private static native void nOutput(long renderNode);
     private static native int nGetDebugSize(long renderNode);
 
+    private static native void nRequestPositionUpdates(long renderNode, SurfaceView callback);
+
     ///////////////////////////////////////////////////////////////////////////
     // Animations
     ///////////////////////////////////////////////////////////////////////////
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 5b48e28..a296051 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -135,7 +135,7 @@
         }
     };
 
-    final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
+    private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
             = new ViewTreeObserver.OnScrollChangedListener() {
                     @Override
                     public void onScrollChanged() {
@@ -143,6 +143,17 @@
                     }
             };
 
+    private final ViewTreeObserver.OnPreDrawListener mDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    // reposition ourselves where the surface is
+                    mHaveFrame = getWidth() > 0 && getHeight() > 0;
+                    updateWindow(false, false);
+                    return true;
+                }
+            };
+
     boolean mRequestedVisible = false;
     boolean mWindowVisibility = false;
     boolean mViewVisibility = false;
@@ -168,17 +179,9 @@
     boolean mUpdateWindowNeeded;
     boolean mReportDrawNeeded;
     private Translator mTranslator;
+    private int mWindowInsetLeft;
+    private int mWindowInsetTop;
 
-    private final ViewTreeObserver.OnPreDrawListener mDrawListener =
-            new ViewTreeObserver.OnPreDrawListener() {
-                @Override
-                public boolean onPreDraw() {
-                    // reposition ourselves where the surface is
-                    mHaveFrame = getWidth() > 0 && getHeight() > 0;
-                    updateWindow(false, false);
-                    return true;
-                }
-            };
     private boolean mGlobalListenersAdded;
 
     public SurfaceView(Context context) {
@@ -443,17 +446,17 @@
         int myHeight = mRequestedHeight;
         if (myHeight <= 0) myHeight = getHeight();
 
-        getLocationInWindow(mLocation);
         final boolean creating = mWindow == null;
         final boolean formatChanged = mFormat != mRequestedFormat;
         final boolean sizeChanged = mWindowSpaceWidth != myWidth || mWindowSpaceHeight != myHeight;
         final boolean visibleChanged = mVisible != mRequestedVisible;
         final boolean layoutSizeChanged = getWidth() != mLayout.width
                 || getHeight() != mLayout.height;
-        final boolean positionChanged = mWindowSpaceLeft != mLocation[0] || mWindowSpaceTop != mLocation[1];
 
         if (force || creating || formatChanged || sizeChanged || visibleChanged
             || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
+            getLocationInWindow(mLocation);
+
             if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
                     + " format=" + formatChanged + " size=" + sizeChanged
                     + " visible=" + visibleChanged
@@ -643,27 +646,69 @@
                 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
                 " w=" + mLayout.width + " h=" + mLayout.height +
                 ", frame=" + mSurfaceFrame);
-        } else if (positionChanged || layoutSizeChanged) { // Only the position has changed
-            mWindowSpaceLeft = mLocation[0];
-            mWindowSpaceTop = mLocation[1];
-            // For our size changed check, we keep mLayout.width and mLayout.height
-            // in view local space.
-            mLocation[0] = mLayout.width = getWidth();
-            mLocation[1] = mLayout.height = getHeight();
+        } else if (!isHardwareAccelerated()) {
+            getLocationInWindow(mLocation);
+            final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
+                    || mWindowSpaceTop != mLocation[1];
+            if (positionChanged || layoutSizeChanged) { // Only the position has changed
+                mWindowSpaceLeft = mLocation[0];
+                mWindowSpaceTop = mLocation[1];
+                // For our size changed check, we keep mLayout.width and mLayout.height
+                // in view local space.
+                mLocation[0] = mLayout.width = getWidth();
+                mLocation[1] = mLayout.height = getHeight();
 
-            transformFromViewToWindowSpace(mLocation);
+                transformFromViewToWindowSpace(mLocation);
 
-            try {
-                mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
-                        mLocation[0], mLocation[1],
-                        viewRoot != null ? viewRoot.getNextFrameNumber() : -1,
-                        mWinFrame);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Exception from relayout", ex);
+                try {
+                    Log.d(TAG, String.format("updateWindowPosition UI, " +
+                            "postion = [%d, %d, %d, %d]", mWindowSpaceLeft, mWindowSpaceTop,
+                            mLocation[0], mLocation[1]));
+                    mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
+                            mLocation[0], mLocation[1], -1, mWinFrame);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Exception from relayout", ex);
+                }
             }
         }
     }
 
+    private Rect mRTLastReportedPosition = new Rect();
+
+    /**
+     * Called by native on RenderThread to update the window position
+     * @hide
+     */
+    public final void updateWindowPositionRT(long frameNumber,
+            int left, int top, int right, int bottom) {
+        IWindowSession session = mSession;
+        MyWindow window = mWindow;
+        if (session == null || window == null) {
+            // Guess we got detached, that sucks
+            return;
+        }
+        if (mRTLastReportedPosition.left == left
+                && mRTLastReportedPosition.top == top
+                && mRTLastReportedPosition.right == right
+                && mRTLastReportedPosition.bottom == bottom) {
+            return;
+        }
+        try {
+            if (DEBUG) {
+                Log.d(TAG, String.format("updateWindowPosition RT, frameNr = %d, " +
+                        "postion = [%d, %d, %d, %d]", frameNumber, left, top,
+                        right, bottom));
+            }
+            // Just using mRTLastReportedPosition as a dummy rect here
+            session.repositionChild(window, left, top, right, bottom, frameNumber,
+                    mRTLastReportedPosition);
+            // Now overwrite mRTLastReportedPosition with our values
+            mRTLastReportedPosition.set(left, top, right, bottom);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Exception from repositionChild", ex);
+        }
+    }
+
     private SurfaceHolder.Callback[] getSurfaceCallbacks() {
         SurfaceHolder.Callback callbacks[];
         synchronized (mCallbacks) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 98e3289..97e0143 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6815,20 +6815,6 @@
         }
     }
 
-    long getNextFrameNumber() {
-        long frameNumber = -1;
-        if (mSurfaceHolder != null) {
-            mSurfaceHolder.mSurfaceLock.lock();
-        }
-        if (mSurface.isValid()) {
-            frameNumber =  mSurface.getNextFrameNumber();
-        }
-        if (mSurfaceHolder != null) {
-            mSurfaceHolder.mSurfaceLock.unlock();
-        }
-        return frameNumber;
-    }
-
     class TakenSurfaceHolder extends BaseSurfaceHolder {
         @Override
         public boolean onAllowLockCanvas() {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 6e90baf..f601f7d 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -392,6 +392,26 @@
         return mDrawable;
     }
 
+    private class ImageDrawableCallback implements Runnable {
+
+        private final Drawable drawable;
+        private final Uri uri;
+        private final int resource;
+
+        ImageDrawableCallback(Drawable drawable, Uri uri, int resource) {
+            this.drawable = drawable;
+            this.uri = uri;
+            this.resource = resource;
+        }
+
+        @Override
+        public void run() {
+            setImageDrawable(drawable);
+            mUri = uri;
+            mResource = resource;
+        }
+    }
+
     /**
      * Sets a drawable as the content of this ImageView.
      *
@@ -405,7 +425,7 @@
      *
      * @attr ref android.R.styleable#ImageView_src
      */
-    @android.view.RemotableViewMethod
+    @android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
     public void setImageResource(@DrawableRes int resId) {
         // The resource configuration may have changed, so we should always
         // try to load the resource even if the resId hasn't changed.
@@ -424,6 +444,11 @@
         invalidate();
     }
 
+    /** @hide **/
+    public Runnable setImageResourceAsync(@DrawableRes int resId) {
+        return new ImageDrawableCallback(getContext().getDrawable(resId), null, resId);
+    }
+
     /**
      * Sets the content of this ImageView to the specified Uri.
      *
@@ -435,7 +460,7 @@
      *
      * @param uri the Uri of an image, or {@code null} to clear the content
      */
-    @android.view.RemotableViewMethod
+    @android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
     public void setImageURI(@Nullable Uri uri) {
         if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
             updateDrawable(null);
@@ -454,6 +479,19 @@
         }
     }
 
+    /** @hide **/
+    public Runnable setImageURIAsync(@Nullable Uri uri) {
+        if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
+            Drawable d = uri == null ? null : getDrawableFromUri(uri);
+            if (d == null) {
+                // Do not set the URI if the drawable couldn't be loaded.
+                uri = null;
+            }
+            return new ImageDrawableCallback(d, uri, 0);
+        }
+        return null;
+    }
+
     /**
      * Sets a drawable as the content of this ImageView.
      *
@@ -490,11 +528,16 @@
      * @param icon an Icon holding the desired image, or {@code null} to clear
      *             the content
      */
-    @android.view.RemotableViewMethod
+    @android.view.RemotableViewMethod(asyncImpl="setImageIconAsync")
     public void setImageIcon(@Nullable Icon icon) {
         setImageDrawable(icon == null ? null : icon.loadDrawable(mContext));
     }
 
+    /** @hide **/
+    public Runnable setImageIconAsync(@Nullable Icon icon) {
+        return new ImageDrawableCallback(icon == null ? null : icon.loadDrawable(mContext), null, 0);
+    }
+
     /**
      * Applies a tint to the image drawable. Does not modify the current tint
      * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
@@ -786,8 +829,7 @@
             return;
         }
 
-        final Resources res = getResources();
-        if (res == null) {
+        if (getResources() == null) {
             return;
         }
 
@@ -802,37 +844,7 @@
                 mUri = null;
             }
         } else if (mUri != null) {
-            final String scheme = mUri.getScheme();
-            if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
-                try {
-                    // Load drawable through Resources, to get the source density information
-                    ContentResolver.OpenResourceIdResult r =
-                            mContext.getContentResolver().getResourceId(mUri);
-                    d = r.r.getDrawable(r.id, mContext.getTheme());
-                } catch (Exception e) {
-                    Log.w(LOG_TAG, "Unable to open content: " + mUri, e);
-                }
-            } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
-                    || ContentResolver.SCHEME_FILE.equals(scheme)) {
-                InputStream stream = null;
-                try {
-                    stream = mContext.getContentResolver().openInputStream(mUri);
-                    d = Drawable.createFromResourceStream(
-                            mUseCorrectStreamDensity ? res : null, null, stream, null);
-                } catch (Exception e) {
-                    Log.w(LOG_TAG, "Unable to open content: " + mUri, e);
-                } finally {
-                    if (stream != null) {
-                        try {
-                            stream.close();
-                        } catch (IOException e) {
-                            Log.w(LOG_TAG, "Unable to close content: " + mUri, e);
-                        }
-                    }
-                }
-            } else {
-                d = Drawable.createFromPath(mUri.toString());
-            }
+            d = getDrawableFromUri(mUri);
 
             if (d == null) {
                 Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
@@ -846,6 +858,41 @@
         updateDrawable(d);
     }
 
+    private Drawable getDrawableFromUri(Uri uri) {
+        final String scheme = uri.getScheme();
+        if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+            try {
+                // Load drawable through Resources, to get the source density information
+                ContentResolver.OpenResourceIdResult r =
+                        mContext.getContentResolver().getResourceId(uri);
+                return r.r.getDrawable(r.id, mContext.getTheme());
+            } catch (Exception e) {
+                Log.w(LOG_TAG, "Unable to open content: " + uri, e);
+            }
+        } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
+                || ContentResolver.SCHEME_FILE.equals(scheme)) {
+            InputStream stream = null;
+            try {
+                stream = mContext.getContentResolver().openInputStream(uri);
+                return Drawable.createFromResourceStream(
+                        mUseCorrectStreamDensity ? getResources() : null, null, stream, null);
+            } catch (Exception e) {
+                Log.w(LOG_TAG, "Unable to open content: " + uri, e);
+            } finally {
+                if (stream != null) {
+                    try {
+                        stream.close();
+                    } catch (IOException e) {
+                        Log.w(LOG_TAG, "Unable to close content: " + uri, e);
+                    }
+                }
+            }
+        } else {
+            return Drawable.createFromPath(uri.toString());
+        }
+        return null;
+    }
+
     @Override
     public int[] onCreateDrawableState(int extraSpace) {
         if (mState == null) {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 584df08..8fa71a2 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -43,6 +43,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
 import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
 import android.view.ViewParent;
@@ -164,7 +165,20 @@
         com.android.internal.R.attr.state_above_anchor
     };
 
+    private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
+            new OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {}
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    mIsAnchorRootAttached = false;
+                }
+            };
+
     private WeakReference<View> mAnchor;
+    private WeakReference<View> mAnchorRoot;
+    private boolean mIsAnchorRootAttached;
 
     private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() {
         @Override
@@ -1037,7 +1051,7 @@
 
         TransitionManager.endTransitions(mDecorView);
 
-        unregisterForScrollChanged();
+        unregisterForViewTreeChanges();
 
         mIsShowing = true;
         mIsDropdown = false;
@@ -1120,7 +1134,7 @@
 
         TransitionManager.endTransitions(mDecorView);
 
-        registerForScrollChanged(anchor, xoff, yoff, gravity);
+        registerForViewTreeChanges(anchor, xoff, yoff, gravity);
 
         mIsShowing = true;
         mIsDropdown = true;
@@ -1633,14 +1647,23 @@
         mIsShowing = false;
         mIsTransitioningToDismiss = true;
 
+        // This method may be called as part of window detachment, in which
+        // case the anchor view (and its root) will still return true from
+        // isAttachedToWindow() during execution of this method; however, we
+        // can expect the OnAttachStateChangeListener to have been called prior
+        // to executing this method, so we can rely on that instead.
         final Transition exitTransition = mExitTransition;
-        if (exitTransition != null && decorView.isLaidOut()) {
+        if (!mIsAnchorRootAttached && exitTransition != null && decorView.isLaidOut()) {
             // The decor view is non-interactive during exit transitions.
             final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
             p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
             p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
             mWindowManager.updateViewLayout(decorView, p);
 
+            // Once we start dismissing the decor view, all state (including
+            // the anchor root) needs to be moved to the decor view since we
+            // may open another popup while it's busy exiting.
+            final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
             final Rect epicenter = getTransitionEpicenter();
             exitTransition.setEpicenterCallback(new EpicenterCallback() {
                 @Override
@@ -1648,18 +1671,19 @@
                     return epicenter;
                 }
             });
-            decorView.startExitTransition(exitTransition, new TransitionListenerAdapter() {
-                @Override
-                public void onTransitionEnd(Transition transition) {
-                    dismissImmediate(decorView, contentHolder, contentView);
-                }
-            });
+            decorView.startExitTransition(exitTransition, anchorRoot,
+                    new TransitionListenerAdapter() {
+                        @Override
+                        public void onTransitionEnd(Transition transition) {
+                            dismissImmediate(decorView, contentHolder, contentView);
+                        }
+                    });
         } else {
             dismissImmediate(decorView, contentHolder, contentView);
         }
 
         // Clears the anchor view.
-        unregisterForScrollChanged();
+        unregisterForViewTreeChanges();
 
         if (mOnDismissListener != null) {
             mOnDismissListener.onDismiss();
@@ -1925,7 +1949,7 @@
         final WeakReference<View> oldAnchor = mAnchor;
         final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
-            registerForScrollChanged(anchor, xoff, yoff, mAnchoredGravity);
+            registerForViewTreeChanges(anchor, xoff, yoff, mAnchoredGravity);
         } else if (needsUpdate) {
             // No need to register again if this is a DropDown, showAsDropDown already did.
             mAnchorXoff = xoff;
@@ -1969,27 +1993,38 @@
         public void onDismiss();
     }
 
-    private void unregisterForScrollChanged() {
-        final WeakReference<View> anchorRef = mAnchor;
-        final View anchor = anchorRef == null ? null : anchorRef.get();
+    private void unregisterForViewTreeChanges() {
+        final View anchor = mAnchor != null ? mAnchor.get() : null;
         if (anchor != null) {
             final ViewTreeObserver vto = anchor.getViewTreeObserver();
             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
         }
 
+        final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
+        if (anchorRoot != null) {
+            anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+        }
+
         mAnchor = null;
+        mAnchorRoot = null;
+        mIsAnchorRootAttached = false;
     }
 
-    private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
-        unregisterForScrollChanged();
-
-        mAnchor = new WeakReference<>(anchor);
+    private void registerForViewTreeChanges(View anchor, int xoff, int yoff, int gravity) {
+        unregisterForViewTreeChanges();
 
         final ViewTreeObserver vto = anchor.getViewTreeObserver();
         if (vto != null) {
             vto.addOnScrollChangedListener(mOnScrollChangedListener);
         }
 
+        final View anchorRoot = anchor.getRootView();
+        anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+
+        mAnchor = new WeakReference<>(anchor);
+        mAnchorRoot = new WeakReference<>(anchorRoot);
+        mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
+
         mAnchorXoff = xoff;
         mAnchorYoff = yoff;
         mAnchoredGravity = gravity;
@@ -2109,16 +2144,23 @@
          * its {@code onTransitionEnd} method called even if the transition
          * never starts; however, it may be called with a {@code null} argument.
          */
-        public void startExitTransition(Transition transition, final TransitionListener listener) {
+        public void startExitTransition(Transition transition, final View anchorRoot,
+                final TransitionListener listener) {
             if (transition == null) {
                 return;
             }
 
+            // The anchor view's window may go away while we're executing our
+            // transition, in which case we need to end the transition
+            // immediately and execute the listener to remove the popup.
+            anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+
             // The exit listener MUST be called for cleanup, even if the
             // transition never starts or ends. Stash it for later.
             mPendingExitListener = new TransitionListenerAdapter() {
                 @Override
                 public void onTransitionEnd(Transition transition) {
+                    anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
                     listener.onTransitionEnd(transition);
 
                     // The listener was called. Our job here is done.
@@ -2153,6 +2195,19 @@
                 mPendingExitListener.onTransitionEnd(null);
             }
         }
+
+        private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
+                new OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View v) {}
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) {
+                        v.removeOnAttachStateChangeListener(this);
+
+                        TransitionManager.endTransitions(PopupDecorView.this);
+                    }
+                };
     }
 
     private class PopupBackgroundView extends FrameLayout {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0dd803a2..dee25d3 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -39,8 +39,10 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.StrictMode;
@@ -66,6 +68,7 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.concurrent.Executor;
 
 /**
  * A class that describes a view hierarchy that can be displayed in
@@ -146,6 +149,8 @@
     private static final Object[] sMethodsLock = new Object[0];
     private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods =
             new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>();
+    private static final ArrayMap<Method, Method> sAsyncMethods = new ArrayMap<>();
+
     private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() {
         @Override
         protected Object[] initialValue() {
@@ -293,10 +298,40 @@
             return (getActionName() + viewId);
         }
 
+        /**
+         * This is called on the background thread. It should perform any non-ui computations
+         * and return the final action which will run on the UI thread.
+         * Override this if some of the tasks can be performed async.
+         */
+        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+            return this;
+        }
+
         int viewId;
     }
 
     /**
+     * Action class used during async inflation of RemoteViews. Subclasses are not parcelable.
+     */
+    private static abstract class RuntimeAction extends Action {
+        @Override
+        public final String getActionName() {
+            return "RuntimeAction";
+        }
+
+        @Override
+        public final void writeToParcel(Parcel dest, int flags) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    // Constant used during async execution. It is not parcelable.
+    private static final Action ACTION_NOOP = new RuntimeAction() {
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { }
+    };
+
+    /**
      * Merges the passed RemoteViews actions with this RemoteViews actions according to
      * action-specific merge rules.
      *
@@ -810,6 +845,36 @@
         return method;
     }
 
+    /**
+     * @return the async implementation of the provided method.
+     */
+    private Method getAsyncMethod(Method method) {
+        synchronized (sAsyncMethods) {
+            int valueIndex = sAsyncMethods.indexOfKey(method);
+            if (valueIndex >= 0) {
+                return sAsyncMethods.valueAt(valueIndex);
+            }
+
+            RemotableViewMethod annotation = method.getAnnotation(RemotableViewMethod.class);
+            Method asyncMethod = null;
+            if (!annotation.asyncImpl().isEmpty()) {
+                try {
+                    asyncMethod = method.getDeclaringClass()
+                            .getMethod(annotation.asyncImpl(), method.getParameterTypes());
+                    if (!asyncMethod.getReturnType().equals(Runnable.class)) {
+                        throw new ActionException("Async implementation for " + method.getName() +
+                            " does not return a Runnable");
+                    }
+                } catch (NoSuchMethodException ex) {
+                    throw new ActionException("Async implementation declared but not defined for " +
+                            method.getName());
+                }
+            }
+            sAsyncMethods.put(method, asyncMethod);
+            return asyncMethod;
+        }
+    }
+
     private static String getParameters(Class<?> paramType) {
         if (paramType == null) return "()";
         return "(" + paramType + ")";
@@ -1324,6 +1389,37 @@
             }
         }
 
+        @Override
+        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+            final View view = root.findViewById(viewId);
+            if (view == null) return ACTION_NOOP;
+
+            Class<?> param = getParameterType();
+            if (param == null) {
+                throw new ActionException("bad type: " + this.type);
+            }
+
+            try {
+                Method method = getMethod(view, this.methodName, param);
+                Method asyncMethod = getAsyncMethod(method);
+
+                if (asyncMethod != null) {
+                    Runnable endAction = (Runnable) asyncMethod.invoke(view, wrapArg(this.value));
+                    if (endAction == null) {
+                        return ACTION_NOOP;
+                    } else {
+                        return new RunnableAction(endAction);
+                    }
+                }
+            } catch (ActionException e) {
+                throw e;
+            } catch (Exception ex) {
+                throw new ActionException(ex);
+            }
+
+            return this;
+        }
+
         public int mergeBehavior() {
             // smoothScrollBy is cumulative, everything else overwites.
             if (methodName.equals("smoothScrollBy")) {
@@ -1340,6 +1436,22 @@
         }
     }
 
+    /**
+     * This is only used for async execution of actions and it not parcelable.
+     */
+    private static final class RunnableAction extends RuntimeAction {
+        private final Runnable mRunnable;
+
+        RunnableAction(Runnable r) {
+            mRunnable = r;
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+            mRunnable.run();
+        }
+    }
+
     private void configureRemoteViewsAsChild(RemoteViews rv) {
         mBitmapCache.assimilate(rv.mBitmapCache);
         rv.setBitmapCache(mBitmapCache);
@@ -1401,6 +1513,43 @@
         }
 
         @Override
+        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+            // In the async implementation, update the view tree so that subsequent calls to
+            // findViewById return the currect view.
+            root.createTree();
+            ViewTree target = root.findViewTreeById(viewId);
+            if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
+                return ACTION_NOOP;
+            }
+            if (nestedViews == null) {
+                // Clear all children when nested views omitted
+                target.mChildren = null;
+                return this;
+            } else {
+                // Inflate nested views and perform all the async tasks for the child remoteView.
+                final Context context = root.mRoot.getContext();
+                final AsyncApplyTask task = nestedViews.getAsyncApplyTask(
+                        context, (ViewGroup) target.mRoot, null, handler);
+                final ViewTree tree = task.doInBackground();
+
+                // Update the global view tree, so that next call to findViewTreeById
+                // goes through the subtree as well.
+                target.addChild(tree);
+
+                return new RuntimeAction() {
+
+                    @Override
+                    public void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException {
+                        // This view will exist as we have already made sure
+                        final ViewGroup target = (ViewGroup) root.findViewById(viewId);
+                        task.onPostExecute(tree);
+                        target.addView(task.mResult);
+                    }
+                };
+            }
+        }
+
+        @Override
         public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
             if (nestedViews != null) {
                 counter.increment(nestedViews.estimateMemoryUsage());
@@ -1520,7 +1669,13 @@
         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
             final TextView target = (TextView) root.findViewById(viewId);
             if (target == null) return;
-            if (useIcons) {
+            if (drawablesLoaded) {
+                if (isRelative) {
+                    target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
+                } else {
+                    target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
+                }
+            } else if (useIcons) {
                 final Context ctx = target.getContext();
                 final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx);
                 final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx);
@@ -1540,6 +1695,33 @@
             }
         }
 
+        @Override
+        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+            final TextView target = (TextView) root.findViewById(viewId);
+            if (target == null) return ACTION_NOOP;
+
+            TextViewDrawableAction copy = useIcons ?
+                    new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) :
+                    new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4);
+
+            // Load the drawables on the background thread.
+            copy.drawablesLoaded = true;
+            final Context ctx = target.getContext();
+
+            if (useIcons) {
+                copy.id1 = i1 == null ? null : i1.loadDrawable(ctx);
+                copy.id2 = i2 == null ? null : i2.loadDrawable(ctx);
+                copy.id3 = i3 == null ? null : i3.loadDrawable(ctx);
+                copy.id4 = i4 == null ? null : i4.loadDrawable(ctx);
+            } else {
+                copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1);
+                copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2);
+                copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3);
+                copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4);
+            }
+            return copy;
+        }
+
         public String getActionName() {
             return "TextViewDrawableAction";
         }
@@ -1549,6 +1731,9 @@
         int d1, d2, d3, d4;
         Icon i1, i2, i3, i4;
 
+        boolean drawablesLoaded = false;
+        Drawable id1, id2, id3, id4;
+
         public final static int TAG = 11;
     }
 
@@ -2852,7 +3037,15 @@
     public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
         RemoteViews rvToApply = getRemoteViewsToApply(context);
 
-        View result;
+        View result = inflateView(context, rvToApply, parent);
+        loadTransitionOverride(context, handler);
+
+        rvToApply.performApply(result, parent, handler);
+
+        return result;
+    }
+
+    private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
         // RemoteViews may be built by an application installed in another
         // user. So build a context that loads resources from that user but
         // still returns the current users userId so settings like data / time formats
@@ -2880,13 +3073,7 @@
         // we don't add a filter to the static version returned by getSystemService.
         inflater = inflater.cloneInContext(inflationContext);
         inflater.setFilter(this);
-        result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
-
-        loadTransitionOverride(context, handler);
-
-        rvToApply.performApply(result, parent, handler);
-
-        return result;
+        return inflater.inflate(rv.getLayoutId(), parent, false);
     }
 
     private static void loadTransitionOverride(Context context,
@@ -2908,6 +3095,143 @@
     }
 
     /**
+     * Implement this interface to receive a callback when
+     * {@link #applyAsync} or {@link #reapplyAsync} is finished.
+     * @hide
+     */
+    public interface OnViewAppliedListener {
+        void onViewApplied(View v);
+
+        void onError(Exception e);
+    }
+
+    /**
+     * Applies the views asynchronously, moving as much of the task on the background
+     * thread as possible.
+     *
+     * @see {@link #apply(Context, ViewGroup)}
+     * @param context Default context to use
+     * @param parent Parent that the resulting view hierarchy will be attached to. This method
+     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
+     * @param listener the callback to run when all actions have been applied. May be null.
+     * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used.
+     * @return CancellationSignal
+     * @hide
+     */
+    public CancellationSignal applyAsync(
+            Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) {
+        return applyAsync(context, parent, executor, listener, null);
+    }
+
+    private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) {
+        CancellationSignal cancelSignal = new CancellationSignal();
+        cancelSignal.setOnCancelListener(task);
+
+        task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
+        return cancelSignal;
+    }
+
+    /** @hide */
+    public CancellationSignal applyAsync(Context context, ViewGroup parent,
+            Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
+        return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor);
+    }
+
+    private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
+            OnViewAppliedListener listener, OnClickHandler handler) {
+        return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
+                handler, null);
+    }
+
+    private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
+            implements CancellationSignal.OnCancelListener {
+        final RemoteViews mRV;
+        final ViewGroup mParent;
+        final Context mContext;
+        final OnViewAppliedListener mListener;
+        final OnClickHandler mHandler;
+
+        private View mResult;
+        private ViewTree mTree;
+        private Action[] mActions;
+        private Exception mError;
+
+        private AsyncApplyTask(
+                RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener,
+                OnClickHandler handler, View result) {
+            mRV = rv;
+            mParent = parent;
+            mContext = context;
+            mListener = listener;
+            mHandler = handler;
+
+            mResult = result;
+            loadTransitionOverride(context, handler);
+        }
+
+        @Override
+        protected ViewTree doInBackground(Void... params) {
+            try {
+                if (mResult == null) {
+                    mResult = inflateView(mContext, mRV, mParent);
+                }
+
+                mTree = new ViewTree(mResult);
+                if (mRV.mActions != null) {
+                    int count = mRV.mActions.size();
+                    mActions = new Action[count];
+                    for (int i = 0; i < count && !isCancelled(); i++) {
+                        // TODO: check if isCanclled in nested views.
+                        mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler);
+                    }
+                } else {
+                    mActions = null;
+                }
+                return mTree;
+            } catch (Exception e) {
+                mError = e;
+                return null;
+            }
+        }
+
+        @Override
+        protected void onPostExecute(ViewTree viewTree) {
+            if (mError == null) {
+                try {
+                    if (mActions != null) {
+                        OnClickHandler handler = mHandler == null
+                                ? DEFAULT_ON_CLICK_HANDLER : mHandler;
+                        for (Action a : mActions) {
+                            a.apply(viewTree.mRoot, mParent, handler);
+                        }
+                    }
+                } catch (Exception e) {
+                    mError = e;
+                }
+            }
+
+            if (mListener != null) {
+                if (mError != null) {
+                    mListener.onError(mError);
+                } else {
+                    mListener.onViewApplied(viewTree.mRoot);
+                }
+            } else if (mError != null) {
+                if (mError instanceof ActionException) {
+                    throw (ActionException) mError;
+                } else {
+                    throw new ActionException(mError);
+                }
+            }
+        }
+
+        @Override
+        public void onCancel() {
+            cancel(true);
+        }
+    }
+
+    /**
      * Applies all of the actions to the provided view.
      *
      * <p><strong>Caller beware: this may throw</strong>
@@ -2936,6 +3260,43 @@
         rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
     }
 
+    /**
+     * Applies all the actions to the provided view, moving as much of the task on the background
+     * thread as possible.
+     *
+     * @see {@link #reapply(Context, View)}
+     * @param context Default context to use
+     * @param v The view to apply the actions to.  This should be the result of
+     * the {@link #apply(Context,ViewGroup)} call.
+     * @param listener the callback to run when all actions have been applied. May be null.
+     * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used
+     * @return CancellationSignal
+     * @hide
+     */
+    public CancellationSignal reapplyAsync(
+            Context context, View v, Executor executor, OnViewAppliedListener listener) {
+        return reapplyAsync(context, v, executor, listener, null);
+    }
+
+    /** @hide */
+    public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
+            OnViewAppliedListener listener, OnClickHandler handler) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context);
+
+        // In the case that a view has this RemoteViews applied in one orientation, is persisted
+        // across orientation change, and has the RemoteViews re-applied in the new orientation,
+        // we throw an exception, since the layouts may be completely unrelated.
+        if (hasLandscapeAndPortraitLayouts()) {
+            if (v.getId() != rvToApply.getLayoutId()) {
+                throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
+                        " that does not share the same root layout id.");
+            }
+        }
+
+        return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
+                context, listener, handler, v), executor);
+    }
+
     private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
         if (mActions != null) {
             handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
@@ -3058,4 +3419,90 @@
             return new RemoteViews[size];
         }
     };
+
+    /**
+     * A representation of the view hierarchy. Only views which have a valid ID are added
+     * and can be searched.
+     */
+    private static class ViewTree {
+        private final View mRoot;
+
+        private ArrayList<ViewTree> mChildren;
+
+        private ViewTree(View root) {
+            mRoot = root;
+        }
+
+        public void createTree() {
+            if (mChildren != null) {
+                return;
+            }
+
+            mChildren = new ArrayList<>();
+            if (mRoot instanceof ViewGroup && mRoot.isRootNamespace()) {
+                ViewGroup vg = (ViewGroup) mRoot;
+                int count = vg.getChildCount();
+                for (int i = 0; i < count; i++) {
+                    addViewChild(vg.getChildAt(i));
+                }
+            }
+        }
+
+        public ViewTree findViewTreeById(int id) {
+            if (mRoot.getId() == id) {
+                return this;
+            }
+            if (mChildren == null) {
+                return null;
+            }
+            for (ViewTree tree : mChildren) {
+                ViewTree result = tree.findViewTreeById(id);
+                if (result != null) {
+                    return result;
+                }
+            }
+            return null;
+        }
+
+        public View findViewById(int id) {
+            if (mChildren == null) {
+                return mRoot.findViewById(id);
+            }
+            ViewTree tree = findViewTreeById(id);
+            return tree == null ? null : tree.mRoot;
+        }
+
+        public void addChild(ViewTree child) {
+            if (mChildren == null) {
+                mChildren = new ArrayList<>();
+            }
+            child.createTree();
+            mChildren.add(child);
+        }
+
+        private void addViewChild(View v) {
+            final ViewTree target;
+
+            // If the view has a valid id, i.e., if can be found using findViewById, add it to the
+            // tree, otherwise skip this view and add its children instead.
+            if (v.getId() != 0) {
+                ViewTree tree = new ViewTree(v);
+                mChildren.add(tree);
+                target = tree;
+            } else {
+                target = this;
+            }
+
+            if (v instanceof ViewGroup && v.isRootNamespace()) {
+                if (target.mChildren == null) {
+                    target.mChildren = new ArrayList<>();
+                    ViewGroup vg = (ViewGroup) v;
+                    int count = vg.getChildCount();
+                    for (int i = 0; i < count; i++) {
+                        target.addViewChild(vg.getChildAt(i));
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c626af6..712a04b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -115,7 +115,6 @@
 import android.view.DragEvent;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
-import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -395,6 +394,14 @@
         }
 
         /**
+         * @return {@code true} if this object contains metadata that needs to
+         *         be retained, {@code false} otherwise
+         */
+        public boolean hasMetadata() {
+            return mDrawablePadding != 0 || mHasTintMode || mHasTint;
+        }
+
+        /**
          * Updates the list of displayed drawables to account for the current
          * layout direction.
          *
@@ -2174,7 +2181,7 @@
         if (!drawables) {
             // Clearing drawables...  can we free the data structure?
             if (dr != null) {
-                if (dr.mDrawablePadding == 0) {
+                if (!dr.hasMetadata()) {
                     mDrawables = null;
                 } else {
                     // We need to retain the last set padding, so just clear
@@ -2377,7 +2384,7 @@
         if (!drawables) {
             // Clearing drawables...  can we free the data structure?
             if (dr != null) {
-                if (dr.mDrawablePadding == 0) {
+                if (!dr.hasMetadata()) {
                     mDrawables = null;
                 } else {
                     // We need to retain the last set padding, so just clear
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index dbec740..015e60d 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -85,8 +85,10 @@
         int callingUserId = getUserId();
 
         if (canForward(newIntent, targetUserId)) {
-            if (newIntent.getAction().equals(Intent.ACTION_CHOOSER)) {
+            if (Intent.ACTION_CHOOSER.equals(newIntent.getAction())) {
                 Intent innerIntent = (Intent) newIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+                // At this point, innerIntent is not null. Otherwise, canForward would have returned
+                // false.
                 innerIntent.prepareToLeaveUser(callingUserId);
             } else {
                 newIntent.prepareToLeaveUser(callingUserId);
@@ -124,7 +126,7 @@
                 Toast.makeText(this, getString(userMessageId), Toast.LENGTH_LONG).show();
             }
         } else {
-            Slog.wtf(TAG, "the intent: " + newIntent + "cannot be forwarded from user "
+            Slog.wtf(TAG, "the intent: " + newIntent + " cannot be forwarded from user "
                     + callingUserId + " to user " + targetUserId);
         }
         finish();
@@ -132,7 +134,7 @@
 
     boolean canForward(Intent intent, int targetUserId)  {
         IPackageManager ipm = AppGlobals.getPackageManager();
-        if (intent.getAction().equals(Intent.ACTION_CHOOSER)) {
+        if (Intent.ACTION_CHOOSER.equals(intent.getAction())) {
             // The EXTRA_INITIAL_INTENTS may not be allowed to be forwarded.
             if (intent.hasExtra(Intent.EXTRA_INITIAL_INTENTS)) {
                 Slog.wtf(TAG, "An chooser intent with extra initial intents cannot be forwarded to"
@@ -145,6 +147,11 @@
                 return false;
             }
             intent = (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
+            if (intent == null) {
+                Slog.wtf(TAG, "Cannot forward a chooser intent with no extra "
+                        + Intent.EXTRA_INTENT);
+                return false;
+            }
         }
         String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
         if (intent.getSelector() != null) {
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 9a178837..956ee8c 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -138,22 +138,12 @@
         super.onCreate(savedInstanceState);
         setHasOptionsMenu(true);
 
-        Locale sortingLocale;
-        if (mCountryMode) {
-            if (mParentLocale == null) {
-                sortingLocale = Locale.getDefault();
-                this.getActivity().setTitle(R.string.country_selection_title);
-            } else {
-                sortingLocale = mParentLocale.getLocale();
-                this.getActivity().setTitle(mParentLocale.getFullNameNative());
-            }
-        } else {
-            sortingLocale = Locale.getDefault();
-            this.getActivity().setTitle(R.string.language_selection_title);
-        }
+        final Locale sortingLocale = (mCountryMode && mParentLocale != null)
+                ? mParentLocale.getLocale()
+                : Locale.getDefault();
 
         mAdapter = new SuggestedLocaleAdapter(mLocaleList, mCountryMode);
-        LocaleHelper.LocaleInfoComparator comp =
+        final LocaleHelper.LocaleInfoComparator comp =
                 new LocaleHelper.LocaleInfoComparator(sortingLocale);
         mAdapter.sort(comp);
         setListAdapter(mAdapter);
@@ -173,6 +163,17 @@
     @Override
     public void onResume() {
         super.onResume();
+
+        if (mCountryMode) {
+            if (mParentLocale == null) {
+                this.getActivity().setTitle(R.string.country_selection_title);
+            } else {
+                this.getActivity().setTitle(mParentLocale.getFullNameNative());
+            }
+        } else {
+            this.getActivity().setTitle(R.string.language_selection_title);
+        }
+
         getListView().requestFocus();
     }
 
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 02efcc6..ce03bb8 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -322,7 +322,7 @@
         /* View components */
         private final ViewGroup mContentContainer;  // holds all contents.
         private final ViewGroup mMainPanel;  // holds menu items that are initially displayed.
-        private final ListView mOverflowPanel;  // holds menu items hidden in the overflow.
+        private final OverflowPanel mOverflowPanel;  // holds menu items hidden in the overflow.
         private final ImageButton mOverflowButton;  // opens/closes the overflow.
         /* overflow button drawables. */
         private final Drawable mArrow;
@@ -895,6 +895,7 @@
 
         private void setPanelsStatesAtRestingPosition() {
             mOverflowButton.setEnabled(true);
+            mOverflowPanel.awakenScrollBars();
 
             if (mIsOverflowOpen) {
                 // Set open state.
@@ -1333,27 +1334,8 @@
             return overflowButton;
         }
 
-        private ListView createOverflowPanel() {
-            final ListView overflowPanel = new ListView(FloatingToolbarPopup.this.mContext) {
-                @Override
-                protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-                    // Update heightMeasureSpec to make sure that this view is not clipped
-                    // as we offset it's coordinates with respect to it's parent.
-                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                            mOverflowPanelSize.getHeight() - mOverflowButtonSize.getHeight(),
-                            MeasureSpec.EXACTLY);
-                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-                }
-
-                @Override
-                public boolean dispatchTouchEvent(MotionEvent ev) {
-                    if (isOverflowAnimating()) {
-                        // Eat the touch event.
-                        return true;
-                    }
-                    return super.dispatchTouchEvent(ev);
-                }
-            };
+        private OverflowPanel createOverflowPanel() {
+            final OverflowPanel overflowPanel = new OverflowPanel(this);
             overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
             overflowPanel.setDivider(null);
@@ -1464,6 +1446,43 @@
         }
 
         /**
+         * A custom ListView for the overflow panel.
+         */
+        private static final class OverflowPanel extends ListView {
+
+            private final FloatingToolbarPopup mPopup;
+
+            OverflowPanel(FloatingToolbarPopup popup) {
+                super(Preconditions.checkNotNull(popup).mContext);
+                this.mPopup = popup;
+            }
+
+            @Override
+            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+                // Update heightMeasureSpec to make sure that this view is not clipped
+                // as we offset it's coordinates with respect to it's parent.
+                int height = mPopup.mOverflowPanelSize.getHeight()
+                        - mPopup.mOverflowButtonSize.getHeight();
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
+
+            @Override
+            public boolean dispatchTouchEvent(MotionEvent ev) {
+                if (mPopup.isOverflowAnimating()) {
+                    // Eat the touch event.
+                    return true;
+                }
+                return super.dispatchTouchEvent(ev);
+            }
+
+            @Override
+            protected boolean awakenScrollBars() {
+                return super.awakenScrollBars();
+            }
+        }
+
+        /**
          * A custom interpolator used for various floating toolbar animations.
          */
         private static final class LogAccelerateInterpolator implements Interpolator {
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index b1d4e26..a9003c1 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -15,6 +15,7 @@
  */
 
 #define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <EGL/egl.h>
 
@@ -24,7 +25,10 @@
 #include <android_runtime/AndroidRuntime.h>
 
 #include <Animator.h>
+#include <DamageAccumulator.h>
+#include <Matrix.h>
 #include <RenderNode.h>
+#include <TreeInfo.h>
 #include <Paint.h>
 
 #include "core_jni_helpers.h"
@@ -462,6 +466,69 @@
 }
 
 // ----------------------------------------------------------------------------
+// SurfaceView position callback
+// ----------------------------------------------------------------------------
+
+jmethodID gSurfaceViewPositionUpdateMethod;
+
+static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
+        jlong renderNodePtr, jobject surfaceview) {
+    class SurfaceViewPositionUpdater : public RenderNode::PositionListener {
+    public:
+        SurfaceViewPositionUpdater(JNIEnv* env, jobject surfaceview) {
+            env->GetJavaVM(&mVm);
+            mWeakRef = env->NewWeakGlobalRef(surfaceview);
+        }
+
+        virtual ~SurfaceViewPositionUpdater() {
+            jnienv()->DeleteWeakGlobalRef(mWeakRef);
+            mWeakRef = nullptr;
+        }
+
+        virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) override {
+            if (CC_UNLIKELY(!mWeakRef || !info.updateWindowPositions)) return;
+            ATRACE_NAME("Update SurfaceView position");
+
+            JNIEnv* env = jnienv();
+            jobject localref = env->NewLocalRef(mWeakRef);
+            if (CC_UNLIKELY(!localref)) {
+                jnienv()->DeleteWeakGlobalRef(mWeakRef);
+                mWeakRef = nullptr;
+                return;
+            }
+            Matrix4 transform;
+            info.damageAccumulator->computeCurrentTransform(&transform);
+            const RenderProperties& props = node.properties();
+            uirenderer::Rect bounds(props.getWidth(), props.getHeight());
+            transform.mapRect(bounds);
+            bounds.left -= info.windowInsetLeft;
+            bounds.right -= info.windowInsetLeft;
+            bounds.top -= info.windowInsetTop;
+            bounds.bottom -= info.windowInsetTop;
+            env->CallVoidMethod(localref, gSurfaceViewPositionUpdateMethod,
+                    (jlong) info.frameNumber, (jint) bounds.left, (jint) bounds.top,
+                    (jint) bounds.right, (jint) bounds.bottom);
+            env->DeleteLocalRef(localref);
+        }
+
+    private:
+        JNIEnv* jnienv() {
+            JNIEnv* env;
+            if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+                LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm);
+            }
+            return env;
+        }
+
+        JavaVM* mVm;
+        jobject mWeakRef;
+    };
+
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    renderNode->setPositionListener(new SurfaceViewPositionUpdater(env, surfaceview));
+}
+
+// ----------------------------------------------------------------------------
 // JNI Glue
 // ----------------------------------------------------------------------------
 
@@ -539,9 +606,14 @@
 
     { "nAddAnimator",              "(JJ)V", (void*) android_view_RenderNode_addAnimator },
     { "nEndAllAnimators",          "(J)V", (void*) android_view_RenderNode_endAllAnimators },
+
+    { "nRequestPositionUpdates",   "(JLandroid/view/SurfaceView;)V", (void*) android_view_RenderNode_requestPositionUpdates },
 };
 
 int register_android_view_RenderNode(JNIEnv* env) {
+    jclass clazz = FindClassOrDie(env, "android/view/SurfaceView");
+    gSurfaceViewPositionUpdateMethod = GetMethodIDOrDie(env, clazz,
+            "updateWindowPositionRT", "(JIIII)V");
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
 }
 
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 8c907dd..acd0501 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -134,7 +134,14 @@
 
     virtual void prepareTree(TreeInfo& info) {
         info.errorHandler = this;
+        // TODO: This is hacky
+        info.windowInsetLeft = -stagingProperties().getLeft();
+        info.windowInsetTop = -stagingProperties().getTop();
+        info.updateWindowPositions = true;
         RenderNode::prepareTree(info);
+        info.updateWindowPositions = false;
+        info.windowInsetLeft = 0;
+        info.windowInsetTop = 0;
         info.errorHandler = NULL;
     }
 
@@ -369,28 +376,28 @@
 static void android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jobject jsurface) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    sp<ANativeWindow> window = android_view_Surface_getNativeWindow(env, jsurface);
-    proxy->initialize(window);
+    sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
+    proxy->initialize(surface);
 }
 
 static void android_view_ThreadedRenderer_updateSurface(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jobject jsurface) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    sp<ANativeWindow> window;
+    sp<Surface> surface;
     if (jsurface) {
-        window = android_view_Surface_getNativeWindow(env, jsurface);
+        surface = android_view_Surface_getSurface(env, jsurface);
     }
-    proxy->updateSurface(window);
+    proxy->updateSurface(surface);
 }
 
 static jboolean android_view_ThreadedRenderer_pauseSurface(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jobject jsurface) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    sp<ANativeWindow> window;
+    sp<Surface> surface;
     if (jsurface) {
-        window = android_view_Surface_getNativeWindow(env, jsurface);
+        surface = android_view_Surface_getSurface(env, jsurface);
     }
-    return proxy->pauseSurface(window);
+    return proxy->pauseSurface(surface);
 }
 
 static void android_view_ThreadedRenderer_setup(JNIEnv* env, jobject clazz, jlong proxyPtr,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7729d48c..cc48902 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -292,9 +292,12 @@
     <protected-broadcast android:name="android.intent.action.REMOTE_BUGREPORT_FINISHED" />
     <protected-broadcast android:name="android.intent.action.REMOTE_BUGREPORT_DISPATCH" />
 
+    <!-- Legacy -->
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
 
+    <protected-broadcast android:name="com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE" />
+
     <protected-broadcast android:name="android.intent.action.HDMI_PLUGGED" />
 
     <protected-broadcast android:name="android.intent.action.PHONE_STATE" />
@@ -2874,6 +2877,12 @@
     <permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows trusted applications to dispatch managed provisioning message to Managed
+         Provisioning app. If requesting app does not have permission, it will be ignored.
+         @hide -->
+    <permission android:name="android.permission.DISPATCH_PROVISIONING_MESSAGE"
+                android:protectionLevel="signature|privileged" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index b45f8bb..6669bae 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -87,7 +87,6 @@
         android:layout_marginStart="2dp"
         android:layout_marginEnd="2dp"
         android:visibility="gone"
-        android:maxWidth="72dp"
         android:singleLine="true"/>
     <TextView
         android:id="@+id/time_divider"
diff --git a/core/res/res/menu/language_selection_list.xml b/core/res/res/menu/language_selection_list.xml
index 63b9627..377c4fa 100644
--- a/core/res/res/menu/language_selection_list.xml
+++ b/core/res/res/menu/language_selection_list.xml
@@ -14,12 +14,12 @@
      limitations under the License.
 -->
 
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
-      xmlns:app="http://schemas.android.com/apk/res-auto">
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
     <item
         android:id="@+id/locale_search_menu"
         android:title="@string/locale_search_menu"
-        app:showAsAction="always"
-        app:actionViewClass="android.widget.SearchView" />
+        android:icon="@*android:drawable/ic_search_api_material"
+        android:showAsAction="always|collapseActionView"
+        android:actionViewClass="android.widget.SearchView" />
 
 </menu>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 329e2e5..5c5aff0 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2697,6 +2697,9 @@
     <public type="attr" name="endY" />
     <public type="attr" name="offset" />
     <public type="attr" name="abiOverride" />
+    <public type="attr" name="bitmap" />
+    <public type="attr" name="hotSpotX" />
+    <public type="attr" name="hotSpotY" />
 
     <public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
     <public type="style" name="Widget.Material.SeekBar.Discrete" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e5a6226..4e10d39 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4176,7 +4176,7 @@
     <string name="language_selection_title">Language preference</string>
     <!-- Title for the region selection screen [CHAR LIMIT=25] -->
     <string name="country_selection_title">Region preference</string>
-    <!-- Hint text in a search edit box (used to filter long language / country lists) [CHAR LIMIT=20] -->
+    <!-- Hint text in a search edit box (used to filter long language / country lists) [CHAR LIMIT=25] -->
     <string name="search_language_hint">Type language name</string>
 
     <!-- List section subheader for the language picker, containing a list of suggested languages determined by the default region [CHAR LIMIT=30] -->
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 6988b02..c232bd1 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -238,6 +238,7 @@
 
 LOCAL_SRC_FILES += \
     $(hwui_test_common_src_files) \
+    tests/unit/BufferPoolTests.cpp \
     tests/unit/CanvasStateTests.cpp \
     tests/unit/ClipAreaTests.cpp \
     tests/unit/CrashHandlerInjector.cpp \
@@ -247,11 +248,11 @@
     tests/unit/GpuMemoryTrackerTests.cpp \
     tests/unit/LayerUpdateQueueTests.cpp \
     tests/unit/LinearAllocatorTests.cpp \
-    tests/unit/VectorDrawableTests.cpp \
     tests/unit/OffscreenBufferPoolTests.cpp \
+    tests/unit/SkiaBehaviorTests.cpp \
     tests/unit/StringUtilsTests.cpp \
-    tests/unit/BufferPoolTests.cpp \
-    tests/unit/TextDropShadowCacheTests.cpp
+    tests/unit/TextDropShadowCacheTests.cpp \
+    tests/unit/VectorDrawableTests.cpp
 
 ifeq (true, $(HWUI_NEW_OPS))
     LOCAL_SRC_FILES += \
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 2184755..e3a5f3e 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -663,7 +663,7 @@
 }
 
 void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
-    TessellationCache::vertexBuffer_pair_t buffers = *(op.shadowTask->getResult());
+    TessellationCache::vertexBuffer_pair_t buffers = op.shadowTask->getResult();
     renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
 }
 
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index 160090d..9c08b4d 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -213,6 +213,7 @@
 
 void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
         SkRegion::Op op) {
+    if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
     onClipUpdated();
     switch (mMode) {
     case ClipMode::Rectangle:
@@ -228,6 +229,7 @@
 }
 
 void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
+    if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
     onClipUpdated();
     enterRegionMode();
     mClipRegion.op(region, op);
@@ -236,6 +238,7 @@
 
 void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
         SkRegion::Op op) {
+    if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
     onClipUpdated();
     SkMatrix skTransform;
     transform->copyTo(skTransform);
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index e44fc20..250296e 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -57,7 +57,7 @@
     // Returns the current dirty area, *NOT* transformed by pushed transforms
     void peekAtDirty(SkRect* dest) const;
 
-    void computeCurrentTransform(Matrix4* outMatrix) const;
+    ANDROID_API void computeCurrentTransform(Matrix4* outMatrix) const;
 
     void finish(SkRect* totalDirty);
 
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index 57e5b9d..185acce 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -203,7 +203,9 @@
         mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
     }
 
-    if (!mCanvasState.quickRejectConservative(0, 0, width, height)) {
+    bool quickRejected = properties.getClipToBounds()
+            && mCanvasState.quickRejectConservative(0, 0, width, height);
+    if (!quickRejected) {
         // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
         if (node.getLayer()) {
             // HW layer
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index d4588ed..bade216 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -381,6 +381,10 @@
     bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence(
             willHaveFunctor, functorsNeedLayer);
 
+    if (CC_UNLIKELY(mPositionListener.get())) {
+        mPositionListener->onPositionUpdated(*this, info);
+    }
+
     prepareLayer(info, animatorDirtyMask);
     if (info.mode == TreeInfo::MODE_FULL) {
         pushStagingDisplayListChanges(info);
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 8e4a3df..f248de54 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -209,6 +209,19 @@
     OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh...
 #endif
 
+    class ANDROID_API PositionListener {
+    public:
+        virtual ~PositionListener() {}
+        virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) = 0;
+    };
+
+    // Note this is not thread safe, this needs to be called
+    // before the RenderNode is used for drawing.
+    // RenderNode takes ownership of the pointer
+    ANDROID_API void setPositionListener(PositionListener* listener) {
+        mPositionListener.reset(listener);
+    }
+
 private:
     typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
 
@@ -317,6 +330,8 @@
     // This is *NOT* thread-safe, and should therefore only be tracking
     // mDisplayList, not mStagingDisplayList.
     uint32_t mParentCount;
+
+    std::unique_ptr<PositionListener> mPositionListener;
 }; // class RenderNode
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index 461e819..fd9fb852 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -242,23 +242,21 @@
             spotBuffer);
 }
 
-class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t*> {
+class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t> {
 public:
     ShadowProcessor(Caches& caches)
-            : TaskProcessor<TessellationCache::vertexBuffer_pair_t*>(&caches.tasks) {}
+            : TaskProcessor<TessellationCache::vertexBuffer_pair_t>(&caches.tasks) {}
     ~ShadowProcessor() {}
 
-    virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t*> >& task) override {
+    virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t> >& task) override {
         TessellationCache::ShadowTask* t = static_cast<TessellationCache::ShadowTask*>(task.get());
         ATRACE_NAME("shadow tessellation");
 
-        VertexBuffer* ambientBuffer = new VertexBuffer;
-        VertexBuffer* spotBuffer = new VertexBuffer;
         tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter,
                 &t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius,
-                *ambientBuffer, *spotBuffer);
+                t->ambientBuffer, t->spotBuffer);
 
-        t->setResult(new TessellationCache::vertexBuffer_pair_t(ambientBuffer, spotBuffer));
+        t->setResult(TessellationCache::vertexBuffer_pair_t(&t->ambientBuffer, &t->spotBuffer));
     }
 };
 
@@ -373,7 +371,7 @@
         task = static_cast<ShadowTask*>(mShadowCache.get(key));
     }
     LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached");
-    outBuffers = *(task->getResult());
+    outBuffers = task->getResult();
 }
 
 sp<TessellationCache::ShadowTask> TessellationCache::getShadowTask(
@@ -392,13 +390,6 @@
     return task;
 }
 
-TessellationCache::ShadowTask::~ShadowTask() {
-    TessellationCache::vertexBuffer_pair_t* bufferPair = getResult();
-    delete bufferPair->getFirst();
-    delete bufferPair->getSecond();
-    delete bufferPair;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // Tessellation precaching
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h
index 977c2d9e..6dcc8120 100644
--- a/libs/hwui/TessellationCache.h
+++ b/libs/hwui/TessellationCache.h
@@ -21,6 +21,7 @@
 #include "Matrix.h"
 #include "Rect.h"
 #include "Vector.h"
+#include "VertexBuffer.h"
 #include "thread/TaskProcessor.h"
 #include "utils/Macros.h"
 #include "utils/Pair.h"
@@ -89,7 +90,7 @@
         hash_t hash() const;
     };
 
-    class ShadowTask : public Task<TessellationCache::vertexBuffer_pair_t*> {
+    class ShadowTask : public Task<vertexBuffer_pair_t> {
     public:
         ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque,
                 const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ,
@@ -104,13 +105,11 @@
             , lightRadius(lightRadius) {
         }
 
-        ~ShadowTask();
-
         /* Note - we deep copy all task parameters, because *even though* pointers into Allocator
          * controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame,
          * certain Allocators are destroyed before trim() is called to flush incomplete tasks.
          *
-         * These deep copies could be avoided, long term, by cancelling or flushing outstanding
+         * These deep copies could be avoided, long term, by canceling or flushing outstanding
          * tasks before tearing down single-frame LinearAllocators.
          */
         const Matrix4 drawTransform;
@@ -121,6 +120,8 @@
         const Matrix4 transformZ;
         const Vector3 lightCenter;
         const float lightRadius;
+        VertexBuffer ambientBuffer;
+        VertexBuffer spotBuffer;
     };
 
     TessellationCache();
@@ -217,12 +218,12 @@
     ///////////////////////////////////////////////////////////////////////////////
     // Shadow tessellation caching
     ///////////////////////////////////////////////////////////////////////////////
-    sp<TaskProcessor<vertexBuffer_pair_t*> > mShadowProcessor;
+    sp<TaskProcessor<vertexBuffer_pair_t> > mShadowProcessor;
 
     // holds a pointer, and implicit strong ref to each shadow task of the frame
-    LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*> mShadowCache;
-    class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t*>*> {
-        void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t*>*& bufferPairTask) override {
+    LruCache<ShadowDescription, Task<vertexBuffer_pair_t>*> mShadowCache;
+    class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t>*> {
+        void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t>*& bufferPairTask) override {
             bufferPairTask->decStrong(nullptr);
         }
     };
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index be25516..accd303 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -86,6 +86,12 @@
 #endif
     ErrorHandler* errorHandler = nullptr;
 
+    // Frame number for use with synchronized surfaceview position updating
+    int64_t frameNumber = -1;
+    int32_t windowInsetLeft = 0;
+    int32_t windowInsetTop = 0;
+    bool updateWindowPositions = false;
+
     struct Out {
         bool hasFunctors = false;
         // This is only updated if evaluateAnimations is true
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index d411621..ea702c0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -92,18 +92,18 @@
     }
 }
 
-void CanvasContext::setSurface(ANativeWindow* window) {
+void CanvasContext::setSurface(Surface* surface) {
     ATRACE_CALL();
 
-    mNativeWindow = window;
+    mNativeSurface = surface;
 
     if (mEglSurface != EGL_NO_SURFACE) {
         mEglManager.destroySurface(mEglSurface);
         mEglSurface = EGL_NO_SURFACE;
     }
 
-    if (window) {
-        mEglSurface = mEglManager.createSurface(window);
+    if (surface) {
+        mEglSurface = mEglManager.createSurface(surface);
     }
 
     if (mEglSurface != EGL_NO_SURFACE) {
@@ -127,8 +127,8 @@
     mSwapBehavior = swapBehavior;
 }
 
-void CanvasContext::initialize(ANativeWindow* window) {
-    setSurface(window);
+void CanvasContext::initialize(Surface* surface) {
+    setSurface(surface);
 #if !HWUI_NEW_OPS
     if (mCanvas) return;
     mCanvas = new OpenGLRenderer(mRenderThread.renderState());
@@ -136,11 +136,11 @@
 #endif
 }
 
-void CanvasContext::updateSurface(ANativeWindow* window) {
-    setSurface(window);
+void CanvasContext::updateSurface(Surface* surface) {
+    setSurface(surface);
 }
 
-bool CanvasContext::pauseSurface(ANativeWindow* window) {
+bool CanvasContext::pauseSurface(Surface* surface) {
     return mRenderThread.removeFrameCallback(this);
 }
 
@@ -204,6 +204,10 @@
     info.renderer = mCanvas;
 #endif
 
+    if (CC_LIKELY(mNativeSurface.get())) {
+        info.frameNumber = static_cast<int64_t>(mNativeSurface->getNextFrameNumber());
+    }
+
     mAnimationContext->startFrame(info.mode);
     for (const sp<RenderNode>& node : mRenderNodes) {
         // Only the primary target node will be drawn full - all other nodes would get drawn in
@@ -219,7 +223,7 @@
     freePrefetechedLayers();
     GL_CHECKPOINT(MODERATE);
 
-    if (CC_UNLIKELY(!mNativeWindow.get())) {
+    if (CC_UNLIKELY(!mNativeSurface.get())) {
         mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
         info.out.canDrawThisFrame = false;
         return;
@@ -242,8 +246,9 @@
         } else {
             // We're maybe behind? Find out for sure
             int runningBehind = 0;
-            mNativeWindow->query(mNativeWindow.get(),
-                    NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
+            // TODO: Have this method be on Surface, too, not just ANativeWindow...
+            ANativeWindow* window = mNativeSurface.get();
+            window->query(window, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
             info.out.canDrawThisFrame = !runningBehind;
         }
     } else {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 63a7977..168166e 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -39,6 +39,7 @@
 #include <SkBitmap.h>
 #include <SkRect.h>
 #include <utils/Functor.h>
+#include <gui/Surface.h>
 
 #include <set>
 #include <string>
@@ -75,10 +76,10 @@
     // Won't take effect until next EGLSurface creation
     void setSwapBehavior(SwapBehavior swapBehavior);
 
-    void initialize(ANativeWindow* window);
-    void updateSurface(ANativeWindow* window);
-    bool pauseSurface(ANativeWindow* window);
-    bool hasSurface() { return mNativeWindow.get(); }
+    void initialize(Surface* surface);
+    void updateSurface(Surface* surface);
+    bool pauseSurface(Surface* surface);
+    bool hasSurface() { return mNativeSurface.get(); }
 
     void setup(int width, int height, float lightRadius,
             uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
@@ -172,7 +173,7 @@
     // lifecycle tracking
     friend class android::uirenderer::RenderState;
 
-    void setSurface(ANativeWindow* window);
+    void setSurface(Surface* window);
     void requireSurface();
 
     void freePrefetechedLayers();
@@ -182,7 +183,7 @@
 
     RenderThread& mRenderThread;
     EglManager& mEglManager;
-    sp<ANativeWindow> mNativeWindow;
+    sp<Surface> mNativeSurface;
     EGLSurface mEglSurface = EGL_NO_SURFACE;
     bool mBufferPreserved = false;
     SwapBehavior mSwapBehavior = kSwap_default;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 1d1b144..7c6cd7e 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -139,38 +139,38 @@
     postAndWait(task); // block since name/value pointers owned by caller
 }
 
-CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
-    args->context->initialize(args->window);
+CREATE_BRIDGE2(initialize, CanvasContext* context, Surface* surface) {
+    args->context->initialize(args->surface);
     return nullptr;
 }
 
-void RenderProxy::initialize(const sp<ANativeWindow>& window) {
+void RenderProxy::initialize(const sp<Surface>& surface) {
     SETUP_TASK(initialize);
     args->context = mContext;
-    args->window = window.get();
+    args->surface = surface.get();
     post(task);
 }
 
-CREATE_BRIDGE2(updateSurface, CanvasContext* context, ANativeWindow* window) {
-    args->context->updateSurface(args->window);
+CREATE_BRIDGE2(updateSurface, CanvasContext* context, Surface* surface) {
+    args->context->updateSurface(args->surface);
     return nullptr;
 }
 
-void RenderProxy::updateSurface(const sp<ANativeWindow>& window) {
+void RenderProxy::updateSurface(const sp<Surface>& surface) {
     SETUP_TASK(updateSurface);
     args->context = mContext;
-    args->window = window.get();
+    args->surface = surface.get();
     postAndWait(task);
 }
 
-CREATE_BRIDGE2(pauseSurface, CanvasContext* context, ANativeWindow* window) {
-    return (void*) args->context->pauseSurface(args->window);
+CREATE_BRIDGE2(pauseSurface, CanvasContext* context, Surface* surface) {
+    return (void*) args->context->pauseSurface(args->surface);
 }
 
-bool RenderProxy::pauseSurface(const sp<ANativeWindow>& window) {
+bool RenderProxy::pauseSurface(const sp<Surface>& surface) {
     SETUP_TASK(pauseSurface);
     args->context = mContext;
-    args->window = window.get();
+    args->surface = surface.get();
     return (bool) postAndWait(task);
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 4180d802..178724a 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -67,9 +67,9 @@
     ANDROID_API bool loadSystemProperties();
     ANDROID_API void setName(const char* name);
 
-    ANDROID_API void initialize(const sp<ANativeWindow>& window);
-    ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
-    ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
+    ANDROID_API void initialize(const sp<Surface>& surface);
+    ANDROID_API void updateSurface(const sp<Surface>& surface);
+    ANDROID_API bool pauseSurface(const sp<Surface>& surface);
     ANDROID_API void setup(int width, int height, float lightRadius,
             uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
     ANDROID_API void setLightCenter(const Vector3& lightCenter);
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index a843e92..58c0876 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -112,9 +112,9 @@
             scene->doFrame(i);
             proxy->syncAndDrawFrame();
         }
-        proxy->fence();
-        nsecs_t done = systemTime(CLOCK_MONOTONIC);
         if (opts.reportFrametimeWeight) {
+            proxy->fence();
+            nsecs_t done = systemTime(CLOCK_MONOTONIC);
             avgMs.add((done - vsync) / 1000000.0);
             if (i % 10 == 9) {
                 printf("Average frametime %.3fms\n", avgMs.average());
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 01bfc5a..20d2f1f 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -455,6 +455,23 @@
     }
 }
 
+TEST(RecordingCanvas, firstClipWillReplace) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SaveFlags::MatrixClip);
+        // since no explicit clip set on canvas, this should be the one observed on op:
+        canvas.clipRect(-100, -100, 300, 300, SkRegion::kIntersect_Op);
+
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+
+        canvas.restore();
+    });
+    ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
+    // first clip must be preserved, even if it extends beyond canvas bounds
+    EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip);
+}
+
 TEST(RecordingCanvas, insertReorderBarrier) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.drawRect(0, 0, 400, 400, SkPaint());
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
new file mode 100644
index 0000000..586625b
--- /dev/null
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "tests/common/TestUtils.h"
+
+#include <gtest/gtest.h>
+#include <SkShader.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+/**
+ * 1x1 bitmaps must not be optimized into solid color shaders, since HWUI can't
+ * compose/render color shaders
+ */
+TEST(SkiaBehavior, CreateBitmapShader1x1) {
+    SkBitmap origBitmap = TestUtils::createSkBitmap(1, 1);
+    std::unique_ptr<SkShader> s(SkShader::CreateBitmapShader(
+            origBitmap,
+            SkShader::kClamp_TileMode,
+            SkShader::kRepeat_TileMode));
+
+    SkBitmap bitmap;
+    SkShader::TileMode xy[2];
+    ASSERT_TRUE(s->isABitmap(&bitmap, nullptr, xy))
+        << "1x1 bitmap shader must query as bitmap shader";
+    EXPECT_EQ(SkShader::kClamp_TileMode, xy[0]);
+    EXPECT_EQ(SkShader::kRepeat_TileMode, xy[1]);
+    EXPECT_EQ(origBitmap.pixelRef(), bitmap.pixelRef());
+}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 20491e4..f9a23f9 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -32,6 +32,7 @@
 import android.graphics.drawable.Icon;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -114,6 +115,7 @@
     private final int mTunerCount;
     private final boolean mCanRecord;
     private final boolean mIsHardwareInput;
+    private final Bundle mExtras;
 
     // Attributes from XML meta data.
     private String mSetupActivity;
@@ -252,7 +254,7 @@
      */
     private TvInputInfo(ResolveInfo service, String id, String parentId, int type,
             boolean isHardwareInput, boolean isConnectedToHdmiSwitch, int tunerCount,
-            boolean canRecord) {
+            boolean canRecord, Bundle extras) {
         mService = service;
         mId = id;
         mParentId = parentId;
@@ -261,6 +263,7 @@
         mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
         mTunerCount = tunerCount;
         mCanRecord = canRecord;
+        mExtras = extras;
     }
 
     /**
@@ -362,6 +365,14 @@
     }
 
     /**
+     * Returns the extras associated with this TV input.
+     * @hide
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
      * Returns the HDMI device information of this TV input.
      * @hide
      */
@@ -524,6 +535,7 @@
         dest.writeInt(mLabelResId);
         dest.writeString(mLabel);
         dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
+        dest.writeBundle(mExtras);
     }
 
     private Drawable loadServiceIcon(Context context) {
@@ -563,6 +575,7 @@
         mLabelResId = in.readInt();
         mLabel = in.readString();
         mIsConnectedToHdmiSwitch = in.readByte() == 1;
+        mExtras = in.readBundle();
     }
 
     /**
@@ -604,6 +617,7 @@
         private HdmiDeviceInfo mHdmiDeviceInfo;
         private String mParentId;
         private TvInputHardwareInfo mTvInputHardwareInfo;
+        private Bundle mExtras;
 
         /**
          * Constructs a new builder for {@link TvInputInfo}.
@@ -732,6 +746,18 @@
         }
 
         /**
+         * Sets the extras associated with this TV input.
+         *
+         * @param extras The extras associated with this TV input.
+         * @return This Builder object to allow for chaining of calls to builder methods.
+         * @hide
+         */
+        public Builder setExtras(Bundle extras) {
+            this.mExtras = extras;
+            return this;
+        }
+
+        /**
          * Creates a {@link TvInputInfo} instance with the specified fields. Most of the information
          * is obtained by parsing the AndroidManifest and {@link TvInputService#SERVICE_META_DATA}
          * for the {@link TvInputService} this TV input implements.
@@ -765,7 +791,7 @@
             }
 
             TvInputInfo info = new TvInputInfo(mResolveInfo, id, mParentId, type, isHardwareInput,
-                    isConnectedToHdmiSwitch, mTunerCount, mCanRecord);
+                    isConnectedToHdmiSwitch, mTunerCount, mCanRecord, mExtras);
             return parseServiceMetadata(type, info);
         }
 
diff --git a/packages/DocumentsUI/res/layout/drawer_layout.xml b/packages/DocumentsUI/res/layout/drawer_layout.xml
index 0146f14..e3def05 100644
--- a/packages/DocumentsUI/res/layout/drawer_layout.xml
+++ b/packages/DocumentsUI/res/layout/drawer_layout.xml
@@ -32,7 +32,7 @@
             android:layout_height="match_parent"
             android:orientation="vertical">
 
-            <com.android.documentsui.DocumentsToolBar
+            <com.android.documentsui.DocumentsToolbar
                 android:id="@+id/toolbar"
                 android:layout_width="match_parent"
                 android:layout_height="?android:attr/actionBarSize"
@@ -48,7 +48,7 @@
                     android:layout_marginStart="4dp"
                     android:overlapAnchor="true" />
 
-            </com.android.documentsui.DocumentsToolBar>
+            </com.android.documentsui.DocumentsToolbar>
 
             <include layout="@layout/directory_cluster"/>
 
diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml
index 0dd4a33..8414feb 100644
--- a/packages/DocumentsUI/res/layout/fixed_layout.xml
+++ b/packages/DocumentsUI/res/layout/fixed_layout.xml
@@ -27,7 +27,7 @@
         android:layout_height="match_parent"
         android:orientation="vertical">
 
-        <com.android.documentsui.DocumentsToolBar
+        <com.android.documentsui.DocumentsToolbar
             android:id="@+id/toolbar"
             android:layout_width="match_parent"
             android:layout_height="?android:attr/actionBarSize"
@@ -43,7 +43,7 @@
                 android:layout_marginStart="4dp"
                 android:overlapAnchor="true" />
 
-        </com.android.documentsui.DocumentsToolBar>
+        </com.android.documentsui.DocumentsToolbar>
 
         <LinearLayout
             android:layout_width="match_parent"
diff --git a/packages/DocumentsUI/res/layout/single_pane_layout.xml b/packages/DocumentsUI/res/layout/single_pane_layout.xml
index c5a5745..f53d698 100644
--- a/packages/DocumentsUI/res/layout/single_pane_layout.xml
+++ b/packages/DocumentsUI/res/layout/single_pane_layout.xml
@@ -27,7 +27,7 @@
         android:layout_height="match_parent"
         android:orientation="vertical">
 
-        <com.android.documentsui.DocumentsToolBar
+        <com.android.documentsui.DocumentsToolbar
             android:id="@+id/toolbar"
             android:layout_width="match_parent"
             android:layout_height="?android:attr/actionBarSize"
@@ -43,7 +43,7 @@
                 android:layout_marginStart="4dp"
                 android:overlapAnchor="true" />
 
-        </com.android.documentsui.DocumentsToolBar>
+        </com.android.documentsui.DocumentsToolbar>
 
         <include layout="@layout/directory_cluster"/>
 
diff --git a/packages/DocumentsUI/res/values/layouts.xml b/packages/DocumentsUI/res/values/layouts.xml
index 8ac1ac2..c9308a1 100644
--- a/packages/DocumentsUI/res/values/layouts.xml
+++ b/packages/DocumentsUI/res/values/layouts.xml
@@ -15,7 +15,7 @@
 -->
 
 <resources>
-    <item name="docs_activity" type="layout">@layout/drawer_layout</item>
+    <item name="documents_activity" type="layout">@layout/drawer_layout</item>
     <item name="files_activity" type="layout">@layout/drawer_layout</item>
-    <item name="manage_roots_activity" type="layout">@layout/single_pane_layout</item>
+    <item name="downloads_activity" type="layout">@layout/single_pane_layout</item>
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 497eb0e..e72343e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -42,16 +42,10 @@
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
+import android.widget.Spinner;
+import android.widget.Toolbar;
 
 import com.android.documentsui.RecentsProvider.ResumeColumns;
 import com.android.documentsui.SearchManager.SearchManagerListener;
@@ -72,7 +66,8 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 
-public abstract class BaseActivity extends Activity implements SearchManagerListener {
+public abstract class BaseActivity extends Activity
+        implements SearchManagerListener, NavigationView.Environment {
 
     static final String EXTRA_STATE = "state";
 
@@ -80,7 +75,7 @@
     RootsCache mRoots;
     SearchManager mSearchManager;
     DrawerController mDrawer;
-    boolean mProductivityDevice;
+    NavigationView mNavigator;
 
     private final String mTag;
     @LayoutRes
@@ -92,7 +87,6 @@
 
     abstract void onTaskFinished(Uri... uris);
     abstract void refreshDirectory(int anim);
-    abstract void updateActionBar();
     abstract void saveStackBlocking();
     abstract State buildState();
 
@@ -101,19 +95,21 @@
         mTag = tag;
     }
 
+    @CallSuper
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        setContentView(mLayoutId);
+
+        mDrawer = DrawerController.create(this);
         mState = (icicle != null)
                 ? icicle.<State>getParcelable(EXTRA_STATE)
                         : buildState();
-
         Metrics.logActivityLaunch(this, mState, getIntent());
 
-        setContentView(mLayoutId);
-
         mRoots = DocumentsApplication.getRootsCache(this);
+
         mRoots.setOnCacheUpdateListener(
                 new RootsCache.OnCacheUpdateListener() {
                     @Override
@@ -121,9 +117,19 @@
                         new HandleRootsChangedTask().execute(getCurrentRoot());
                     }
                 });
+
         mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory);
         mSearchManager = new SearchManager(this);
 
+        DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
+        setActionBar(toolbar);
+        mNavigator = new NavigationView(
+                mDrawer,
+                toolbar,
+                (Spinner) findViewById(R.id.stack),
+                mState,
+                this);
+
         // Base classes must update result in their onCreate.
         setResult(Activity.RESULT_CANCELED);
     }
@@ -133,7 +139,7 @@
         boolean showMenu = super.onCreateOptionsMenu(menu);
 
         getMenuInflater().inflate(R.menu.activity, menu);
-        mSearchManager.install((DocumentsToolBar) findViewById(R.id.toolbar));
+        mSearchManager.install((DocumentsToolbar) findViewById(R.id.toolbar));
 
         return showMenu;
     }
@@ -201,6 +207,11 @@
     void onStackRestored(boolean restored, boolean external) {}
 
     void onRootPicked(RootInfo root) {
+        // Skip refreshing if root didn't change
+        if(root.equals(getCurrentRoot())) {
+            return;
+        }
+
         mState.derivedMode = LocalPreferences.getViewMode(this, root, MODE_GRID);
 
         // Clear entire backstack and start in new root
@@ -336,7 +347,8 @@
      * The current directory name and selection will get updated.
      * @param anim
      */
-    final void refreshCurrentRootAndDirectory(int anim) {
+    @Override
+    public final void refreshCurrentRootAndDirectory(int anim) {
         mSearchManager.cancelSearch();
 
         mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_ENTER);
@@ -347,8 +359,7 @@
             roots.onCurrentRootChanged();
         }
 
-        updateActionBar();
-
+        mNavigator.update();
         invalidateOptionsMenu();
     }
 
@@ -484,6 +495,12 @@
         super.onRestoreInstanceState(state);
     }
 
+    @Override
+    public boolean isSearchExpanded() {
+        return mSearchManager.isExpanded();
+    }
+
+    @Override
     public RootInfo getCurrentRoot() {
         if (mState.stack.root != null) {
             return mState.stack.root;
@@ -692,92 +709,6 @@
         }
     }
 
-    final class ItemSelectedListener implements OnItemSelectedListener {
-
-        boolean mIgnoreNextNavigation;
-
-        @Override
-        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-            if (mIgnoreNextNavigation) {
-                mIgnoreNextNavigation = false;
-                return;
-            }
-
-            while (mState.stack.size() > position + 1) {
-                mState.popDocument();
-            }
-            refreshCurrentRootAndDirectory(ANIM_LEAVE);
-        }
-
-        @Override
-        public void onNothingSelected(AdapterView<?> parent) {
-            // Ignored
-        }
-    }
-
-    /**
-     * Class providing toolbar with runtime access to useful activity data.
-     */
-    final class StackAdapter extends BaseAdapter {
-        @Override
-        public int getCount() {
-            return mState.stack.size();
-        }
-
-        @Override
-        public DocumentInfo getItem(int position) {
-            return mState.stack.get(mState.stack.size() - position - 1);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = LayoutInflater.from(parent.getContext())
-                        .inflate(R.layout.item_subdir_title, parent, false);
-            }
-
-            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
-            final DocumentInfo doc = getItem(position);
-
-            if (position == 0) {
-                final RootInfo root = getCurrentRoot();
-                title.setText(root.title);
-            } else {
-                title.setText(doc.displayName);
-            }
-
-            return convertView;
-        }
-
-        @Override
-        public View getDropDownView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = LayoutInflater.from(parent.getContext())
-                        .inflate(R.layout.item_subdir, parent, false);
-            }
-
-            final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
-            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
-            final DocumentInfo doc = getItem(position);
-
-            if (position == 0) {
-                final RootInfo root = getCurrentRoot();
-                title.setText(root.title);
-                subdir.setVisibility(View.GONE);
-            } else {
-                title.setText(doc.displayName);
-                subdir.setVisibility(View.VISIBLE);
-            }
-
-            return convertView;
-        }
-    }
-
     /**
      * Interface providing access to current view of documents
      * even when all documents are not homed to the same parent.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index b933d0a..815ff3d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -43,10 +43,6 @@
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
-import android.widget.BaseAdapter;
-import android.widget.Spinner;
-import android.widget.Toolbar;
 
 import com.android.documentsui.RecentsProvider.RecentColumns;
 import com.android.documentsui.RecentsProvider.ResumeColumns;
@@ -63,16 +59,8 @@
     private static final int CODE_FORWARD = 42;
     private static final String TAG = "DocumentsActivity";
 
-    private Toolbar mToolbar;
-    private Spinner mToolbarStack;
-
-    private Toolbar mRootsToolbar;
-
-    private ItemSelectedListener mStackListener;
-    private BaseAdapter mStackAdapter;
-
     public DocumentsActivity() {
-        super(R.layout.docs_activity, TAG);
+        super(R.layout.documents_activity, TAG);
     }
 
     @Override
@@ -81,18 +69,6 @@
 
         final Resources res = getResources();
 
-        mDrawer = DrawerController.create(this);
-        mToolbar = (Toolbar) findViewById(R.id.toolbar);
-
-        mStackAdapter = new StackAdapter();
-        mStackListener = new ItemSelectedListener();
-        mToolbarStack = (Spinner) findViewById(R.id.stack);
-        mToolbarStack.setOnItemSelectedListener(mStackListener);
-
-        mRootsToolbar = (Toolbar) findViewById(R.id.roots_toolbar);
-
-        setActionBar(mToolbar);
-
         if (mState.action == ACTION_CREATE) {
             final String mimeType = getIntent().getType();
             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
@@ -180,7 +156,7 @@
         }
 
         if (showDrawer) {
-            setRootsDrawerOpen(true);
+            mNavigator.revealRootsDrawer(true);
         }
     }
 
@@ -217,66 +193,28 @@
     @Override
     protected void onPostCreate(Bundle savedInstanceState) {
         super.onPostCreate(savedInstanceState);
-        mDrawer.syncState();
-        updateActionBar();
-    }
-
-    public void setRootsDrawerOpen(boolean open) {
-        mDrawer.setOpen(open);
+        mDrawer.update();
+        mNavigator.update();
     }
 
     @Override
-    public void updateActionBar() {
-        if (mRootsToolbar != null) {
-            final String prompt = getIntent().getStringExtra(DocumentsContract.EXTRA_PROMPT);
-            if (prompt != null) {
-                mRootsToolbar.setTitle(prompt);
+    public String getDrawerTitle() {
+        String title = getIntent().getStringExtra(DocumentsContract.EXTRA_PROMPT);
+        if (title == null) {
+            if (mState.action == ACTION_OPEN ||
+                mState.action == ACTION_GET_CONTENT ||
+                mState.action == ACTION_OPEN_TREE) {
+                title = getResources().getString(R.string.title_open);
+            } else if (mState.action == ACTION_CREATE ||
+                       mState.action == ACTION_PICK_COPY_DESTINATION) {
+                title = getResources().getString(R.string.title_save);
             } else {
-                if (mState.action == ACTION_OPEN ||
-                    mState.action == ACTION_GET_CONTENT ||
-                    mState.action == ACTION_OPEN_TREE) {
-                    mRootsToolbar.setTitle(R.string.title_open);
-                } else if (mState.action == ACTION_CREATE ||
-                           mState.action == ACTION_PICK_COPY_DESTINATION) {
-                    mRootsToolbar.setTitle(R.string.title_save);
-                }
+                // If all else fails, just call it "Files".
+                title = getResources().getString(R.string.files_label);
             }
         }
 
-        if (mDrawer.isUnlocked()) {
-            mToolbar.setNavigationIcon(R.drawable.ic_hamburger);
-            mToolbar.setNavigationContentDescription(R.string.drawer_open);
-            mToolbar.setNavigationOnClickListener(
-                    new View.OnClickListener() {
-                        @Override
-                        public void onClick(View v) {
-                            setRootsDrawerOpen(true);
-                        }
-                    });
-        } else {
-            mToolbar.setNavigationIcon(null);
-            mToolbar.setNavigationContentDescription(R.string.drawer_open);
-            mToolbar.setNavigationOnClickListener(null);
-        }
-
-        if (mSearchManager.isExpanded()) {
-            mToolbar.setTitle(null);
-            mToolbarStack.setVisibility(View.GONE);
-            mToolbarStack.setAdapter(null);
-        } else {
-            if (mState.stack.size() <= 1) {
-                mToolbar.setTitle(getCurrentRoot().title);
-                mToolbarStack.setVisibility(View.GONE);
-                mToolbarStack.setAdapter(null);
-            } else {
-                mToolbar.setTitle(null);
-                mToolbarStack.setVisibility(View.VISIBLE);
-                mToolbarStack.setAdapter(mStackAdapter);
-
-                mStackListener.mIgnoreNextNavigation = true;
-                mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
-            }
-        }
+        return title;
     }
 
     @Override
@@ -331,11 +269,6 @@
     }
 
     @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        return mDrawer.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
-    }
-
-    @Override
     void refreshDirectory(int anim) {
         final FragmentManager fm = getFragmentManager();
         final RootInfo root = getCurrentRoot();
@@ -396,7 +329,11 @@
     @Override
     void onRootPicked(RootInfo root) {
         super.onRootPicked(root);
-        setRootsDrawerOpen(false);
+        mNavigator.revealRootsDrawer(false);
+    }
+
+    public void setRootsDrawerOpen(boolean open) {
+        mNavigator.revealRootsDrawer(open);
     }
 
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsToolBar.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsToolbar.java
similarity index 86%
rename from packages/DocumentsUI/src/com/android/documentsui/DocumentsToolBar.java
rename to packages/DocumentsUI/src/com/android/documentsui/DocumentsToolbar.java
index 36b7646..7742cbf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsToolBar.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsToolbar.java
@@ -24,28 +24,28 @@
 /**
  * ToolBar of Documents UI.
  */
-public class DocumentsToolBar extends Toolbar {
+public class DocumentsToolbar extends Toolbar {
     interface OnActionViewCollapsedListener {
         void onActionViewCollapsed();
     }
 
     private OnActionViewCollapsedListener mOnActionViewCollapsedListener;
 
-    public DocumentsToolBar(Context context, AttributeSet attrs,
+    public DocumentsToolbar(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
-    public DocumentsToolBar(Context context, AttributeSet attrs,
+    public DocumentsToolbar(Context context, AttributeSet attrs,
             int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
 
-    public DocumentsToolBar(Context context, AttributeSet attrs) {
+    public DocumentsToolbar(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    public DocumentsToolBar(Context context) {
+    public DocumentsToolbar(Context context) {
         super(context);
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
index 5cc677c..89be910 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
@@ -35,9 +35,6 @@
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
-import android.widget.BaseAdapter;
-import android.widget.Spinner;
 import android.widget.Toolbar;
 
 import com.android.documentsui.RecentsProvider.ResumeColumns;
@@ -56,14 +53,8 @@
 public class DownloadsActivity extends BaseActivity {
     private static final String TAG = "DownloadsActivity";
 
-    private Toolbar mToolbar;
-    private Spinner mToolbarStack;
-
-    private ItemSelectedListener mStackListener;
-    private BaseAdapter mStackAdapter;
-
     public DownloadsActivity() {
-        super(R.layout.manage_roots_activity, TAG);
+        super(R.layout.downloads_activity, TAG);
     }
 
     @Override
@@ -72,19 +63,10 @@
 
         final Context context = this;
 
-        mDrawer = DrawerController.createDummy();
-
-        mToolbar = (Toolbar) findViewById(R.id.toolbar);
-        mToolbar.setTitleTextAppearance(context,
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        toolbar.setTitleTextAppearance(context,
                 android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
 
-        mStackAdapter = new StackAdapter();
-        mStackListener = new ItemSelectedListener();
-        mToolbarStack = (Spinner) findViewById(R.id.stack);
-        mToolbarStack.setOnItemSelectedListener(mStackListener);
-
-        setActionBar(mToolbar);
-
         if (!mState.restored) {
             // In this case, we set the activity title in AsyncTask.onPostExecute(). To prevent
             // talkback from reading aloud the default title, we clear it here.
@@ -112,33 +94,12 @@
     @Override
     protected void onPostCreate(Bundle savedInstanceState) {
         super.onPostCreate(savedInstanceState);
-        updateActionBar();
+        mNavigator.update();
     }
 
     @Override
-    public void updateActionBar() {
-        // No navigation in manage root mode.
-        mToolbar.setNavigationIcon(null);
-        mToolbar.setNavigationOnClickListener(null);
-
-        if (mSearchManager.isExpanded()) {
-            mToolbar.setTitle(null);
-            mToolbarStack.setVisibility(View.GONE);
-            mToolbarStack.setAdapter(null);
-        } else {
-            if (mState.stack.size() <= 1) {
-                mToolbar.setTitle(getCurrentRoot().title);
-                mToolbarStack.setVisibility(View.GONE);
-                mToolbarStack.setAdapter(null);
-            } else {
-                mToolbar.setTitle(null);
-                mToolbarStack.setVisibility(View.VISIBLE);
-                mToolbarStack.setAdapter(mStackAdapter);
-
-                mStackListener.mIgnoreNextNavigation = true;
-                mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
-            }
-        }
+    public String getDrawerTitle() {
+        return null;  // being and nothingness
     }
 
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
index df3ac1b..bcf69c4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
@@ -22,8 +22,8 @@
 import android.support.v4.app.ActionBarDrawerToggle;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v4.widget.DrawerLayout.DrawerListener;
-import android.view.MenuItem;
 import android.view.View;
+import android.widget.Toolbar;
 
 /**
  * A facade over the various pieces comprising "roots fragment in a Drawer".
@@ -33,13 +33,10 @@
 abstract class DrawerController implements DrawerListener {
 
     abstract void setOpen(boolean open);
-    abstract void lockOpen();
-    abstract void lockClosed();
     abstract boolean isPresent();
     abstract boolean isOpen();
-    abstract boolean isUnlocked();
-    abstract void syncState();
-    abstract boolean onOptionsItemSelected(MenuItem item);
+    abstract void setTitle(String title);
+    abstract void update();
 
     /**
      * Returns a controller suitable for {@code Layout}.
@@ -53,6 +50,8 @@
         }
 
         View drawer = activity.findViewById(R.id.drawer_roots);
+        Toolbar toolbar = (Toolbar) activity.findViewById(R.id.roots_toolbar);
+
         ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                 activity,
                 layout,
@@ -60,7 +59,7 @@
                 R.string.drawer_open,
                 R.string.drawer_close);
 
-        return new RuntimeDrawerController(layout, drawer, toggle);
+        return new RuntimeDrawerController(layout, drawer, toggle, toolbar);
     }
 
     /**
@@ -78,9 +77,12 @@
         private final ActionBarDrawerToggle mToggle;
         private DrawerLayout mLayout;
         private View mDrawer;
+        private Toolbar mToolbar;
 
         public RuntimeDrawerController(
-                DrawerLayout layout, View drawer, ActionBarDrawerToggle toggle) {
+                DrawerLayout layout, View drawer, ActionBarDrawerToggle toggle,
+                Toolbar drawerToolbar) {
+            mToolbar = drawerToolbar;
             checkArgument(layout != null);
 
             mLayout = layout;
@@ -110,31 +112,16 @@
         }
 
         @Override
-        void syncState() {
+        void setTitle(String title) {
+            mToolbar.setTitle(title);
+        }
+
+        @Override
+        void update() {
             mToggle.syncState();
         }
 
         @Override
-        boolean isUnlocked() {
-            return mLayout.getDrawerLockMode(mDrawer) == DrawerLayout.LOCK_MODE_UNLOCKED;
-        }
-
-        @Override
-        void lockOpen() {
-            mLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
-        }
-
-        @Override
-        void lockClosed() {
-            mLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
-        }
-
-        @Override
-        boolean onOptionsItemSelected(MenuItem item) {
-            return false;
-        }
-
-        @Override
         public void onDrawerSlide(View drawerView, float slideOffset) {
             mToggle.onDrawerSlide(drawerView, slideOffset);
         }
@@ -163,14 +150,6 @@
         @Override
         void setOpen(boolean open) {}
 
-        @Override
-        void syncState() {}
-
-        @Override
-        void lockOpen() {}
-
-        @Override
-        void lockClosed() {}
 
         @Override
         boolean isOpen() {
@@ -178,19 +157,15 @@
         }
 
         @Override
-        boolean isUnlocked() {
-            return true;
-        }
-
-        @Override
         boolean isPresent() {
             return false;
         }
 
         @Override
-        boolean onOptionsItemSelected(MenuItem item) {
-            return false;
-        }
+        void setTitle(String title) {}
+
+        @Override
+        void update() {}
 
         @Override
         public void onDrawerSlide(View drawerView, float slideOffset) {}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 064535a..3aba356 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -40,10 +40,6 @@
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
-import android.widget.BaseAdapter;
-import android.widget.Spinner;
-import android.widget.Toolbar;
 
 import com.android.documentsui.OperationDialogFragment.DialogType;
 import com.android.documentsui.RecentsProvider.ResumeColumns;
@@ -67,10 +63,6 @@
 
     public static final String TAG = "FilesActivity";
 
-    private Toolbar mToolbar;
-    private Spinner mToolbarStack;
-    private ItemSelectedListener mStackListener;
-    private BaseAdapter mStackAdapter;
     private DocumentClipper mClipper;
 
     public FilesActivity() {
@@ -81,17 +73,7 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        mToolbar = (Toolbar) findViewById(R.id.toolbar);
-
-        mStackAdapter = new StackAdapter();
-        mStackListener = new ItemSelectedListener();
-        mToolbarStack = (Spinner) findViewById(R.id.stack);
-        mToolbarStack.setOnItemSelectedListener(mStackListener);
-
-        setActionBar(mToolbar);
-
         mClipper = new DocumentClipper(this);
-        mDrawer = DrawerController.create(this);
 
         RootsFragment.show(getFragmentManager(), null);
 
@@ -179,11 +161,11 @@
         // serach. Why? Because this avoid an early (undesired) load of
         // the recents root...which is the default root in other activities.
         // In Files app "Home" is the default, but it is loaded async.
-        // updateActionBar will be called once Home root is loaded.
+        // update will be called once Home root is loaded.
         // Except while searching we need this call to ensure the
         // search bits get layed out correctly.
         if (mSearchManager.isSearching()) {
-            updateActionBar();
+            mNavigator.update();
         }
     }
 
@@ -203,44 +185,8 @@
     }
 
     @Override
-    public void updateActionBar() {
-        final RootInfo root = getCurrentRoot();
-
-        if (mDrawer.isPresent()) {
-            mToolbar.setNavigationIcon(R.drawable.ic_hamburger);
-            mToolbar.setNavigationContentDescription(R.string.drawer_open);
-            mToolbar.setNavigationOnClickListener(
-                    new View.OnClickListener() {
-                        @Override
-                        public void onClick(View v) {
-                            mDrawer.setOpen(true);
-                        }
-                    });
-        } else {
-            mToolbar.setNavigationIcon(
-                    root != null ? root.loadToolbarIcon(mToolbar.getContext()) : null);
-            mToolbar.setNavigationContentDescription(R.string.drawer_open);
-            mToolbar.setNavigationOnClickListener(null);
-        }
-
-        if (mSearchManager.isExpanded()) {
-            mToolbar.setTitle(null);
-            mToolbarStack.setVisibility(View.GONE);
-            mToolbarStack.setAdapter(null);
-        } else {
-            if (mState.stack.size() <= 1) {
-                mToolbar.setTitle(root.title);
-                mToolbarStack.setVisibility(View.GONE);
-                mToolbarStack.setAdapter(null);
-            } else {
-                mToolbar.setTitle(null);
-                mToolbarStack.setVisibility(View.VISIBLE);
-                mToolbarStack.setAdapter(mStackAdapter);
-
-                mStackListener.mIgnoreNextNavigation = true;
-                mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
-            }
-        }
+    public String getDrawerTitle() {
+        return getResources().getString(R.string.files_label);
     }
 
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
new file mode 100644
index 0000000..ff1940a
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2016 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.documentsui;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_LEAVE;
+
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
+
+/**
+ * A facade over the portions of the app and drawer toolbars.
+ */
+class NavigationView {
+
+    private static final String TAG = "NavigationView";
+
+    private final DrawerController mDrawer;
+    private final DocumentsToolbar mToolbar;
+    private final Spinner mBreadcrumb;
+    private final State mState;
+    private final NavigationView.Environment mEnv;
+    private final BreadcrumbAdapter mBreadcrumbAdapter;
+
+    private boolean mIgnoreNextNavigation;
+
+    public NavigationView(
+            DrawerController drawer,
+            DocumentsToolbar toolbar,
+            Spinner breadcrumb,
+            State state,
+            NavigationView.Environment env) {
+
+        mToolbar = toolbar;
+        mBreadcrumb = breadcrumb;
+        mDrawer = drawer;
+        mState = state;
+        mEnv = env;
+
+        mBreadcrumbAdapter = new BreadcrumbAdapter(mState, mEnv);
+        mToolbar.setNavigationOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        onNavigationIconClicked();
+                    }
+
+                });
+
+        mBreadcrumb.setOnItemSelectedListener(
+                new OnItemSelectedListener() {
+                    @Override
+                    public void onItemSelected(
+                            AdapterView<?> parent, View view, int position, long id) {
+                        onBreadcrumbItemSelected(position);
+                    }
+
+                    @Override
+                    public void onNothingSelected(AdapterView<?> parent) {}
+                });
+
+    }
+
+    private void onNavigationIconClicked() {
+        if (mDrawer.isPresent()) {
+            mDrawer.setOpen(true);
+        }
+    }
+
+    private void onBreadcrumbItemSelected(int position) {
+        if (mIgnoreNextNavigation) {
+            mIgnoreNextNavigation = false;
+            return;
+        }
+
+        while (mState.stack.size() > position + 1) {
+            mState.popDocument();
+        }
+        mEnv.refreshCurrentRootAndDirectory(ANIM_LEAVE);
+    }
+
+    void update() {
+
+        // TODO: Looks to me like this block is never getting hit.
+        if (mEnv.isSearchExpanded()) {
+            mToolbar.setTitle(null);
+            mBreadcrumb.setVisibility(View.GONE);
+            mBreadcrumb.setAdapter(null);
+            return;
+        }
+
+        mDrawer.setTitle(mEnv.getDrawerTitle());
+
+        mToolbar.setNavigationIcon(getActionBarIcon());
+        mToolbar.setNavigationContentDescription(R.string.drawer_open);
+
+        if (mState.stack.size() <= 1) {
+            showBreadcrumb(false);
+            String title = mEnv.getCurrentRoot().title;
+            if (DEBUG) Log.d(TAG, "New toolbar title is: " + title);
+            mToolbar.setTitle(title);
+        } else {
+            showBreadcrumb(true);
+            mToolbar.setTitle(null);
+            mIgnoreNextNavigation = true;
+            mBreadcrumb.setSelection(mBreadcrumbAdapter.getCount() - 1);
+        }
+
+        if (DEBUG) Log.d(TAG, "Final toolbar title is: " + mToolbar.getTitle());
+    }
+
+    private void showBreadcrumb(boolean visibility) {
+        if (visibility) {
+            mBreadcrumb.setVisibility(VISIBLE);
+            mBreadcrumb.setAdapter(mBreadcrumbAdapter);
+        } else {
+            mBreadcrumb.setVisibility(GONE);
+            mBreadcrumb.setAdapter(null);
+        }
+    }
+
+    // Hamburger if drawer is present, else root icon, or sad nullness.
+    private @Nullable Drawable getActionBarIcon() {
+        if (mDrawer.isPresent()) {
+            return mToolbar.getContext().getDrawable(R.drawable.ic_hamburger);
+        } else {
+            RootInfo root = mEnv.getCurrentRoot();
+            if (root != null) {
+                return root.loadToolbarIcon(mToolbar.getContext());
+            }
+        }
+        return null;
+    }
+
+    void revealRootsDrawer(boolean open) {
+        mDrawer.setOpen(open);
+    }
+
+    /**
+     * Class providing toolbar with runtime access to useful activity data.
+     */
+    static final class BreadcrumbAdapter extends BaseAdapter {
+
+        private Environment mEnv;
+        private State mState;
+
+        public BreadcrumbAdapter(State state, Environment env) {
+            mState = state;
+            mEnv = env;
+        }
+
+        @Override
+        public int getCount() {
+            return mState.stack.size();
+        }
+
+        @Override
+        public DocumentInfo getItem(int position) {
+            return mState.stack.get(mState.stack.size() - position - 1);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.item_subdir_title, parent, false);
+            }
+
+            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final DocumentInfo doc = getItem(position);
+
+            if (position == 0) {
+                final RootInfo root = mEnv.getCurrentRoot();
+                title.setText(root.title);
+            } else {
+                title.setText(doc.displayName);
+            }
+
+            return convertView;
+        }
+
+        @Override
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.item_subdir, parent, false);
+            }
+
+            final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
+            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final DocumentInfo doc = getItem(position);
+
+            if (position == 0) {
+                final RootInfo root = mEnv.getCurrentRoot();
+                title.setText(root.title);
+                subdir.setVisibility(View.GONE);
+            } else {
+                title.setText(doc.displayName);
+                subdir.setVisibility(View.VISIBLE);
+            }
+
+            return convertView;
+        }
+    }
+
+    interface Environment {
+        RootInfo getCurrentRoot();
+        String getDrawerTitle();
+        void refreshCurrentRootAndDirectory(int animation);
+        boolean isSearchExpanded();
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index c64953d..26bda312 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -180,9 +180,9 @@
             Item item = mAdapter.getItem(position);
             if (item instanceof RootItem) {
                 BaseActivity activity = BaseActivity.get(RootsFragment.this);
-                RootInfo info = ((RootItem) item).root;
-                Metrics.logRootVisited(getActivity(), info);
-                activity.onRootPicked(info);
+                RootInfo newRoot = ((RootItem) item).root;
+                Metrics.logRootVisited(getActivity(), newRoot);
+                activity.onRootPicked(newRoot);
             } else if (item instanceof AppItem) {
                 DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
                 ResolveInfo info = ((AppItem) item).info;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java b/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java
index fb585a6..69f54c7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java
@@ -47,7 +47,7 @@
     private boolean mSearchExpanded;
     private boolean mIgnoreNextClose;
 
-    private DocumentsToolBar mActionBar;
+    private DocumentsToolbar mActionBar;
     private MenuItem mMenu;
     private SearchView mView;
 
@@ -59,7 +59,7 @@
         mListener = listener;
     }
 
-    public void install(DocumentsToolBar actionBar) {
+    public void install(DocumentsToolbar actionBar) {
         assert (mActionBar == null);
         mActionBar = actionBar;
         mMenu = actionBar.getSearchMenu();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 5f04eac..1e3da6b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -100,6 +100,7 @@
 import com.android.documentsui.model.RootInfo;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperations;
+
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
@@ -151,7 +152,6 @@
     private String mStateKey;
 
     private int mLastSortOrder = SORT_ORDER_UNKNOWN;
-    private boolean mLastShowSize;
     private DocumentsAdapter mAdapter;
     private LoaderCallbacks<DirectoryResult> mCallbacks;
     private FragmentTuner mTuner;
@@ -428,7 +428,6 @@
 
     private void updateDisplayState() {
         State state = getDisplayState();
-        mLastShowSize = state.showSize;
         updateLayout(state.derivedMode);
         mRecView.setAdapter(mAdapter);
     }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
index 0a91427..243c357 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
@@ -30,7 +30,7 @@
 import android.support.test.uiautomator.Configurator;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
-import android.test.InstrumentationTestCase;
+import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -38,7 +38,7 @@
 import com.android.documentsui.model.RootInfo;
 
 @LargeTest
-public class DownloadsActivityUiTest extends InstrumentationTestCase {
+public class DownloadsActivityUiTest extends ActivityInstrumentationTestCase2<DownloadsActivity> {
 
     private static final int TIMEOUT = 5000;
     private static final String TAG = "DownloadsActivityUiTest";
@@ -53,6 +53,10 @@
     private ContentProviderClient mClient;
     private RootInfo mRoot;
 
+    public DownloadsActivityUiTest() {
+        super(DownloadsActivity.class);
+    }
+
     public void setUp() throws Exception {
         // Initialize UiDevice instance.
         Instrumentation instrumentation = getInstrumentation();
@@ -79,7 +83,8 @@
         intent.setDataAndType(mRoot.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        mContext.startActivity(intent);
+        setActivityIntent(intent);
+        getActivity();  // Start the activity.
 
         // Wait for the app to appear.
         mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
@@ -92,11 +97,11 @@
 
     @Override
     protected void tearDown() throws Exception {
-        // Need to kill off the task we started.
-        super.tearDown();
         Log.d(TAG, "Resetting storage from setUp");
         resetStorage();
         mClient.release();
+
+        super.tearDown();
     }
 
     private void resetStorage() throws RemoteException {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 2535a80e..868dbbb 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -30,7 +30,7 @@
 import android.support.test.uiautomator.Configurator;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
-import android.test.InstrumentationTestCase;
+import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -38,7 +38,7 @@
 import com.android.documentsui.model.RootInfo;
 
 @LargeTest
-public class FilesActivityUiTest extends InstrumentationTestCase {
+public class FilesActivityUiTest extends ActivityInstrumentationTestCase2<FilesActivity> {
 
     private static final int TIMEOUT = 5000;
     private static final String TAG = "FilesActivityUiTest";
@@ -54,6 +54,10 @@
     private RootInfo mRoot_0;
     private RootInfo mRoot_1;
 
+    public FilesActivityUiTest() {
+        super(FilesActivity.class);
+    }
+
     public void setUp() throws Exception {
         // Initialize UiDevice instance.
         Instrumentation instrumentation = getInstrumentation();
@@ -71,12 +75,14 @@
         mResolver = mContext.getContentResolver();
 
         mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+        assertNotNull("Failed to acquire ContentProviderClient.", mClient);
         mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
 
         // Launch app.
         Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        mContext.startActivity(intent);
+        setActivityIntent(intent);
+        getActivity();  // Start the activity.
 
         // Wait for the app to appear.
         mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
@@ -89,10 +95,11 @@
 
     @Override
     protected void tearDown() throws Exception {
-        super.tearDown();
         Log.d(TAG, "Resetting storage from setUp");
         resetStorage();
         mClient.release();
+
+        super.tearDown();
     }
 
     private void resetStorage() throws RemoteException {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 9855427..2527650 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -16,10 +16,6 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.Shared.TAG;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.ProviderInfo;
@@ -471,6 +467,14 @@
 
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
+        // We're not supposed to override any of the default DocumentsProvider
+        // methods that are supported by "call", so javadoc asks that we
+        // always call super.call first and return if response is not null.
+        Bundle result = super.call(method, arg, extras);
+        if (result != null) {
+            return result;
+        }
+
         switch (method) {
             case "clear":
                 clearCacheAndBuildRoots();
@@ -484,11 +488,10 @@
                 simulateReadErrorsForFile(arg);
                 return null;
             case "createDocumentWithFlags":
-                Bundle bundle = dispatchCreateDocumentWithFlags(extras);
-                return bundle;
-            default:
-                return super.call(method, arg, extras);
+                return dispatchCreateDocumentWithFlags(extras);
         }
+
+        return null;
     }
 
     private Bundle createVirtualFileFromBundle(Bundle extras) {
diff --git a/packages/ExtServices/Android.mk b/packages/ExtServices/Android.mk
new file mode 100644
index 0000000..e8a4007
--- /dev/null
+++ b/packages/ExtServices/Android.mk
@@ -0,0 +1,40 @@
+# Copyright (C) 2016 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)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ExtServices
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_AAPT_FLAGS := --shared-lib
+
+LOCAL_EXPORT_PACKAGE_RESOURCES := true
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.proguard
+
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
+
+
+
+
+
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
new file mode 100644
index 0000000..100b35c
--- /dev/null
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="android.ext.services"
+    android:versionCode="1"
+    android:versionName="1"
+    coreApp="true">
+
+    <application android:label="@string/app_name"
+        android:allowBackup="false"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
+
+        <library android:name="android.ext.services"/>
+
+    </application>
+
+</manifest>
diff --git a/packages/ExtServices/MODULE_LICENSE_APACHE2 b/packages/ExtServices/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/ExtServices/MODULE_LICENSE_APACHE2
diff --git a/packages/ExtServices/NOTICE b/packages/ExtServices/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/packages/ExtServices/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/packages/ExtServices/proguard.proguard b/packages/ExtServices/proguard.proguard
new file mode 100644
index 0000000..e5dfbe1
--- /dev/null
+++ b/packages/ExtServices/proguard.proguard
@@ -0,0 +1,7 @@
+-keepparameternames
+-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
+                SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
+
+-keep public class * {
+    public protected *;
+}
diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml
new file mode 100644
index 0000000..531e517
--- /dev/null
+++ b/packages/ExtServices/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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">
+    <string name="app_name">Android Services Library</string>
+</resources>
diff --git a/packages/ExtServices/src/android/ext/services/Version.java b/packages/ExtServices/src/android/ext/services/Version.java
new file mode 100644
index 0000000..026cccd
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/Version.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.ext.services;
+
+/**
+ * Class that provides the version of the library.
+ */
+public final class Version {
+
+    private Version() {
+        /* do nothing - hide constructor */
+    }
+
+    /**
+     * Gets the version of the library.
+     *
+     * @return The version.
+     */
+    public static int getVersionCode() {
+        return 1;
+    }
+}
\ No newline at end of file
diff --git a/packages/ExtShared/Android.mk b/packages/ExtShared/Android.mk
new file mode 100644
index 0000000..d8052df
--- /dev/null
+++ b/packages/ExtShared/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2016 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)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ExtShared
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_AAPT_FLAGS := --shared-lib
+
+LOCAL_EXPORT_PACKAGE_RESOURCES := true
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.proguard
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/packages/ExtShared/AndroidManifest.xml b/packages/ExtShared/AndroidManifest.xml
new file mode 100644
index 0000000..e2e81d0
--- /dev/null
+++ b/packages/ExtShared/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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="android.ext.shared"
+    android:versionCode="1"
+    android:versionName="1"
+    coreApp="true">
+
+    <application android:label="@string/app_name"
+        android:allowBackup="false"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
+
+        <library android:name="android.ext.shared"/>
+
+    </application>
+
+</manifest>
diff --git a/packages/ExtShared/MODULE_LICENSE_APACHE2 b/packages/ExtShared/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/ExtShared/MODULE_LICENSE_APACHE2
diff --git a/packages/ExtShared/NOTICE b/packages/ExtShared/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/packages/ExtShared/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/packages/ExtShared/proguard.proguard b/packages/ExtShared/proguard.proguard
new file mode 100644
index 0000000..e5dfbe1
--- /dev/null
+++ b/packages/ExtShared/proguard.proguard
@@ -0,0 +1,7 @@
+-keepparameternames
+-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
+                SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
+
+-keep public class * {
+    public protected *;
+}
diff --git a/packages/ExtShared/res/values/strings.xml b/packages/ExtShared/res/values/strings.xml
new file mode 100644
index 0000000..0914e43
--- /dev/null
+++ b/packages/ExtShared/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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">
+    <string name="app_name">Android Shared Library</string>
+</resources>
diff --git a/packages/ExtShared/src/android/ext/shared/Version.java b/packages/ExtShared/src/android/ext/shared/Version.java
new file mode 100644
index 0000000..3ff2d41
--- /dev/null
+++ b/packages/ExtShared/src/android/ext/shared/Version.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.ext.shared;
+
+/**
+ * Class that provides the version of the library.
+ */
+public final class Version {
+
+    private Version() {
+        /* do nothing - hide constructor */
+    }
+
+    /**
+     * Gets the version of the library.
+     *
+     * @return The version.
+     */
+    public static int getVersionCode() {
+        return 1;
+    }
+}
\ No newline at end of file
diff --git a/packages/MtpDocumentsProvider/AndroidManifest.xml b/packages/MtpDocumentsProvider/AndroidManifest.xml
index 7ea54c9..2dd49ab 100644
--- a/packages/MtpDocumentsProvider/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/AndroidManifest.xml
@@ -30,16 +30,12 @@
         </activity>
 
         <receiver android:name=".UsbIntentReceiver" android:exported="true">
-            <!-- TODO: Remove an intent-filter for USB_DEVICE_ATTACHED after the provider becomes to
-                       open devices on demand. -->
             <intent-filter>
                 <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
             </intent-filter>
             <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                        android:resource="@xml/device_filter" />
-            <intent-filter>
-                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
-            </intent-filter>
         </receiver>
     </application>
 </manifest>
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
index 76a1fad..124d207 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
@@ -44,11 +44,38 @@
             return false;
         final Identifier other = (Identifier) obj;
         return mDeviceId == other.mDeviceId && mStorageId == other.mStorageId &&
-                mObjectHandle == other.mObjectHandle && mDocumentId == other.mDocumentId;
+                mObjectHandle == other.mObjectHandle && mDocumentId.equals(other.mDocumentId);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mDeviceId, mStorageId, mObjectHandle, mDocumentId);
     }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("Identifier { ");
+
+        builder.append("mDeviceId: ");
+        builder.append(mDeviceId);
+        builder.append(", ");
+
+        builder.append("mStorageId: ");
+        builder.append(mStorageId);
+        builder.append(", ");
+
+        builder.append("mObjectHandle: ");
+        builder.append(mObjectHandle);
+        builder.append(", ");
+
+        builder.append("mDocumentId: ");
+        builder.append(mDocumentId);
+        builder.append(", ");
+
+        builder.append("mDocumentType: ");
+        builder.append(mDocumentType);
+        builder.append(" }");
+        return builder.toString();
+    }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index ac47067..3faa8f4 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -20,11 +20,9 @@
 
 import android.annotation.Nullable;
 import android.content.ContentValues;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
 import android.mtp.MtpObjectInfo;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
@@ -87,12 +85,10 @@
     /**
      * Puts root information to database.
      * @param parentDocumentId Document ID of device document.
-     * @param resources Resources required to localize root name.
      * @param roots List of root information.
      * @return If roots are added or removed from the database.
      */
-    synchronized boolean putStorageDocuments(
-            String parentDocumentId, Resources resources, MtpRoot[] roots) {
+    synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) {
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         database.beginTransaction();
         try {
@@ -117,7 +113,7 @@
                 valuesList[i] = new ContentValues();
                 extraValuesList[i] = new ContentValues();
                 MtpDatabase.getStorageDocumentValues(
-                        valuesList[i], extraValuesList[i], resources, parentDocumentId, roots[i]);
+                        valuesList[i], extraValuesList[i], parentDocumentId, roots[i]);
             }
             final boolean changed = putDocuments(
                     valuesList,
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 59d85c1..b44f9af 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -108,7 +108,7 @@
      * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
      * @return Database cursor.
      */
-    Cursor queryRoots(String[] columnNames) {
+    Cursor queryRoots(Resources resources, String[] columnNames) {
         final String selection =
                 COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + " = ?";
         final Cursor deviceCursor = mDatabase.query(
@@ -183,10 +183,14 @@
                     }
                     if (storageCursor.getCount() == 1 && values.containsKey(Root.COLUMN_TITLE)) {
                         storageCursor.moveToFirst();
+                        // Add storage name to device name if we have only 1 storage.
                         values.put(
                                 Root.COLUMN_TITLE,
-                                storageCursor.getString(
-                                        storageCursor.getColumnIndex(Root.COLUMN_TITLE)));
+                                resources.getString(
+                                        R.string.root_name,
+                                        values.getAsString(Root.COLUMN_TITLE),
+                                        storageCursor.getString(
+                                                storageCursor.getColumnIndex(Root.COLUMN_TITLE))));
                     }
                 } finally {
                     storageCursor.close();
@@ -533,13 +537,11 @@
     /**
      * Gets {@link ContentValues} for the given root.
      * @param values {@link ContentValues} that receives values.
-     * @param resources Resources used to get localized root name.
      * @param root Root to be converted {@link ContentValues}.
      */
     static void getStorageDocumentValues(
             ContentValues values,
             ContentValues extraValues,
-            Resources resources,
             String parentDocumentId,
             MtpRoot root) {
         values.clear();
@@ -550,13 +552,12 @@
         values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
         values.put(COLUMN_DOCUMENT_TYPE, DOCUMENT_TYPE_STORAGE);
         values.put(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
-        values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
+        values.put(Document.COLUMN_DISPLAY_NAME, root.mDescription);
         values.putNull(Document.COLUMN_SUMMARY);
         values.putNull(Document.COLUMN_LAST_MODIFIED);
         values.put(Document.COLUMN_ICON, R.drawable.ic_root_mtp);
         values.put(Document.COLUMN_FLAGS, 0);
-        values.put(Document.COLUMN_SIZE,
-                (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
+        values.put(Document.COLUMN_SIZE, root.mMaxCapacity - root.mFreeSpace);
 
         extraValues.put(
                 Root.COLUMN_FLAGS,
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 775f976..70a1aae 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -58,6 +58,8 @@
             Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
     };
 
+    static final boolean DEBUG = false;
+
     private final Object mDeviceListLock = new Object();
 
     private static MtpDocumentsProvider sSingleton;
@@ -70,6 +72,7 @@
     private Resources mResources;
     private MtpDatabase mDatabase;
     private AppFuse mAppFuse;
+    private ServiceIntentSender mIntentSender;
 
     /**
      * Provides singleton instance to MtpDocumentsService.
@@ -86,8 +89,9 @@
         mResolver = getContext().getContentResolver();
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
         mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
-        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
+        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
         mAppFuse = new AppFuse(TAG, new AppFuseCallback());
+        mIntentSender = new ServiceIntentSender(getContext());
         // TODO: Mount AppFuse on demands.
         try {
             mAppFuse.mount(getContext().getSystemService(StorageManager.class));
@@ -105,14 +109,16 @@
             MtpManager mtpManager,
             ContentResolver resolver,
             MtpDatabase database,
-            StorageManager storageManager) {
+            StorageManager storageManager,
+            ServiceIntentSender intentSender) {
         mResources = resources;
         mMtpManager = mtpManager;
         mResolver = resolver;
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
         mDatabase = database;
-        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
+        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
         mAppFuse = new AppFuse(TAG, new AppFuseCallback());
+        mIntentSender = intentSender;
         // TODO: Mount AppFuse on demands.
         try {
             mAppFuse.mount(storageManager);
@@ -129,7 +135,7 @@
         if (projection == null) {
             projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
         }
-        final Cursor cursor = mDatabase.queryRoots(projection);
+        final Cursor cursor = mDatabase.queryRoots(mResources, projection);
         cursor.setNotificationUri(
                 mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
         return cursor;
@@ -147,11 +153,15 @@
     @Override
     public Cursor queryChildDocuments(String parentDocumentId,
             String[] projection, String sortOrder) throws FileNotFoundException {
+        if (DEBUG) {
+            Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
+        }
         if (projection == null) {
             projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
         }
         Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
         try {
+            openDevice(parentIdentifier.mDeviceId);
             if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
                 final Identifier singleStorageIdentifier =
                         mDatabase.getSingleStorageIdentifier(parentDocumentId);
@@ -176,11 +186,12 @@
                     throws FileNotFoundException {
         final Identifier identifier = mDatabase.createIdentifier(documentId);
         try {
+            openDevice(identifier.mDeviceId);
             switch (mode) {
                 case "r":
                     final long fileSize = getFileSize(documentId);
-                    // MTP getPartialObject operation does not support files that are larger than 4GB.
-                    // Fallback to non-seekable file descriptor.
+                    // MTP getPartialObject operation does not support files that are larger than
+                    // 4GB. Fallback to non-seekable file descriptor.
                     // TODO: Use getPartialObject64 for MTP devices that support Android vendor
                     // extension.
                     if (fileSize <= 0xffffffffl) {
@@ -213,6 +224,7 @@
             CancellationSignal signal) throws FileNotFoundException {
         final Identifier identifier = mDatabase.createIdentifier(documentId);
         try {
+            openDevice(identifier.mDeviceId);
             return new AssetFileDescriptor(
                     getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
                     0,  // Start offset.
@@ -227,6 +239,7 @@
     public void deleteDocument(String documentId) throws FileNotFoundException {
         try {
             final Identifier identifier = mDatabase.createIdentifier(documentId);
+            openDevice(identifier.mDeviceId);
             final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
             mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
             mDatabase.deleteDocument(documentId);
@@ -259,6 +272,7 @@
             throws FileNotFoundException {
         try {
             final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
+            openDevice(parentId.mDeviceId);
             final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
             pipe[0].close();  // 0 bytes for a new document.
             final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
@@ -286,11 +300,22 @@
 
     void openDevice(int deviceId) throws IOException {
         synchronized (mDeviceListLock) {
+            if (mDeviceToolkits.containsKey(deviceId)) {
+                return;
+            }
+            if (DEBUG) {
+                Log.d(TAG, "Open device " + deviceId);
+            }
             mMtpManager.openDevice(deviceId);
             mDeviceToolkits.put(
                     deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
+            mIntentSender.sendUpdateNotificationIntent();
+            try {
+                mRootScanner.resume().await();
+            } catch (InterruptedException error) {
+                Log.e(TAG, "openDevice", error);
+            }
         }
-        mRootScanner.resume();
     }
 
     void closeDevice(int deviceId) throws IOException, InterruptedException {
@@ -298,6 +323,7 @@
             closeDeviceInternal(deviceId);
         }
         mRootScanner.resume();
+        mIntentSender.sendUpdateNotificationIntent();
     }
 
     int[] getOpenedDeviceIds() {
@@ -318,6 +344,13 @@
     }
 
     /**
+     * Resumes root scanner to handle the update of device list.
+     */
+    void resumeRootScanner() {
+        mRootScanner.resume();
+    }
+
+    /**
      * Finalize the content provider for unit tests.
      */
     @Override
@@ -356,6 +389,12 @@
 
     private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
         // TODO: Flush the device before closing (if not closed externally).
+        if (!mDeviceToolkits.containsKey(deviceId)) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Close device " + deviceId);
+        }
         getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
         mDeviceToolkits.remove(deviceId);
         mMtpManager.closeDevice(deviceId);
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
index 5bede86..9c4952b 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
@@ -19,13 +19,12 @@
 import android.app.Notification;
 import android.app.Service;
 import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
-import android.hardware.usb.UsbDevice;
 import android.os.IBinder;
 import android.util.Log;
 
-import com.android.internal.util.Preconditions;
-
 import java.io.IOException;
 
 /**
@@ -36,6 +35,7 @@
 public class MtpDocumentsService extends Service {
     static final String ACTION_OPEN_DEVICE = "com.android.mtp.OPEN_DEVICE";
     static final String ACTION_CLOSE_DEVICE = "com.android.mtp.CLOSE_DEVICE";
+    static final String ACTION_UPDATE_NOTIFICATION = "com.android.mtp.UPDATE_NOTIFICATION";
     static final String EXTRA_DEVICE = "device";
 
     NotificationManager mNotificationManager;
@@ -55,32 +55,10 @@
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         // If intent is null, the service was restarted.
-        if (intent != null) {
-            final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
-            final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
-            try {
-                Preconditions.checkNotNull(device);
-                switch (intent.getAction()) {
-                    case ACTION_OPEN_DEVICE:
-                        provider.openDevice(device.getDeviceId());
-                        break;
-
-                    case ACTION_CLOSE_DEVICE:
-                        mNotificationManager.cancel(device.getDeviceId());
-                        provider.closeDevice(device.getDeviceId());
-                        break;
-
-                    default:
-                        throw new IllegalArgumentException("Received unknown intent action.");
-                }
-            } catch (IOException | InterruptedException | IllegalArgumentException error) {
-                logErrorMessage(error);
-            }
-        } else {
-            // TODO: Fetch devices again.
+        if (intent == null || ACTION_UPDATE_NOTIFICATION.equals(intent.getAction())) {
+            return updateForegroundState() ? START_STICKY : START_NOT_STICKY;
         }
-
-        return updateForegroundState() ? START_STICKY : START_NOT_STICKY;
+        return START_NOT_STICKY;
     }
 
     /**
@@ -92,6 +70,7 @@
         final int[] deviceIds = provider.getOpenedDeviceIds();
         int notificationId = 0;
         Notification notification = null;
+        // TODO: Hide notification if the device has already been removed.
         for (final int deviceId : deviceIds) {
             try {
                 final String title = getResources().getString(
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index efe5ff1..5519efd 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -273,9 +273,7 @@
             final MtpRoot[] results = new MtpRoot[storageIds.length];
             for (int i = 0; i < storageIds.length; i++) {
                 results[i] = new MtpRoot(
-                        device.getDeviceId(),
-                        device.getDeviceInfo().getModel(),
-                        device.getStorageInfo(storageIds[i]));
+                        device.getDeviceId(), device.getStorageInfo(storageIds[i]));
             }
             return results;
         }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java
index ec338c3..8530aaf 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java
@@ -16,7 +16,6 @@
 
 package com.android.mtp;
 
-import android.content.res.Resources;
 import android.mtp.MtpStorageInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -24,7 +23,6 @@
 class MtpRoot {
     final int mDeviceId;
     final int mStorageId;
-    final String mDeviceModelName;
     final String mDescription;
     final long mFreeSpace;
     final long mMaxCapacity;
@@ -33,24 +31,21 @@
     @VisibleForTesting
     MtpRoot(int deviceId,
             int storageId,
-            String deviceName,
             String description,
             long freeSpace,
             long maxCapacity,
             String volumeIdentifier) {
         mDeviceId = deviceId;
         mStorageId = storageId;
-        mDeviceModelName = deviceName;
         mDescription = description;
         mFreeSpace = freeSpace;
         mMaxCapacity = maxCapacity;
         mVolumeIdentifier = volumeIdentifier;
     }
 
-    MtpRoot(int deviceId, String deviceModelName, MtpStorageInfo storageInfo) {
+    MtpRoot(int deviceId, MtpStorageInfo storageInfo) {
         mDeviceId = deviceId;
         mStorageId = storageInfo.getStorageId();
-        mDeviceModelName = deviceModelName;
         mDescription = storageInfo.getDescription();
         mFreeSpace = storageInfo.getFreeSpace();
         mMaxCapacity = storageInfo.getMaxCapacity();
@@ -64,7 +59,6 @@
         final MtpRoot other = (MtpRoot) object;
         return mDeviceId == other.mDeviceId &&
                 mStorageId == other.mStorageId &&
-                mDeviceModelName.equals(other.mDeviceModelName) &&
                 mDescription.equals(other.mDescription) &&
                 mFreeSpace == other.mFreeSpace &&
                 mMaxCapacity == other.mMaxCapacity &&
@@ -73,19 +67,12 @@
 
     @Override
     public int hashCode() {
-        return mDeviceId ^ mStorageId ^ mDeviceModelName.hashCode() ^ mDescription.hashCode() ^
+        return mDeviceId ^ mStorageId ^ mDescription.hashCode() ^
                 ((int) mFreeSpace) ^ ((int) mMaxCapacity) ^ mVolumeIdentifier.hashCode();
     }
-    
+
     @Override
     public String toString() {
-        return "MtpRoot{Name: " + mDeviceModelName + " " + mDescription + "}";
-    }
-    
-    String getRootName(Resources resources) {
-        return String.format(
-                resources.getString(R.string.root_name),
-                mDeviceModelName,
-                mDescription);
+        return "MtpRoot{Name: " + mDescription + "}";
     }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index 15b8ef3..a4c3cf4 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -1,12 +1,28 @@
+/*
+ * Copyright (C) 2016 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.mtp;
 
 import android.content.ContentResolver;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Process;
 import android.provider.DocumentsContract;
 import android.util.Log;
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.FutureTask;
@@ -35,7 +51,6 @@
     private final static long AWAIT_TERMINATION_TIMEOUT = 2000;
 
     final ContentResolver mResolver;
-    final Resources mResources;
     final MtpManager mManager;
     final MtpDatabase mDatabase;
 
@@ -44,11 +59,9 @@
 
     RootScanner(
             ContentResolver resolver,
-            Resources resources,
             MtpManager manager,
             MtpDatabase database) {
         mResolver = resolver;
-        mResources = resources;
         mManager = manager;
         mDatabase = database;
     }
@@ -64,9 +77,8 @@
 
     /**
      * Starts to check new changes right away.
-     * If the background thread has already gone, it restarts another background thread.
      */
-    synchronized void resume() {
+    synchronized CountDownLatch resume() {
         if (mExecutor == null) {
             // Only single thread updates the database.
             mExecutor = Executors.newSingleThreadExecutor();
@@ -75,8 +87,10 @@
             // Cancel previous task.
             mCurrentTask.cancel(true);
         }
-        mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null);
+        final UpdateRootsRunnable runnable = new UpdateRootsRunnable();
+        mCurrentTask = new FutureTask<Void>(runnable, null);
         mExecutor.submit(mCurrentTask);
+        return runnable.mFirstScanCompleted;
     }
 
     /**
@@ -98,6 +112,8 @@
      * Runnable to scan roots and update the database information.
      */
     private final class UpdateRootsRunnable implements Runnable {
+        final CountDownLatch mFirstScanCompleted = new CountDownLatch(1);
+
         @Override
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -124,8 +140,7 @@
                         continue;
                     }
                     mDatabase.getMapper().startAddingDocuments(documentId);
-                    if (mDatabase.getMapper().putStorageDocuments(
-                            documentId, mResources, device.roots)) {
+                    if (mDatabase.getMapper().putStorageDocuments(documentId, device.roots)) {
                         changed = true;
                     }
                     if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
@@ -136,6 +151,7 @@
                 if (changed) {
                     notifyChange();
                 }
+                mFirstScanCompleted.countDown();
                 pollingCount++;
                 try {
                     // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java b/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java
new file mode 100644
index 0000000..a1bb2c1
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 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.mtp;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Sends intent to MtpDocumentsService.
+ */
+class ServiceIntentSender {
+    private Context mContext;
+
+    ServiceIntentSender(Context context) {
+        mContext = context;
+    }
+
+    void sendUpdateNotificationIntent() {
+        final Intent intent = new Intent(MtpDocumentsService.ACTION_UPDATE_NOTIFICATION);
+        intent.setComponent(new ComponentName(mContext, MtpDocumentsService.class));
+        mContext.startService(intent);
+    }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java b/packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java
index 0ac130e..0489ea8 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java
@@ -21,7 +21,9 @@
 import android.content.Intent;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbManager;
-import android.net.Uri;
+import android.util.Log;
+
+import java.io.IOException;
 
 public class UsbIntentReceiver extends BroadcastReceiver {
     @Override
@@ -29,17 +31,15 @@
         final UsbDevice device = intent.getExtras().getParcelable(UsbManager.EXTRA_DEVICE);
         switch (intent.getAction()) {
             case UsbManager.ACTION_USB_DEVICE_ATTACHED:
-                startService(context, MtpDocumentsService.ACTION_OPEN_DEVICE, device);
+                MtpDocumentsProvider.getInstance().resumeRootScanner();
                 break;
             case UsbManager.ACTION_USB_DEVICE_DETACHED:
-                startService(context, MtpDocumentsService.ACTION_CLOSE_DEVICE, device);
+                try {
+                    MtpDocumentsProvider.getInstance().closeDevice(device.getDeviceId());
+                } catch (IOException | InterruptedException e) {
+                    Log.e(MtpDocumentsProvider.TAG, "Failed to close device", e);
+                }
                 break;
         }
     }
-
-    private void startService(Context context, String action, UsbDevice device) {
-        final Intent intent = new Intent(action, Uri.EMPTY, context, MtpDocumentsService.class);
-        intent.putExtra(MtpDocumentsService.EXTRA_DEVICE, device);
-        context.startService(intent);
-    }
 }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index 5f71606..af1fed4 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -42,8 +42,8 @@
     public void setUp() {
         mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new TestResources(), new MtpRoot[] {
-                new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 0, "Storage", 1000, 1000, "")
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
         mManager = new BlockableTestMtpManager(getContext());
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 4022886..a49dc67 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -81,8 +81,8 @@
         mDatabase.getMapper().stopAddingDocuments(null);
 
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, "")
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 1, "Storage", 1000, 2000, "")
         });
         mDatabase.getMapper().stopAddingDocuments("1");
 
@@ -96,7 +96,7 @@
             assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
             assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
             assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             assertTrue(isNull(cursor, COLUMN_SUMMARY));
             assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
             assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
@@ -109,7 +109,7 @@
         }
 
         {
-            final Cursor cursor = mDatabase.queryRoots(new String [] {
+            final Cursor cursor = mDatabase.queryRoots(resources, new String [] {
                     Root.COLUMN_ROOT_ID,
                     Root.COLUMN_FLAGS,
                     Root.COLUMN_ICON,
@@ -139,10 +139,10 @@
 
     public void testPutStorageDocuments() throws Exception {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
-                new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
-                new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
-                new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 1, "Storage", 1000, 2000, ""),
+                new MtpRoot(0, 2, "Storage", 2000, 4000, ""),
+                new MtpRoot(0, 3, "/@#%&<>Storage", 3000, 6000,"")
         });
 
         {
@@ -155,7 +155,7 @@
             assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
             assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
             assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             assertTrue(isNull(cursor, COLUMN_SUMMARY));
             assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
             assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
@@ -166,11 +166,11 @@
 
             cursor.moveToNext();
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
-            assertEquals("Device /@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("/@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.close();
         }
@@ -264,9 +264,9 @@
         };
 
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
-                new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage A", 1000, 0, ""),
+                new MtpRoot(0, 101, "Storage B", 1001, 0, "")
         });
 
         {
@@ -275,11 +275,11 @@
             cursor.moveToNext();
             assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(101, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
@@ -291,18 +291,18 @@
             cursor.moveToNext();
             assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
-                new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
-                new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 200, "Storage A", 2000, 0, ""),
+                new MtpRoot(0, 202, "Storage C", 2002, 0, "")
         });
 
         {
@@ -311,15 +311,15 @@
             cursor.moveToNext();
             assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
             assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
@@ -331,11 +331,11 @@
             cursor.moveToNext();
             assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
             assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
     }
@@ -432,11 +432,11 @@
 
         mDatabase.getMapper().startAddingDocuments("1");
         mDatabase.getMapper().startAddingDocuments("2");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device A", "Storage", 0, 0, "")
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage", 0, 0, "")
         });
-        mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] {
-                new MtpRoot(1, 100, "Device B", "Storage", 0, 0, "")
+        mDatabase.getMapper().putStorageDocuments("2", new MtpRoot[] {
+                new MtpRoot(1, 100, "Storage", 0, 0, "")
         });
 
         {
@@ -445,16 +445,16 @@
             cursor.moveToNext();
             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device A Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
             assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device B Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
         {
-            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(resources, rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
@@ -469,11 +469,11 @@
 
         mDatabase.getMapper().startAddingDocuments("1");
         mDatabase.getMapper().startAddingDocuments("2");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 200, "Storage", 2000, 0, "")
         });
-        mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] {
-                new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
+        mDatabase.getMapper().putStorageDocuments("2", new MtpRoot[] {
+                new MtpRoot(1, 300, "Storage", 3000, 0, "")
         });
         mDatabase.getMapper().stopAddingDocuments("1");
         mDatabase.getMapper().stopAddingDocuments("2");
@@ -482,18 +482,18 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
         {
-            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(resources, rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
@@ -562,24 +562,24 @@
 
         mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putDeviceDocument(
-                new MtpDeviceRecord(0, "Device",  false,  new MtpRoot[0], null, null));
+                new MtpDeviceRecord(0, "Device", false,  new MtpRoot[0], null, null));
         mDatabase.getMapper().stopAddingDocuments(null);
 
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage", 0, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 200, "Storage", 2000, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 300, "Storage", 3000, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("1");
 
@@ -589,11 +589,11 @@
             cursor.moveToNext();
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
         {
-            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(resources, rootColumns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
@@ -610,15 +610,15 @@
         };
 
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage", 0, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
-                new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
-                new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 200, "Storage", 2000, 0, ""),
+                new MtpRoot(0, 201, "Storage", 2001, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
 
@@ -628,11 +628,11 @@
             cursor.moveToNext();
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(201, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
     }
@@ -646,14 +646,14 @@
         // The client code should be able to replace existing rows with new information.
         // Add one.
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("1");
         // Replace it.
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage B", 1000, 1000, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("1");
         {
@@ -667,7 +667,7 @@
             cursor.moveToNext();
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
         {
@@ -676,7 +676,7 @@
                     Root.COLUMN_TITLE,
                     Root.COLUMN_AVAILABLE_BYTES
             };
-            final Cursor cursor = mDatabase.queryRoots(columns);
+            final Cursor cursor = mDatabase.queryRoots(resources, columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
@@ -695,21 +695,21 @@
         mDatabase.getMapper().stopAddingDocuments(null);
 
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
-        final Cursor oldCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
+        final Cursor oldCursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_ROOT_ID));
         assertEquals(1, oldCursor.getCount());
 
         // Add one.
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""),
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 101, "Storage B", 1000, 1000, ""),
         });
         // Add one more before resolving unmapped documents.
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""),
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 102, "Storage B", 1000, 1000, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("1");
 
@@ -729,15 +729,15 @@
 
     public void testQueryDocuments() {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
 
         final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
         assertEquals(1, cursor.getCount());
         cursor.moveToNext();
-        assertEquals("Device Storage A", getString(cursor, Document.COLUMN_DISPLAY_NAME));
+        assertEquals("Storage A", getString(cursor, Document.COLUMN_DISPLAY_NAME));
         cursor.close();
     }
 
@@ -750,7 +750,7 @@
 
         // It the device does not have storages, it shows a device root.
         {
-            final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE));
+            final Cursor cursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_TITLE));
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("Device", cursor.getString(0));
@@ -758,14 +758,14 @@
         }
 
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, "")
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage A", 0, 0, "")
         });
         mDatabase.getMapper().stopAddingDocuments("1");
 
         // It the device has single storage, it shows a storage root.
         {
-            final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE));
+            final Cursor cursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_TITLE));
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("Device Storage A", cursor.getString(0));
@@ -773,15 +773,15 @@
         }
 
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
-                new MtpRoot(0, 101, "Device", "Storage B", 0, 0, "")
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
+                new MtpRoot(0, 101, "Storage B", 0, 0, "")
         });
         mDatabase.getMapper().stopAddingDocuments("1");
 
         // It the device has multiple storages, it shows a device root.
         {
-            final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE));
+            final Cursor cursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_TITLE));
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("Device", cursor.getString(0));
@@ -791,8 +791,8 @@
 
     public void testGetParentId() throws FileNotFoundException {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
 
@@ -810,8 +810,8 @@
 
     public void testDeleteDocument() {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
 
@@ -854,8 +854,8 @@
 
     public void testPutNewDocument() {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
 
@@ -911,7 +911,7 @@
                 DocumentsContract.Root.COLUMN_TITLE,
                 DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
         };
-        try (final Cursor cursor = mDatabase.queryRoots(columns)) {
+        try (final Cursor cursor = mDatabase.queryRoots(resources, columns)) {
             assertEquals(1, cursor.getCount());
             assertTrue(cursor.moveToNext());
             assertEquals(1, cursor.getLong(0));
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 3606612..5b0f557 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -23,13 +23,11 @@
 import android.os.ParcelFileDescriptor;
 import android.os.storage.StorageManager;
 import android.provider.DocumentsContract.Root;
-import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 import android.provider.DocumentsContract;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.util.Log;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -64,13 +62,12 @@
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         mMtpManager.addValidDevice(new MtpDeviceRecord(
                 0,
-                "Device",
+                "Device A",
                 false /* unopened */,
                 new MtpRoot[] {
                     new MtpRoot(
                             0 /* deviceId */,
                             1 /* storageId */,
-                            "Device A" /* device model name */,
                             "Storage A" /* volume description */,
                             1024 /* free space */,
                             2048 /* total space */,
@@ -79,11 +76,14 @@
                 null,
                 null));
 
-        mProvider.openDevice(0);
+        mProvider.resumeRootScanner();
         mResolver.waitForNotification(ROOTS_URI, 1);
 
-        mProvider.closeDevice(0);
+        mProvider.openDevice(0);
         mResolver.waitForNotification(ROOTS_URI, 2);
+
+        mProvider.closeDevice(0);
+        mResolver.waitForNotification(ROOTS_URI, 3);
     }
 
     public void testOpenAndCloseErrorDevice() throws Exception {
@@ -94,24 +94,17 @@
         } catch (Throwable error) {
             assertTrue(error instanceof IOException);
         }
-
-        try {
-            mProvider.closeDevice(1);
-            fail();
-        } catch (Throwable error) {
-            assertTrue(error instanceof IOException);
-        }
+        assertEquals(0, mProvider.getOpenedDeviceIds().length);
 
         // Check if the following notification is the first one or not.
         mMtpManager.addValidDevice(new MtpDeviceRecord(
                 0,
-                "Device",
+                "Device A",
                 false /* unopened */,
                 new MtpRoot[] {
                     new MtpRoot(
                             0 /* deviceId */,
                             1 /* storageId */,
-                            "Device A" /* device model name */,
                             "Storage A" /* volume description */,
                             1024 /* free space */,
                             2048 /* total space */,
@@ -119,21 +112,66 @@
                 },
                 null,
                 null));
-        mProvider.openDevice(0);
+        mProvider.resumeRootScanner();
         mResolver.waitForNotification(ROOTS_URI, 1);
+        mProvider.openDevice(0);
+        mResolver.waitForNotification(ROOTS_URI, 2);
+    }
+
+    public void testOpenDeviceOnDemand() throws Exception {
+        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        mMtpManager.addValidDevice(new MtpDeviceRecord(
+                0,
+                "Device A",
+                false /* unopened */,
+                new MtpRoot[] {
+                    new MtpRoot(
+                            0 /* deviceId */,
+                            1 /* storageId */,
+                            "Storage A" /* volume description */,
+                            1024 /* free space */,
+                            2048 /* total space */,
+                            "" /* no volume identifier */)
+                },
+                null,
+                null));
+        mMtpManager.setObjectHandles(0, 1, -1, new int[0]);
+        mProvider.resumeRootScanner();
+        mResolver.waitForNotification(ROOTS_URI, 1);
+        final String[] columns = new String[] {
+                DocumentsContract.Root.COLUMN_TITLE,
+                DocumentsContract.Root.COLUMN_DOCUMENT_ID
+        };
+        try (final Cursor cursor = mProvider.queryRoots(columns)) {
+            assertEquals(1, cursor.getCount());
+            assertTrue(cursor.moveToNext());
+            assertEquals("Device A", cursor.getString(0));
+            assertEquals(1, cursor.getLong(1));
+        }
+        {
+            final int [] openedDevice = mProvider.getOpenedDeviceIds();
+            assertEquals(0, openedDevice.length);
+        }
+        // Device is opened automatically when querying its children.
+        try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {}
+
+        {
+            final int [] openedDevice = mProvider.getOpenedDeviceIds();
+            assertEquals(1, openedDevice.length);
+            assertEquals(0, openedDevice[0]);
+        }
     }
 
     public void testQueryRoots() throws Exception {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         mMtpManager.addValidDevice(new MtpDeviceRecord(
                 0,
-                "Device",
+                "Device A",
                 false /* unopened */,
                 new MtpRoot[] {
                         new MtpRoot(
                                 0 /* deviceId */,
                                 1 /* storageId */,
-                                "Device A" /* device model name */,
                                 "Storage A" /* volume description */,
                                 1024 /* free space */,
                                 2048 /* total space */,
@@ -143,13 +181,12 @@
                 null));
         mMtpManager.addValidDevice(new MtpDeviceRecord(
                 1,
-                "Device",
+                "Device B",
                 false /* unopened */,
                 new MtpRoot[] {
                     new MtpRoot(
                             1 /* deviceId */,
                             1 /* storageId */,
-                            "Device B" /* device model name */,
                             "Storage B" /* volume description */,
                             2048 /* free space */,
                             4096 /* total space */,
@@ -194,13 +231,12 @@
                 0, "Device A", false /* unopened */, new MtpRoot[0], null, null));
         mMtpManager.addValidDevice(new MtpDeviceRecord(
                 1,
-                "Device",
+                "Device B",
                 false /* unopened */,
                 new MtpRoot[] {
                     new MtpRoot(
                             1 /* deviceId */,
                             1 /* storageId */,
-                            "Device B" /* device model name */,
                             "Storage B" /* volume description */,
                             2048 /* free space */,
                             4096 /* total space */,
@@ -210,9 +246,13 @@
                 null));
         {
             mProvider.openDevice(0);
-            mProvider.openDevice(1);
+            mProvider.resumeRootScanner();
             mResolver.waitForNotification(ROOTS_URI, 1);
 
+            mProvider.openDevice(1);
+            mProvider.resumeRootScanner();
+            mResolver.waitForNotification(ROOTS_URI, 2);
+
             final Cursor cursor = mProvider.queryRoots(null);
             assertEquals(2, cursor.getCount());
 
@@ -236,7 +276,7 @@
 
     public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
-        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
         setupDocuments(
                 0,
                 0,
@@ -273,7 +313,7 @@
     public void testQueryDocument_directory()
             throws IOException, InterruptedException, TimeoutException {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
-        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
         setupDocuments(
                 0,
                 0,
@@ -312,7 +352,6 @@
                 new MtpRoot(
                         0 /* deviceId */,
                         1 /* storageId */,
-                        "Device A" /* device model name */,
                         "Storage A" /* volume description */,
                         1024 /* free space */,
                         4096 /* total space */,
@@ -324,7 +363,7 @@
         cursor.moveToNext();
         assertEquals("2", cursor.getString(0));
         assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
-        assertEquals("Device A Storage A", cursor.getString(2));
+        assertEquals("Storage A", cursor.getString(2));
         assertTrue(cursor.isNull(3));
         assertEquals(0, cursor.getInt(4));
         assertEquals(3072, cursor.getInt(5));
@@ -332,7 +371,7 @@
 
     public void testQueryChildDocuments() throws Exception {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
-        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
         setupDocuments(
                 0,
                 0,
@@ -375,7 +414,7 @@
 
     public void testQueryChildDocuments_documentError() throws Exception {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
-        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
         mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
         try {
             mProvider.queryChildDocuments("1", null, null);
@@ -388,7 +427,7 @@
     public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         setupRoots(0, new MtpRoot[] {
-                new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+                new MtpRoot(0, 0, "Storage", 0, 0, "")
         });
         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
                 new MtpObjectInfo.Builder()
@@ -408,7 +447,7 @@
             throws IOException, InterruptedException, TimeoutException {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         setupRoots(0, new MtpRoot[] {
-                new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+                new MtpRoot(0, 0, "Storage", 0, 0, "")
         });
         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
                 new MtpObjectInfo.Builder()
@@ -431,7 +470,7 @@
     public void testOpenDocument() throws Exception {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         setupRoots(0, new MtpRoot[] {
-                new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+                new MtpRoot(0, 0, "Storage", 0, 0, "")
         });
         final byte[] bytes = "Hello world".getBytes();
         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
@@ -469,7 +508,7 @@
         };
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         setupRoots(0, new MtpRoot[] {
-                new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+                new MtpRoot(0, 0, "Storage", 0, 0, "")
         });
         final byte[] bytes = "Hello world".getBytes();
         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
@@ -492,7 +531,12 @@
         mProvider = new MtpDocumentsProvider();
         final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
         assertTrue(mProvider.onCreateForTesting(
-                mResources, mMtpManager, mResolver, mDatabase, storageManager));
+                mResources,
+                mMtpManager,
+                mResolver,
+                mDatabase,
+                storageManager,
+                new TestServiceIntentSender()));
     }
 
     private String[] getStrings(Cursor cursor) {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
index eb80e3b..b23038b 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
@@ -27,4 +27,9 @@
         }
         throw new NotFoundException();
     }
+
+    @Override
+    public String getString(int id, Object... formatArgs) throws NotFoundException {
+        return String.format(getString(id), formatArgs);
+    }
 }
diff --git a/wifi/java/android/net/wifi/ScanInfo.aidl b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java
similarity index 61%
copy from wifi/java/android/net/wifi/ScanInfo.aidl
copy to packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java
index 18ae508..d4a4a48 100644
--- a/wifi/java/android/net/wifi/ScanInfo.aidl
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2015, The Android Open Source Project
+/*
+ * Copyright (C) 2016 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
+ *      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,
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
-package android.net.wifi;
+package com.android.mtp;
 
-parcelable ScanInfo;
+class TestServiceIntentSender extends ServiceIntentSender {
+    TestServiceIntentSender() {
+        super(null);
+    }
+
+    @Override
+    void sendUpdateNotificationIntent() {}
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index a1ea658..d0cf635 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -265,7 +265,14 @@
                 new Runnable() {
             @Override
             public void run() {
-                onConnectedToPrintSpooler(adapter);
+                if (isFinishing()) {
+                    // onPause might have not been able to cancel the job, see PrintActivity#onPause
+                    // To be sure, cancel the job again. Double canceling does no harm.
+                    mSpoolerProvider.getSpooler().setPrintJobState(mPrintJob.getId(),
+                            PrintJobInfo.STATE_CANCELED, null);
+                } else {
+                    onConnectedToPrintSpooler(adapter);
+                }
             }
         });
     }
@@ -353,7 +360,9 @@
 
         if (mState == STATE_INITIALIZING) {
             if (isFinishing()) {
-                spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
+                if (spooler != null) {
+                    spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
+                }
             }
             super.onPause();
             return;
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d3c8416..38d8907 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -515,9 +515,9 @@
     <string name="pointer_location_summary">Screen overlay showing current touch data</string>
 
     <!-- UI debug setting: show touches? [CHAR LIMIT=25] -->
-    <string name="show_touches">Show touches</string>
+    <string name="show_touches">Show taps</string>
     <!-- UI debug setting: show touches location summary [CHAR LIMIT=50] -->
-    <string name="show_touches_summary">Show visual feedback for touches</string>
+    <string name="show_touches_summary">Show visual feedback for taps</string>
 
     <!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
     <string name="show_screen_updates">Show surface updates</string>
@@ -634,7 +634,7 @@
     <!-- Summary text of the "local backup password" setting when the user has not supplied a password -->
     <string name="local_backup_password_summary_none">Desktop full backups aren\u2019t currently protected</string>
     <!-- Summary text of the "local backup password" setting when the user has already supplied a password -->
-    <string name="local_backup_password_summary_change">Touch to change or remove the password for desktop full backups</string>
+    <string name="local_backup_password_summary_change">Tap to change or remove the password for desktop full backups</string>
 
     <!-- Toast message shown when the user successfully sets a new local backup password [CHAR LIMIT=80] -->
     <string name="local_backup_password_toast_success">New backup password set</string>
@@ -660,9 +660,9 @@
     <!-- Settings item title for inactive apps [CHAR LIMIT=35] -->
     <string name="inactive_apps_title">Inactive apps</string>
     <!-- Settings item summary for inactive app [CHAR LIMIT=100] -->
-    <string name="inactive_app_inactive_summary">Inactive. Touch to toggle.</string>
+    <string name="inactive_app_inactive_summary">Inactive. Tap to toggle.</string>
     <!-- Settings item summary for active app [CHAR LIMIT=100] -->
-    <string name="inactive_app_active_summary">Active. Touch to toggle.</string>
+    <string name="inactive_app_active_summary">Active. Tap to toggle.</string>
 
     <!-- Services settings screen, setting option name for the user to go to the screen to view running services -->
     <string name="runningservices_settings_title">Running services</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
index 78c530c..f885110 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -155,7 +155,7 @@
             for (UserInfo userInfo : um.getProfiles(userId)) {
                 final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
                 if (admins == null) {
-                    return null;
+                    continue;
                 }
                 final boolean isSeparateProfileChallengeEnabled =
                         lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id);
@@ -209,16 +209,7 @@
         IPackageManager ipm = AppGlobals.getPackageManager();
         try {
             if (ipm.getBlockUninstallForUser(packageName, userId)) {
-                DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
-                        Context.DEVICE_POLICY_SERVICE);
-                if (dpm == null) {
-                    return null;
-                }
-                ComponentName admin = dpm.getProfileOwner();
-                if (admin == null) {
-                    admin = dpm.getDeviceOwnerComponentOnCallingUser();
-                }
-                return new EnforcedAdmin(admin, UserHandle.myUserId());
+                return getProfileOrDeviceOwner(context, userId);
             }
         } catch (RemoteException e) {
             // Nothing to do
@@ -238,7 +229,7 @@
         try {
             ApplicationInfo ai = ipm.getApplicationInfo(packageName, 0, userId);
             if (ai != null && ((ai.flags & ApplicationInfo.FLAG_SUSPENDED) != 0)) {
-                return getProfileOrDeviceOwnerOnCallingUser(context);
+                return getProfileOrDeviceOwner(context, userId);
             }
         } catch (RemoteException e) {
             // Nothing to do
@@ -246,6 +237,80 @@
         return null;
     }
 
+    public static EnforcedAdmin checkIfInputMethodDisallowed(Context context,
+            String packageName, int userId) {
+        DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+                Context.DEVICE_POLICY_SERVICE);
+        if (dpm == null) {
+            return null;
+        }
+        EnforcedAdmin admin = getProfileOrDeviceOwner(context, userId);
+        boolean permitted = true;
+        if (admin != null) {
+            permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
+                    packageName, userId);
+        }
+        int managedProfileId = getManagedProfileId(context, userId);
+        EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context, managedProfileId);
+        boolean permittedByProfileAdmin = true;
+        if (profileAdmin != null) {
+            permittedByProfileAdmin = dpm.isInputMethodPermittedByAdmin(profileAdmin.component,
+                    packageName, managedProfileId);
+        }
+        if (!permitted && !permittedByProfileAdmin) {
+            return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+        } else if (!permitted) {
+            return admin;
+        } else if (!permittedByProfileAdmin) {
+            return profileAdmin;
+        }
+        return null;
+    }
+
+    public static EnforcedAdmin checkIfAccessibilityServiceDisallowed(Context context,
+            String packageName, int userId) {
+        DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+                Context.DEVICE_POLICY_SERVICE);
+        if (dpm == null) {
+            return null;
+        }
+        EnforcedAdmin admin = getProfileOrDeviceOwner(context, userId);
+        boolean permitted = true;
+        if (admin != null) {
+            permitted = dpm.isAccessibilityServicePermittedByAdmin(admin.component,
+                    packageName, userId);
+        }
+        int managedProfileId = getManagedProfileId(context, userId);
+        EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context, managedProfileId);
+        boolean permittedByProfileAdmin = true;
+        if (profileAdmin != null) {
+            permittedByProfileAdmin = dpm.isAccessibilityServicePermittedByAdmin(
+                    profileAdmin.component, packageName, managedProfileId);
+        }
+        if (!permitted && !permittedByProfileAdmin) {
+            return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+        } else if (!permitted) {
+            return admin;
+        } else if (!permittedByProfileAdmin) {
+            return profileAdmin;
+        }
+        return null;
+    }
+
+    private static int getManagedProfileId(Context context, int userId) {
+        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        List<UserInfo> userProfiles = um.getProfiles(userId);
+        for (UserInfo uInfo : userProfiles) {
+            if (uInfo.id == userId) {
+                continue;
+            }
+            if (uInfo.isManagedProfile()) {
+                return uInfo.id;
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
     /**
      * Check if account management for a specific type of account is disabled by admin.
      * Only a profile or device owner can disable account management. So, we check if account
@@ -255,7 +320,7 @@
      * or {@code null} if the account management is not disabled.
      */
     public static EnforcedAdmin checkIfAccountManagementDisabled(Context context,
-            String accountType) {
+            String accountType, int userId) {
         if (accountType == null) {
             return null;
         }
@@ -265,7 +330,7 @@
             return null;
         }
         boolean isAccountTypeDisabled = false;
-        String[] disabledTypes = dpm.getAccountTypesWithManagementDisabled();
+        String[] disabledTypes = dpm.getAccountTypesWithManagementDisabledAsUser(userId);
         for (String type : disabledTypes) {
             if (accountType.equals(type)) {
                 isAccountTypeDisabled = true;
@@ -275,7 +340,7 @@
         if (!isAccountTypeDisabled) {
             return null;
         }
-        return getProfileOrDeviceOwnerOnCallingUser(context);
+        return getProfileOrDeviceOwner(context, userId);
     }
 
     /**
@@ -296,7 +361,7 @@
     }
 
     /**
-     * Checks if an admin has enforced minimum password quality requirements on the device.
+     * Checks if an admin has enforced minimum password quality requirements on the given user.
      *
      * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
      * or {@code null} if no quality requirements are set. If the requirements are set by
@@ -304,35 +369,73 @@
      * {@link UserHandle#USER_NULL}.
      *
      */
-    public static EnforcedAdmin checkIfPasswordQualityIsSet(Context context) {
+    public static EnforcedAdmin checkIfPasswordQualityIsSet(Context context, int userId) {
         final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
         if (dpm == null) {
             return null;
         }
-        boolean isDisabledByMultipleAdmins = false;
-        ComponentName adminComponent = null;
-        List<ComponentName> admins = dpm.getActiveAdmins();
-        int quality;
-        if (admins != null) {
+
+        LockPatternUtils lockPatternUtils = new LockPatternUtils(context);
+        EnforcedAdmin enforcedAdmin = null;
+        if (lockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
+            // userId is managed profile and has a separate challenge, only consider
+            // the admins in that user.
+            final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userId);
+            if (admins == null) {
+                return null;
+            }
             for (ComponentName admin : admins) {
-                quality = dpm.getPasswordQuality(admin);
-                if (quality >= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
-                    if (adminComponent == null) {
-                        adminComponent = admin;
+                if (dpm.getPasswordQuality(admin, userId)
+                        > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+                    if (enforcedAdmin == null) {
+                        enforcedAdmin = new EnforcedAdmin(admin, userId);
                     } else {
-                        isDisabledByMultipleAdmins = true;
-                        break;
+                        return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
                     }
                 }
             }
-        }
-        EnforcedAdmin enforcedAdmin = null;
-        if (adminComponent != null) {
-            if (!isDisabledByMultipleAdmins) {
-                enforcedAdmin = new EnforcedAdmin(adminComponent, UserHandle.myUserId());
-            } else {
-                enforcedAdmin = new EnforcedAdmin();
+        } else {
+            // Return all admins for this user and the profiles that are visible from this
+            // user that do not use a separate work challenge.
+            final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+            for (UserInfo userInfo : um.getProfiles(userId)) {
+                final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
+                if (admins == null) {
+                    continue;
+                }
+                final boolean isSeparateProfileChallengeEnabled =
+                        lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id);
+                for (ComponentName admin : admins) {
+                    if (!isSeparateProfileChallengeEnabled) {
+                        if (dpm.getPasswordQuality(admin, userInfo.id)
+                                > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+                            if (enforcedAdmin == null) {
+                                enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
+                            } else {
+                                return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+                            }
+                            // This same admins could have set policies both on the managed profile
+                            // and on the parent. So, if the admin has set the policy on the
+                            // managed profile here, we don't need to further check if that admin
+                            // has set policy on the parent admin.
+                            continue;
+                        }
+                    }
+                    if (userInfo.isManagedProfile()) {
+                        // If userInfo.id is a managed profile, we also need to look at
+                        // the policies set on the parent.
+                        DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo);
+                        if (parentDpm.getPasswordQuality(admin, userInfo.id)
+                                > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+                            if (enforcedAdmin == null) {
+                                enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
+                            } else {
+                                return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+                            }
+                        }
+                    }
+                }
             }
         }
         return enforcedAdmin;
@@ -352,7 +455,8 @@
         EnforcedAdmin enforcedAdmin = null;
         final int userId = UserHandle.myUserId();
         if (lockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
-            // If the user has a separate challenge, only consider the admins in that user.
+            // userId is managed profile and has a separate challenge, only consider
+            // the admins in that user.
             final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userId);
             if (admins == null) {
                 return null;
@@ -373,7 +477,7 @@
             for (UserInfo userInfo : um.getProfiles(userId)) {
                 final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
                 if (admins == null) {
-                    return null;
+                    continue;
                 }
                 final boolean isSeparateProfileChallengeEnabled =
                         lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id);
@@ -410,19 +514,24 @@
         return enforcedAdmin;
     }
 
-    public static EnforcedAdmin getProfileOrDeviceOwnerOnCallingUser(Context context) {
+    public static EnforcedAdmin getProfileOrDeviceOwner(Context context, int userId) {
+        if (userId == UserHandle.USER_NULL) {
+            return null;
+        }
         final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
         if (dpm == null) {
             return null;
         }
-        ComponentName adminComponent = dpm.getDeviceOwnerComponentOnCallingUser();
+        ComponentName adminComponent = dpm.getProfileOwnerAsUser(userId);
         if (adminComponent != null) {
-            return new EnforcedAdmin(adminComponent, UserHandle.myUserId());
+            return new EnforcedAdmin(adminComponent, userId);
         }
-        adminComponent = dpm.getProfileOwner();
-        if (adminComponent != null) {
-            return new EnforcedAdmin(adminComponent, UserHandle.myUserId());
+        if (dpm.getDeviceOwnerUserId() == userId) {
+            adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();
+            if (adminComponent != null) {
+                return new EnforcedAdmin(adminComponent, userId);
+            }
         }
         return null;
     }
diff --git a/packages/SystemUI/res/layout/qs_add_tile_layout.xml b/packages/SystemUI/res/layout/qs_add_tile_layout.xml
deleted file mode 100644
index 962b00e..0000000
--- a/packages/SystemUI/res/layout/qs_add_tile_layout.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 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_height="wrap_content"
-    android:layout_width="wrap_content"
-    android:paddingTop="20dp"
-    android:paddingStart="7dp"
-    android:paddingEnd="7dp">
-    <LinearLayout
-        android:layout_height="wrap_content"
-        android:layout_width="80dp"
-        android:orientation="vertical">
-        <ImageView
-            android:id="@+id/tile_icon"
-            android:layout_gravity="center"
-            android:layout_width="@dimen/qs_tile_icon_size"
-            android:layout_height="@dimen/qs_tile_icon_size" />
-        <TextView
-            android:id="@+id/tile_label"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center|bottom"
-            android:paddingTop="10dp"
-            android:gravity="center"
-            android:textSize="@dimen/qs_tile_text_size" />
-    </LinearLayout>
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_customize_layout.xml b/packages/SystemUI/res/layout/qs_customize_divider.xml
similarity index 63%
rename from packages/SystemUI/res/layout/qs_customize_layout.xml
rename to packages/SystemUI/res/layout/qs_customize_divider.xml
index 0b8e02f..71ad85b 100644
--- a/packages/SystemUI/res/layout/qs_customize_layout.xml
+++ b/packages/SystemUI/res/layout/qs_customize_divider.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2015 The Android Open Source Project
+     Copyright (C) 2016 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.
@@ -14,18 +14,16 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.qs.customize.NonPagedTileLayout
+
+<TextView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/tiles_container"
+    android:id="@android:id/title"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <view
-        class="com.android.systemui.qs.PagedTileLayout$TilePage"
-        android:id="@+id/tile_page"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-</com.android.systemui.qs.customize.NonPagedTileLayout>
-
+    android:paddingTop="20dp"
+    android:paddingStart="16dp"
+    android:paddingEnd="8dp"
+    android:paddingBottom="13dp"
+    android:textAppearance="@android:style/TextAppearance.Material.Body2"
+    android:textColor="?android:attr/colorAccent"
+    android:text="@string/drag_to_add_tiles" />
diff --git a/packages/SystemUI/res/layout/qs_customize_panel.xml b/packages/SystemUI/res/layout/qs_customize_panel.xml
index e56431b..73a92d9 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel.xml
@@ -22,85 +22,53 @@
     android:background="@drawable/qs_customizer_background"
     android:gravity="center_horizontal">
 
-    <FrameLayout
+    <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@drawable/notification_header_bg">
+        android:paddingTop="28dp"
+        android:paddingEnd="8dp">
 
-        <LinearLayout
-            android:id="@+id/drag_buttons"
-            android:layout_width="match_parent"
-            android:layout_height="fill_parent"
-            android:orientation="horizontal">
-            <FrameLayout
-                android:layout_width="0dp"
-                android:layout_height="fill_parent"
-                android:layout_weight="1">
-                <com.android.systemui.qs.customize.DropButton
-                    android:id="@+id/info_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center"
-                    android:gravity="center"
-                    android:drawableStart="@drawable/ic_info"
-                    android:drawablePadding="10dp"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textColor="@android:color/white"
-                    android:text="@string/qs_customize_info" />
-            </FrameLayout>
-            <FrameLayout
-                android:layout_width="0dp"
-                android:layout_height="fill_parent"
-                android:layout_weight="1">
-                <com.android.systemui.qs.customize.DropButton
-                    android:id="@+id/remove_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center"
-                    android:gravity="center"
-                    android:drawableStart="@drawable/ic_close_white"
-                    android:drawablePadding="10dp"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textColor="@android:color/white"
-                    android:text="@string/qs_customize_remove" />
-            </FrameLayout>
-        </LinearLayout>
+        <ImageView
+            android:id="@+id/close"
+            android:layout_width="56dp"
+            android:layout_height="56dp"
+            android:padding="16dp"
+            android:src="@drawable/ic_close_white" />
 
-        <Toolbar
-            android:id="@*android:id/action_bar"
-            android:layout_width="match_parent"
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+        <Button
+            android:id="@+id/save"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:navigationContentDescription="@*android:string/action_bar_up_description"
-            android:background="@drawable/notification_header_bg"
-            style="?android:attr/toolbarStyle" />
-    </FrameLayout>
+            android:paddingStart="12dp"
+            android:paddingEnd="12dp"
+            android:background="?android:attr/selectableItemBackground"
+            android:textAppearance="@android:style/TextAppearance.Material.Widget.Button.Inverse"
+            android:textColor="?android:attr/colorAccent"
+            android:text="@string/save" />
 
-    <com.android.systemui.tuner.AutoScrollView
+        <Button
+            android:id="@+id/reset"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingStart="12dp"
+            android:paddingEnd="12dp"
+            android:background="?android:attr/selectableItemBackground"
+            android:textAppearance="@android:style/TextAppearance.Material.Widget.Button.Inverse"
+            android:textColor="?android:attr/colorAccent"
+            android:text="@*android:string/reset" />
+
+    </LinearLayout>
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@android:id/list"
         android:layout_width="@dimen/notification_panel_width"
         android:layout_height="0dp"
-        android:layout_weight="1"
-        android:paddingTop="12dp"
-        android:paddingBottom="8dp"
-        android:elevation="2dp">
-
-        <com.android.systemui.qs.customize.CustomQSPanel
-            android:id="@+id/quick_settings_panel"
-            android:background="#0000"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-    </com.android.systemui.tuner.AutoScrollView>
-
-    <com.android.systemui.qs.customize.FloatingActionButton
-        android:id="@+id/fab"
-        android:clickable="true"
-        android:layout_width="@dimen/fab_size"
-        android:layout_height="@dimen/fab_size"
-        android:layout_gravity="bottom|end"
-        android:layout_marginEnd="@dimen/fab_margin"
-        android:layout_marginBottom="@dimen/fab_margin"
-        android:elevation="@dimen/fab_elevation"
-        android:background="@drawable/fab_background" />
+        android:layout_weight="1" />
 
     <View
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/qs_customize_tile_frame.xml b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml
new file mode 100644
index 0000000..aaa84fd
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 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_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:paddingStart="8dp"
+    android:paddingEnd="8dp"
+    android:paddingBottom="16dp" />
diff --git a/packages/SystemUI/res/layout/qs_detail_header.xml b/packages/SystemUI/res/layout/qs_detail_header.xml
index 5a96dc3..153e35f 100644
--- a/packages/SystemUI/res/layout/qs_detail_header.xml
+++ b/packages/SystemUI/res/layout/qs_detail_header.xml
@@ -18,10 +18,20 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_alignParentBottom="true"
-    android:padding="@dimen/qs_panel_padding"
+    android:paddingEnd="@dimen/qs_panel_padding"
     android:visibility="invisible"
-    android:background="@drawable/btn_borderless_rect" >
+    android:background="@drawable/btn_borderless_rect"
+    android:gravity="center">
+
+    <ImageView
+        android:id="@*android:id/up"
+        android:layout_width="56dp"
+        android:layout_height="56dp"
+        android:layout_marginEnd="16dp"
+        android:padding="16dp"
+        android:clickable="true"
+        android:background="?android:attr/selectableItemBackground"
+        android:src="?android:attr/homeAsUpIndicator" />
 
     <TextView
         android:id="@android:id/title"
@@ -37,4 +47,4 @@
         android:clickable="false"
         android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
 
-</com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file
+</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
index ccdddf7..5bb4f5d 100644
--- a/packages/SystemUI/res/layout/qs_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_detail_item.xml
@@ -28,7 +28,7 @@
         android:id="@android:id/icon"
         android:layout_width="24dp"
         android:layout_height="24dp"
-        android:layout_marginEnd="12dp" />
+        android:layout_marginEnd="20dp" />
 
     <LinearLayout
         android:layout_width="0dp"
diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
index 0bef513..5fde4f6 100644
--- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
@@ -15,6 +15,7 @@
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_height="48dp"
     android:paddingLeft="16dp"
     android:paddingRight="16dp"
     style="@style/BrightnessDialogContainer">
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 6784695..07ac6a5 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -106,7 +106,7 @@
     <LinearLayout
         android:id="@+id/date_time_group"
         android:layout_width="wrap_content"
-        android:layout_height="28dp"
+        android:layout_height="25dp"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
         android:orientation="horizontal">
@@ -115,7 +115,7 @@
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_marginStart="16dp"
-            android:layout_marginTop="2dp"
+            android:layout_marginTop="4dp"
             android:id="@+id/clock" />
 
         <com.android.systemui.statusbar.policy.DateView
@@ -123,7 +123,7 @@
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_marginStart="6dp"
-            android:layout_marginTop="8dp"
+            android:layout_marginTop="4dp"
             android:drawableStart="@drawable/header_dot"
             android:drawablePadding="6dp"
             android:singleLine="true"
@@ -136,7 +136,7 @@
             android:id="@+id/alarm_status"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_marginTop="8dp"
+            android:layout_marginTop="4dp"
             android:drawablePadding="6dp"
             android:drawableStart="@drawable/ic_access_alarms_small"
             android:textColor="#64ffffff"
@@ -152,9 +152,9 @@
         android:background="#0000"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="30dp"
-        android:layout_marginStart="8dp"
-        android:layout_marginEnd="8dp"
+        android:layout_marginTop="25dp"
+        android:layout_marginStart="12dp"
+        android:layout_marginEnd="12dp"
         android:layout_alignParentEnd="true"
         android:clipChildren="false"
         android:clipToPadding="false" />
@@ -164,7 +164,7 @@
         layout="@layout/qs_detail_header"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
+        android:layout_marginTop="28dp"
         />
 
     <com.android.systemui.statusbar.AlphaOptimizedImageView
diff --git a/packages/SystemUI/res/layout/status_bar_no_notifications.xml b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
index dd501d4..6f87184 100644
--- a/packages/SystemUI/res/layout/status_bar_no_notifications.xml
+++ b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
@@ -25,9 +25,9 @@
             android:id="@+id/no_notifications"
             android:layout_width="match_parent"
             android:layout_height="64dp"
-            android:paddingTop="12dp"
+            android:paddingTop="28dp"
             android:gravity="top|center_horizontal"
-            android:textColor="#ffffff"
-            android:textSize="20sp"
+            android:textColor="#e6ffffff"
+            android:textSize="16sp"
             android:text="@string/empty_shade_text"/>
 </com.android.systemui.statusbar.EmptyShadeView>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
index dc7577a..efb273f 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
@@ -19,15 +19,16 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:visibility="gone"
-        android:clipChildren="false"
-        android:clipToPadding="false">
+        android:paddingEnd="8dp"
+        android:visibility="gone">
     <com.android.systemui.statusbar.DismissViewButton
+            style="@android:style/Widget.Material.Button.Borderless"
             android:id="@+id/dismiss_text"
-            android:layout_width="48dp"
-            android:layout_height="48dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:layout_gravity="end"
             android:focusable="true"
-            android:background="@drawable/ripple_drawable"
-            android:contentDescription="@string/accessibility_clear_all"/>
+            android:contentDescription="@string/accessibility_clear_all"
+            android:text="@string/clear_all_notifications_text"
+            android:textAllCaps="true"/>
 </com.android.systemui.statusbar.DismissView>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index e0affa1..43e7bac 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -38,6 +38,4 @@
          while the stack is not focused. -->
     <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
     <item name="recents_layout_unfocused_range_max" format="float" type="integer">1.5</item>
-
-    <integer name="quick_settings_num_columns">4</integer>
 </resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index c75a89f..26a81c8 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -19,8 +19,7 @@
     <!-- thickness (width) of the navigation bar on phones that require it -->
     <dimen name="navigation_bar_size">@*android:dimen/navigation_bar_width</dimen>
 
-    <!-- Standard notification width + gravity -->
-    <dimen name="notification_panel_width">@dimen/standard_notification_panel_width</dimen>
+    <!-- Standard notification gravity -->
     <integer name="notification_panel_layout_gravity">@integer/standard_notification_panel_layout_gravity</integer>
 
     <dimen name="docked_divider_handle_width">2dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 71f92fd..c0652d8 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -93,4 +93,6 @@
 
     <dimen name="navigation_key_width">128dp</dimen>
     <dimen name="navigation_key_padding">25dp</dimen>
+
+    <dimen name="qs_expand_margin">0dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml
new file mode 100644
index 0000000..71e54a1
--- /dev/null
+++ b/packages/SystemUI/res/values-w550dp-land/config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <integer name="quick_settings_num_columns">4</integer>
+</resources>
diff --git a/wifi/java/android/net/wifi/ScanInfo.aidl b/packages/SystemUI/res/values-w550dp-land/dimens.xml
similarity index 65%
copy from wifi/java/android/net/wifi/ScanInfo.aidl
copy to packages/SystemUI/res/values-w550dp-land/dimens.xml
index 18ae508..4160c83 100644
--- a/wifi/java/android/net/wifi/ScanInfo.aidl
+++ b/packages/SystemUI/res/values-w550dp-land/dimens.xml
@@ -1,5 +1,6 @@
-/**
- * Copyright (c) 2015, The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2016, 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.
@@ -12,8 +13,11 @@
  * 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>
+    <!-- Standard notification width + gravity -->
+    <dimen name="notification_panel_width">544dp</dimen>
 
-package android.net.wifi;
-
-parcelable ScanInfo;
+    <dimen name="qs_expand_margin">32dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 32d09e8..e5e5710 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -124,7 +124,7 @@
     <dimen name="close_handle_underlap">32dp</dimen>
 
     <!-- Height of the status bar header bar -->
-    <dimen name="status_bar_header_height">90dp</dimen>
+    <dimen name="status_bar_header_height">80dp</dimen>
 
     <!-- Height of the status bar header bar when expanded -->
     <dimen name="status_bar_header_height_expanded">116dp</dimen>
@@ -186,6 +186,7 @@
     <dimen name="qs_detail_empty_text_size">14sp</dimen>
     <dimen name="qs_data_usage_text_size">14sp</dimen>
     <dimen name="qs_data_usage_usage_text_size">36sp</dimen>
+    <dimen name="qs_expand_margin">0dp</dimen>
 
     <dimen name="segmented_button_spacing">0dp</dimen>
     <dimen name="borderless_button_radius">2dp</dimen>
@@ -606,4 +607,7 @@
 
     <dimen name="docked_divider_handle_width">16dp</dimen>
     <dimen name="docked_divider_handle_height">2dp</dimen>
+
+    <dimen name="battery_height">14.5dp</dimen>
+    <dimen name="battery_width">9.5dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c6c448d0..76752e6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1044,9 +1044,6 @@
     <!-- VolumeUI restoration notification: text -->
     <string name="volumeui_notification_text">Touch to restore the original.</string>
 
-    <!-- Describes the way 2 names are concatenated. An example would be ", " to produce "Peter Muller, Paul Curry". Please also include a space here if it's appropriate in the language and if it's a RTL language include it on the left. The translation should start and end with " to keep the white space if desired [CHAR LIMIT=5] -->
-    <string name="group_summary_concadenation">", "</string>
-
     <!-- Toast shown when user unlocks screen and managed profile activity is in the foreground -->
     <string name="managed_profile_foreground_toast">You\'re using your work profile</string>
 
@@ -1400,4 +1397,7 @@
     <!-- SysUI Tuner: Label for preview area in navigation bar tuner [CHAR LIMIT=NONE] -->
     <string name="preview">Preview</string>
 
+    <!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
+    <string name="drag_to_add_tiles">Drag to add tiles</string>
+
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9931ab9..b078a68 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -241,6 +241,10 @@
         parent="@*android:style/TextAppearance.Material.Notification.Info">
     </style>
 
+    <style name="TextAppearance.Material.Notification.HybridNotificationDivider"
+        parent="@*android:style/TextAppearance.Material.Notification">
+    </style>
+
     <style name="SearchPanelCircle">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index a0dbad4..e770b5d 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -51,6 +51,8 @@
     private static final float BOLT_LEVEL_THRESHOLD = 0.3f;  // opaque bolt below this fraction
 
     private final int[] mColors;
+    private final int mIntrinsicWidth;
+    private final int mIntrinsicHeight;
 
     private boolean mShowPercent;
     private float mButtonHeightFraction;
@@ -161,6 +163,19 @@
         mLightModeBackgroundColor =
                 context.getColor(R.color.light_mode_icon_color_dual_tone_background);
         mLightModeFillColor = context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
+
+        mIntrinsicWidth = context.getResources().getDimensionPixelSize(R.dimen.battery_width);
+        mIntrinsicHeight = context.getResources().getDimensionPixelSize(R.dimen.battery_height);
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mIntrinsicHeight;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mIntrinsicWidth;
     }
 
     public void startListening() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
index ed90904..1df372b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
@@ -24,6 +24,7 @@
 import android.view.ViewGroup;
 import android.widget.ImageView;
 
+import android.widget.ImageView.ScaleType;
 import com.android.systemui.R;
 
 import java.util.Objects;
@@ -96,7 +97,7 @@
     protected View createIcon() {
         final ImageView icon = new ImageView(mContext);
         icon.setId(android.R.id.icon);
-        icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+        icon.setScaleType(ScaleType.FIT_CENTER);
         return icon;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 35000d3..d79f4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -76,7 +76,7 @@
 
     private String mTileSpec;
 
-    abstract protected TState newTileState();
+    public abstract TState newTileState();
     abstract protected void handleClick();
     abstract protected void handleUpdateState(TState state, Object arg);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 753efb0..f208470 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -18,12 +18,13 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-
+import android.widget.Space;
 import com.android.systemui.R;
 
 import java.util.ArrayList;
@@ -102,6 +103,8 @@
 
     private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
 
+        private final ImageView mDownArrow;
+
         public HeaderTileLayout(Context context) {
             super(context);
             setClipChildren(false);
@@ -111,23 +114,40 @@
 
             int padding =
                     mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
-            ImageView downArrow = new ImageView(context);
-            downArrow.setImageResource(R.drawable.ic_expand_more);
-            downArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
+            mDownArrow = new ImageView(context);
+            mDownArrow.setImageResource(R.drawable.ic_expand_more);
+            mDownArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
                     android.R.color.white, null)));
-            downArrow.setLayoutParams(generateLayoutParams());
-            downArrow.setPadding(padding, padding, padding, padding);
-            addView(downArrow);
+            mDownArrow.setLayoutParams(generateLayoutParams());
+            mDownArrow.setPadding(padding, padding, padding, padding);
+            updateDownArrowMargin();
+            addView(mDownArrow);
             setOrientation(LinearLayout.HORIZONTAL);
         }
 
         @Override
-        public void addTile(TileRecord tile) {
-            tile.tileView.setLayoutParams(generateLayoutParams());
-            addView(tile.tileView, getChildCount() - 1 /* Leave icon at end */);
+        protected void onConfigurationChanged(Configuration newConfig) {
+            super.onConfigurationChanged(newConfig);
+            updateDownArrowMargin();
         }
 
-        private LayoutParams generateLayoutParams() {
+        private void updateDownArrowMargin() {
+            LayoutParams params = (LayoutParams) mDownArrow.getLayoutParams();
+            params.setMarginStart(mContext.getResources().getDimensionPixelSize(
+                    R.dimen.qs_expand_margin));
+            mDownArrow.setLayoutParams(params);
+        }
+
+        @Override
+        public void addTile(TileRecord tile) {
+            addView(tile.tileView, getChildCount() - 1 /* Leave icon at end */,
+                    generateLayoutParams());
+            // Add a spacer.
+            addView(new Space(mContext), getChildCount() - 1 /* Leave icon at end */,
+                    generateSpaceParams());
+        }
+
+        private LayoutParams generateSpaceParams() {
             int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
             LayoutParams lp = new LayoutParams(0, size);
             lp.weight = 1;
@@ -135,9 +155,30 @@
             return lp;
         }
 
+        private LayoutParams generateLayoutParams() {
+            int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
+            LayoutParams lp = new LayoutParams(size, size);
+            lp.gravity = Gravity.CENTER;
+            return lp;
+        }
+
         @Override
         public void removeTile(TileRecord tile) {
-            removeView(tile.tileView);
+            int childIndex = getChildIndex(tile.tileView);
+            // Remove the tile.
+            removeViewAt(childIndex);
+            // Remove its spacer as well.
+            removeViewAt(childIndex);
+        }
+
+        private int getChildIndex(QSTileBaseView tileView) {
+            final int N = getChildCount();
+            for (int i = 0; i < N; i++) {
+                if (getChildAt(i) == tileView) {
+                    return i;
+                }
+            }
+            return -1;
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
deleted file mode 100644
index 36bed0d..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 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.systemui.qs.customize;
-
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.graphics.drawable.Drawable;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-
-public class BlankCustomTile extends QSTile<QSTile.State> {
-    public static final String PREFIX = "custom(";
-
-    private final ComponentName mComponent;
-
-    private BlankCustomTile(Host host, String action) {
-        super(host);
-        mComponent = ComponentName.unflattenFromString(action);
-    }
-
-    public static QSTile<?> create(Host host, String spec) {
-        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
-            throw new IllegalArgumentException("Bad custom tile spec: " + spec);
-        }
-        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
-        if (action.isEmpty()) {
-            throw new IllegalArgumentException("Empty custom tile spec action");
-        }
-        return new BlankCustomTile(host, action);
-    }
-
-    @Override
-    public void setListening(boolean listening) {
-    }
-
-    @Override
-    protected State newTileState() {
-        return new State();
-    }
-
-    @Override
-    protected void handleUserSwitch(int newUserId) {
-        super.handleUserSwitch(newUserId);
-    }
-
-    @Override
-    protected void handleClick() {
-        MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
-    }
-
-    @Override
-    protected void handleLongClick() {
-    }
-
-    @Override
-    protected void handleUpdateState(State state, Object arg) {
-        try {
-            PackageManager pm = mContext.getPackageManager();
-            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
-            Drawable drawable = info.loadIcon(pm);
-            drawable.setTint(mContext.getColor(R.color.qs_tile_tint_active));
-            state.icon = new DrawableIcon(drawable);
-            state.label = info.loadLabel(pm).toString();
-            state.contentDescription = state.label;
-        } catch (Exception e) {
-        }
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return MetricsEvent.QS_INTENT;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
deleted file mode 100644
index 286748b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2015 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.systemui.qs.customize;
-
-import android.app.ActivityManager;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.external.TileLifecycleManager;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.tuner.TunerService;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A version of QSPanel that allows tiles to be dragged around rather than
- * clicked on.  Dragging starting and receiving is handled in the NonPagedTileLayout,
- * and the saving/ordering is handled by the CustomQSTileHost.
- */
-public class CustomQSPanel extends QSPanel {
-    
-    private static final String TAG = "CustomQSPanel";
-    private static final boolean DEBUG = false;
-
-    private List<String> mSavedTiles = Collections.emptyList();
-    private ArrayList<String> mStash;
-    private List<String> mTiles = new ArrayList<>();
-
-    private ArrayList<QSTile<?>> mCurrentTiles = new ArrayList<>();
-
-    public CustomQSPanel(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
-                .inflate(R.layout.qs_customize_layout, mQsContainer, false);
-        mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */);
-        ((NonPagedTileLayout) mTileLayout).setCustomQsPanel(this);
-        removeView(mFooter.getView());
-
-        if (DEBUG) Log.d(TAG, "new CustomQSPanel", new Throwable());
-        TunerService.get(mContext).addTunable(this, QSTileHost.TILES_SETTING);
-    }
-
-    @Override
-    protected void showDetail(boolean show, Record r) {
-        // No detail here.
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        // Don't allow the super to unregister the tunable.
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (key.equals(QS_SHOW_BRIGHTNESS)) {
-            // No Brightness for you.
-            super.onTuningChanged(key, "0");
-        }
-        if (QSTileHost.TILES_SETTING.equals(key)) {
-            mSavedTiles = Collections.unmodifiableList(
-                    QSTileHost.loadTileSpecs(mContext, newValue));
-            if (DEBUG) Log.d(TAG, "New saved tiles " + TextUtils.join(",", mSavedTiles));
-        }
-    }
-
-    @Override
-    protected void createCustomizePanel() {
-        // Already in CustomizePanel.
-    }
-
-    public void tileSelected(QSTile<?> tile, ClipData currentClip) {
-        String sourceSpec = getSpec(currentClip);
-        String destSpec = tile.getTileSpec();
-        if (!sourceSpec.equals(destSpec)) {
-            moveTo(sourceSpec, destSpec);
-        }
-    }
-
-    public ClipData getClip(QSTile<?> tile) {
-        String tileSpec = tile.getTileSpec();
-        // TODO: Something better than plain text.
-        // TODO: Once using something better than plain text, stop listening to non-QS drag events.
-        return ClipData.newPlainText(tileSpec, tileSpec);
-    }
-
-    public String getSpec(ClipData data) {
-        return data.getItemAt(0).getText().toString();
-    }
-
-    public void setSavedTiles() {
-        if (DEBUG) Log.d(TAG, "setSavedTiles " + TextUtils.join(",", mSavedTiles));
-        setTiles(mSavedTiles);
-    }
-
-    public void saveCurrentTiles() {
-        mHost.changeTiles(mSavedTiles, mTiles);
-    }
-
-    public void stashCurrentTiles() {
-        mStash = new ArrayList<>(mTiles);
-    }
-
-    public void unstashTiles() {
-        setTiles(mStash);
-    }
-
-    @Override
-    public void setTiles(Collection<QSTile<?>> tiles) {
-        setTilesInternal();
-    }
-
-    private void setTilesInternal() {
-        if (DEBUG) Log.d(TAG, "Set tiles internal");
-        for (int i = 0; i < mCurrentTiles.size(); i++) {
-            mCurrentTiles.get(i).destroy();
-        }
-        mCurrentTiles.clear();
-        for (int i = 0; i < mTiles.size(); i++) {
-            if (mTiles.get(i).startsWith(CustomTile.PREFIX)) {
-                QSTile<?> tile = BlankCustomTile.create(mHost, mTiles.get(i));
-                tile.setTileSpec(mTiles.get(i));
-                mCurrentTiles.add(tile);
-            } else {
-                QSTile<?> tile = mHost.createTile(mTiles.get(i));
-                if (tile != null) {
-                    tile.setTileSpec(mTiles.get(i));
-                    mCurrentTiles.add(tile);
-                } else {
-                    if (DEBUG) Log.d(TAG, "Skipping " + mTiles.get(i));
-                }
-            }
-        }
-        super.setTiles(mCurrentTiles);
-    }
-
-    public void addTile(String spec) {
-        if (DEBUG) Log.d(TAG, "addTile " + spec);
-        mTiles.add(spec);
-        setTilesInternal();
-    }
-
-    public void moveTo(String from, String to) {
-        if (DEBUG) Log.d(TAG, "moveTo " + from + " " + to);
-        int fromIndex = mTiles.indexOf(from);
-        if (fromIndex < 0) {
-            Log.e(TAG, "Unknown from tile " + from);
-            return;
-        }
-        int index = mTiles.indexOf(to);
-        if (index < 0) {
-            Log.e(TAG, "Unknown to tile " + to);
-            return;
-        }
-        mTiles.remove(fromIndex);
-        mTiles.add(index, from);
-        setTilesInternal();
-    }
-
-    public void remove(String spec) {
-        if (!mTiles.remove(spec)) {
-            Log.e(TAG, "Unknown remove spec " + spec);
-        }
-        setTilesInternal();
-    }
-
-    public void setTiles(List<String> tiles) {
-        if (DEBUG) Log.d(TAG, "Set tiles " + TextUtils.join(",", tiles));
-        mTiles = new ArrayList<>(tiles);
-        setTilesInternal();
-    }
-
-    public Collection<QSTile<?>> getTiles() {
-        return mCurrentTiles;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
deleted file mode 100644
index 3135408..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 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.systemui.qs.customize;
-
-import android.content.ClipData;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.DragEvent;
-import android.view.View;
-import android.view.View.OnDragListener;
-import android.widget.TextView;
-
-public class DropButton extends TextView implements OnDragListener {
-
-    private OnDropListener mListener;
-
-    public DropButton(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        // TODO: Don't do this, instead make this view the right size...
-        ((View) getParent()).setOnDragListener(this);
-    }
-
-    public void setOnDropListener(OnDropListener listener) {
-        mListener = listener;
-    }
-
-    private void setHovering(boolean hovering) {
-        setAlpha(hovering ? .3f : 1);
-    }
-
-    @Override
-    public boolean onDrag(View v, DragEvent event) {
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_ENTERED:
-                setHovering(true);
-                break;
-            case DragEvent.ACTION_DROP:
-                if (mListener != null) {
-                    mListener.onDrop(this, event.getClipData());
-                }
-            case DragEvent.ACTION_DRAG_EXITED:
-            case DragEvent.ACTION_DRAG_ENDED:
-                setHovering(false);
-                break;
-        }
-        return true;
-    }
-
-    public interface OnDropListener {
-        void onDrop(View v, ClipData data);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java
deleted file mode 100644
index 8791a10..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2015 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.systemui.qs.customize;
-
-import android.animation.AnimatorInflater;
-import android.content.Context;
-import android.graphics.Outline;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-public class FloatingActionButton extends ImageView {
-
-    public FloatingActionButton(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setScaleType(ScaleType.CENTER);
-        setStateListAnimator(AnimatorInflater.loadStateListAnimator(context, R.anim.fab_elevation));
-        setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setOval(0, 0, getWidth(), getHeight());
-            }
-        });
-        setClipToOutline(true);
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        invalidateOutline();
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
deleted file mode 100644
index 98c7be4..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2015 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.systemui.qs.customize;
-
-import android.content.ClipData;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.qs.PagedTileLayout;
-import com.android.systemui.qs.PagedTileLayout.TilePage;
-import com.android.systemui.qs.QSPanel.QSTileLayout;
-import com.android.systemui.qs.QSPanel.TileRecord;
-import com.android.systemui.qs.QSTile;
-
-import java.util.ArrayList;
-
-/**
- * Similar to PagedTileLayout, except that instead of pages it lays them out
- * vertically and expects to be inside a ScrollView.
- * @see CustomQSPanel
- */
-public class NonPagedTileLayout extends LinearLayout implements QSTileLayout, OnTouchListener {
-
-    private final ArrayList<TilePage> mPages = new ArrayList<>();
-    private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>();
-    private CustomQSPanel mPanel;
-    private final Rect mHitRect = new Rect();
-
-    private ClipData mCurrentClip;
-    private View mCurrentView;
-
-    public NonPagedTileLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        TilePage page = (PagedTileLayout.TilePage) findViewById(R.id.tile_page);
-        page.setMaxRows(3 /* First page only gets 3 */);
-        mPages.add(page);
-    }
-
-    public void setCustomQsPanel(CustomQSPanel qsPanel) {
-        mPanel = qsPanel;
-    }
-
-    @Override
-    public void addTile(TileRecord record) {
-        mTiles.add(record);
-        distributeTiles();
-        if (record.tileView.getTag() == record.tile) {
-            return;
-        }
-        record.tileView.setTag(record.tile);
-        record.tileView.setVisibility(View.VISIBLE);
-        record.tileView.init(null, null);
-        record.tileView.setOnTouchListener(this);
-        if (mCurrentClip != null && mCurrentClip.getItemAt(0)
-                .getText().toString().equals(record.tile.getTileSpec())) {
-            record.tileView.setAlpha(.3f);
-            mCurrentView = record.tileView;
-        }
-    }
-
-    @Override
-    public void removeTile(TileRecord tile) {
-        if (mTiles.remove(tile)) {
-            distributeTiles();
-        }
-    }
-
-    private void distributeTiles() {
-        final int NP = mPages.size();
-        for (int i = 0; i < NP; i++) {
-            mPages.get(i).removeAllViews();
-        }
-        int index = 0;
-        final int NT = mTiles.size();
-        for (int i = 0; i < NT; i++) {
-            TileRecord tile = mTiles.get(i);
-            mPages.get(index).addTile(tile);
-            // Keep everything in one layout for now.
-            if (false && mPages.get(index).isFull()) {
-                if (++index == mPages.size()) {
-                    LayoutInflater inflater = LayoutInflater.from(mContext);
-                    inflater.inflate(R.layout.horizontal_divider, this);
-                    mPages.add((TilePage) inflater.inflate(R.layout.qs_paged_page, this, false));
-                    addView(mPages.get(mPages.size() - 1));
-                }
-            }
-        }
-    }
-
-    @Override
-    public int getOffsetTop(TileRecord tile) {
-        // No touch feedback, so this isn't required.
-        return 0;
-    }
-
-    @Override
-    public boolean updateResources() {
-        return false;
-    }
-
-    @Override
-    public boolean onDragEvent(DragEvent event) {
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_LOCATION:
-                float x = event.getX();
-                float y = event.getY();
-                final int NP = mPages.size();
-                for (int i = 0; i < NP; i++) {
-                    TilePage page = mPages.get(i);
-                    if (contains(page, x, y)) {
-                        x -= page.getLeft();
-                        y -= page.getTop();
-                        final int NC = page.getChildCount();
-                        for (int j = 0; j < NC; j++) {
-                            View child = page.getChildAt(j);
-                            if (contains(child, x, y)) {
-                                mPanel.tileSelected((QSTile<?>) child.getTag(), mCurrentClip);
-                            }
-                        }
-                        break;
-                    }
-                }
-                break;
-            case DragEvent.ACTION_DRAG_ENDED:
-                onDragEnded();
-                break;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                // Stash the current tiles, in case the drop is on info, that we can restore
-                // the previous state.
-                mPanel.stashCurrentTiles();
-                mCurrentView = v;
-                mCurrentClip = mPanel.getClip((QSTile<?>) v.getTag());
-                View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);
-                ((View) getParent().getParent()).startDrag(mCurrentClip, shadow, null, 0);
-                v.setAlpha(.3f);
-                return true;
-        }
-        return false;
-    }
-
-    public void onDragEnded() {
-        mCurrentView.setAlpha(1f);
-        mCurrentView = null;
-        mCurrentClip = null;
-    }
-
-    private boolean contains(View v, float x, float y) {
-        v.getHitRect(mHitRect);
-        return mHitRect.contains((int) x, (int) y);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index a6c7fe4..edcccac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -16,38 +16,25 @@
 package com.android.systemui.qs.customize;
 
 import android.animation.Animator;
-import android.content.ClipData;
+import android.animation.Animator.AnimatorListener;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnDismissListener;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
 import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.Toolbar;
-import android.widget.Toolbar.OnMenuItemClickListener;
-
 import com.android.systemui.R;
 import com.android.systemui.qs.QSDetailClipper;
-import com.android.systemui.qs.QSTile.Host.Callback;
-import com.android.systemui.qs.customize.DropButton.OnDropListener;
-import com.android.systemui.qs.customize.TileAdapter.TileSelectedListener;
+import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Allows full-screen customization of QS, through show() and hide().
@@ -55,26 +42,19 @@
  * This adds itself to the status bar window, so it can appear on top of quick settings and
  * *someday* do fancy animations to get into/out of it.
  */
-public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback,
-        OnDropListener, OnClickListener, Animator.AnimatorListener, TileSelectedListener,
-        OnCancelListener, OnDismissListener {
+public class QSCustomizer extends LinearLayout implements AnimatorListener, OnClickListener {
 
-    private static final int MENU_SAVE = Menu.FIRST;
-    private static final int MENU_RESET = Menu.FIRST + 1;
     private final QSDetailClipper mClipper;
 
     private PhoneStatusBar mPhoneStatusBar;
 
-    private Toolbar mToolbar;
-    private ViewGroup mDragButtons;
-    private CustomQSPanel mQsPanel;
-
     private boolean isShown;
-    private DropButton mInfoButton;
-    private DropButton mRemoveButton;
-    private FloatingActionButton mFab;
-    private SystemUIDialog mDialog;
     private QSTileHost mHost;
+    private RecyclerView mRecyclerView;
+    private TileAdapter mTileAdapter;
+    private View mClose;
+    private View mSave;
+    private View mReset;
 
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -83,59 +63,42 @@
 
     public void setHost(QSTileHost host) {
         mHost = host;
-        mHost.addCallback(this);
         mPhoneStatusBar = host.getPhoneStatusBar();
-        mQsPanel.setTiles(mHost.getTiles());
-        mQsPanel.setHost(mHost);
-        mQsPanel.setSavedTiles();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mToolbar = (Toolbar) findViewById(com.android.internal.R.id.action_bar);
-        TypedValue value = new TypedValue();
-        mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
-        mToolbar.setNavigationIcon(
-                getResources().getDrawable(R.drawable.ic_close_white, mContext.getTheme()));
-        mToolbar.setNavigationOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hide(0, 0);
-            }
-        });
-        mToolbar.setOnMenuItemClickListener(this);
-        mToolbar.getMenu().add(Menu.NONE, MENU_SAVE, 0, mContext.getString(R.string.save))
-                .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-        mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
-                mContext.getString(com.android.internal.R.string.reset));
+        mClose = findViewById(R.id.close);
+        mSave = findViewById(R.id.save);
+        mReset = findViewById(R.id.reset);
+        mClose.setOnClickListener(this);
+        mSave.setOnClickListener(this);
+        mReset.setOnClickListener(this);
 
-        mQsPanel = (CustomQSPanel) findViewById(R.id.quick_settings_panel);
-
-        mDragButtons = (ViewGroup) findViewById(R.id.drag_buttons);
-        setDragging(false);
-
-        mInfoButton = (DropButton) findViewById(R.id.info_button);
-        mInfoButton.setOnDropListener(this);
-        mRemoveButton = (DropButton) findViewById(R.id.remove_button);
-        mRemoveButton.setOnDropListener(this);
-
-        mFab = (FloatingActionButton) findViewById(R.id.fab);
-        mFab.setImageResource(R.drawable.ic_add);
-        mFab.setOnClickListener(this);
+        mRecyclerView = (RecyclerView) findViewById(android.R.id.list);
+        mTileAdapter = new TileAdapter(getContext());
+        mRecyclerView.setAdapter(mTileAdapter);
+        new ItemTouchHelper(mTileAdapter.getCallback()).attachToRecyclerView(mRecyclerView);
+        GridLayoutManager layout = new GridLayoutManager(getContext(), 3);
+        layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
+        mRecyclerView.setLayoutManager(layout);
+        mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
+        DefaultItemAnimator animator = new DefaultItemAnimator();
+        animator.setMoveDuration(TileAdapter.MOVE_DURATION);
+        mRecyclerView.setItemAnimator(animator);
     }
 
     public void show(int x, int y) {
         isShown = true;
-        mQsPanel.setSavedTiles();
         mPhoneStatusBar.getStatusBarWindow().addView(this);
-        mQsPanel.setListening(true);
+        setTileSpecs();
         mClipper.animateCircularClip(x, y, true, this);
+        new TileQueryHelper(mContext, mHost).setListener(mTileAdapter);
     }
 
     public void hide(int x, int y) {
         isShown = false;
-        mQsPanel.setListening(false);
         mClipper.animateCircularClip(x, y, false, this);
     }
 
@@ -149,109 +112,35 @@
         for (String tile : defTiles.split(",")) {
             tiles.add(tile);
         }
-        mQsPanel.setTiles(tiles);
+        mTileAdapter.setTileSpecs(tiles);
     }
 
-    private void setDragging(boolean dragging) {
-        mToolbar.setVisibility(!dragging ? View.VISIBLE : View.INVISIBLE);
+    private void setTileSpecs() {
+        List<String> specs = new ArrayList<>();
+        for (QSTile tile : mHost.getTiles()) {
+            specs.add(tile.getTileSpec());
+        }
+        mTileAdapter.setTileSpecs(specs);
     }
 
     private void save() {
-        Log.d("CustomQSPanel", "Save!");
-        mQsPanel.saveCurrentTiles();
-        // TODO: At save button.
-        hide(0, 0);
-    }
-
-    @Override
-    public boolean onMenuItemClick(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_SAVE:
-                Log.d("CustomQSPanel", "Save...");
-                save();
-                break;
-            case MENU_RESET:
-                reset();
-                break;
-        }
-        return true;
-    }
-
-    @Override
-    public void onTileSelected(String spec) {
-        if (mDialog != null) {
-            mQsPanel.addTile(spec);
-            mDialog.dismiss();
-        }
-    }
-
-    @Override
-    public void onTilesChanged() {
-        mQsPanel.setTiles(mHost.getTiles());
-    }
-
-    public boolean onDragEvent(DragEvent event) {
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_STARTED:
-                setDragging(true);
-                break;
-            case DragEvent.ACTION_DRAG_ENDED:
-                setDragging(false);
-                break;
-        }
-        return true;
-    }
-
-    public void onDrop(View v, ClipData data) {
-        if (v == mRemoveButton) {
-            mQsPanel.remove(mQsPanel.getSpec(data));
-        } else if (v == mInfoButton) {
-            mQsPanel.unstashTiles();
-            SystemUIDialog dialog = new SystemUIDialog(mContext);
-            dialog.setTitle(mQsPanel.getSpec(data));
-            dialog.setPositiveButton(R.string.ok, null);
-            dialog.show();
-        }
+        mTileAdapter.saveSpecs(mHost);
+        hide((int) mSave.getX() + mSave.getWidth() / 2, (int) mSave.getY() + mSave.getHeight() / 2);
     }
 
     @Override
     public void onClick(View v) {
-        if (mFab == v) {
-            mDialog = new SystemUIDialog(mContext,
-                    android.R.style.Theme_Material_Dialog);
-            View view = LayoutInflater.from(mContext).inflate(R.layout.qs_add_tiles_list, null);
-            ListView listView = (ListView) view.findViewById(android.R.id.list);
-            TileAdapter adapter = new TileAdapter(mContext, mQsPanel.getTiles(), mHost);
-            adapter.setListener(this);
-            listView.setDivider(null);
-            listView.setDividerHeight(0);
-            listView.setAdapter(adapter);
-            listView.setEmptyView(view.findViewById(R.id.empty_text));
-            mDialog.setView(view);
-            mDialog.setOnDismissListener(this);
-            mDialog.setOnCancelListener(this);
-            mDialog.show();
-            // Too lazy to figure out what this will be now, but it should probably be something
-            // besides just a dialog.
-            // For now, just make it big.
-            WindowManager.LayoutParams params = mDialog.getWindow().getAttributes();
-            params.width = WindowManager.LayoutParams.MATCH_PARENT;
-            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
-            mDialog.getWindow().setAttributes(params);
+        if (v == mClose) {
+            hide((int) mClose.getX() + mClose.getWidth() / 2,
+                    (int) mClose.getY() + mClose.getHeight() / 2);
+        } else if (v == mSave) {
+            save();
+        } else if (v == mReset) {
+            reset();
         }
     }
 
     @Override
-    public void onDismiss(DialogInterface dialog) {
-        mDialog = null;
-    }
-
-    @Override
-    public void onCancel(DialogInterface dialog) {
-        mDialog = null;
-    }
-
-    @Override
     public void onAnimationEnd(Animator animation) {
         if (!isShown) {
             mPhoneStatusBar.getStatusBarWindow().removeView(this);
@@ -274,4 +163,4 @@
     public void onAnimationRepeat(Animator animation) {
         // Don't care.
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index b72789e..fb3818c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -1,262 +1,293 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 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
+ * 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.
+ * 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.systemui.qs.customize;
 
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
-import android.service.quicksettings.TileService;
-import android.util.Log;
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ItemDecoration;
+import android.support.v7.widget.RecyclerView.State;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.support.v7.widget.helper.ItemTouchHelper.Callback;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.GridLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
+import android.widget.FrameLayout;
 import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTile.Icon;
-import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.qs.QSIconView;
+import com.android.systemui.qs.QSTileView;
+import com.android.systemui.qs.customize.TileAdapter.Holder;
+import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
+import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
 import com.android.systemui.statusbar.phone.QSTileHost;
 
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 
-public class TileAdapter extends BaseAdapter {
+public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileStateListener {
 
-    private static final String TAG = "TileAdapter";
+    private static final long DRAG_LENGTH = 100;
+    private static final float DRAG_SCALE = 1.2f;
+    public static final long MOVE_DURATION = 150;
 
-    private final ArrayList<TileGroup> mGroups = new ArrayList<>();
+    private static final int TYPE_TILE = 0;
+    private static final int TYPE_EDIT = 1;
+
     private final Context mContext;
 
-    private TileSelectedListener mListener;
-    private ArrayList<String> mCurrentTiles;
+    private final List<TileInfo> mTiles = new ArrayList<>();
+    private int mDividerIndex;
+    private List<String> mCurrentSpecs;
+    private List<TileInfo> mOtherTiles;
+    private List<TileInfo> mAllTiles;
 
-    public TileAdapter(Context context, Collection<QSTile<?>> currentTiles, QSTileHost host) {
+    private Holder mCurrentDrag;
+
+    public TileAdapter(Context context) {
         mContext = context;
-        addSystemTiles(currentTiles, host);
-        // TODO: Live?
-    }
-
-    private void addSystemTiles(Collection<QSTile<?>> currentTiles, QSTileHost host) {
-        try {
-            ArrayList<String> tileSpecs = new ArrayList<>();
-            for (QSTile<?> tile : currentTiles) {
-                tileSpecs.add(tile.getTileSpec());
-            }
-            mCurrentTiles = tileSpecs;
-            final TileGroup group = new TileGroup("com.android.settings", mContext);
-            boolean hasColorMod = host.getDisplayController().isEnabled();
-            String possible = mContext.getString(R.string.quick_settings_tiles_default)
-                    + ",hotspot,inversion,saver" + (hasColorMod ? ",colors" : "");
-            String[] possibleTiles = possible.split(",");
-            for (int i = 0; i < possibleTiles.length; i++) {
-                final String spec = possibleTiles[i];
-                if (spec.startsWith("q")) {
-                    // Quick tiles can't be customized.
-                    continue;
-                }
-                if (tileSpecs.contains(spec)) {
-                    Log.d(TAG, "Skipping " + spec);
-                    continue;
-                }
-                Log.d(TAG, "Trying " + spec);
-                final QSTile<?> tile = host.createTile(spec);
-                if (tile == null) {
-                    continue;
-                }
-                // Bad, bad, very bad.
-                tile.setListening(true);
-                tile.clearState();
-                tile.refreshState();
-                tile.setListening(false);
-                new Handler(host.getLooper()).post(new Runnable() {
-                    @Override
-                    public void run() {
-                        group.addTile(spec, tile.getState().icon, tile.getState().label, mContext);
-                    }
-                });
-            }
-            // Error: Badness (10000).
-            // Serialize this work after the host's looper's queue is empty.
-            new Handler(host.getLooper()).post(new Runnable() {
-                @Override
-                public void run() {
-                    new Handler(Looper.getMainLooper()).post(new Runnable() {
-                        @Override
-                        public void run() {
-                            if (group.mTiles.size() > 0) {
-                                mGroups.add(group);
-                                notifyDataSetChanged();
-                            }
-                            new QueryTilesTask().execute();
-                        }
-                    });
-                }
-            });
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Couldn't load system tiles", e);
-        }
-    }
-
-    public void setListener(TileSelectedListener listener) {
-        mListener = listener;
-    }
-
-    @Override
-    public int getCount() {
-        return mGroups.size();
-    }
-
-    @Override
-    public Object getItem(int position) {
-        return mGroups.get(position);
+        setHasStableIds(true);
     }
 
     @Override
     public long getItemId(int position) {
-        return position;
+        return mTiles.get(position) != null ? mAllTiles.indexOf(mTiles.get(position)) : -1;
+    }
+
+    public Callback getCallback() {
+        return mCallbacks;
+    }
+
+    public ItemDecoration getItemDecoration() {
+        return mDecoration;
+    }
+
+    public void saveSpecs(QSTileHost host) {
+        List<String> newSpecs = new ArrayList<>();
+        for (int i = 0; mTiles.get(i) != null; i++) {
+            newSpecs.add(mTiles.get(i).spec);
+        }
+        host.changeTiles(mCurrentSpecs, newSpecs);
+        setTileSpecs(newSpecs);
+    }
+
+    public void setTileSpecs(List<String> currentSpecs) {
+        mCurrentSpecs = currentSpecs;
+        recalcSpecs();
     }
 
     @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-        return mGroups.get(position).getView(mContext, convertView, parent, mListener);
+    public void onTilesChanged(List<TileInfo> tiles) {
+        mAllTiles = tiles;
+        recalcSpecs();
     }
 
-    private static class TileGroup {
-        private final ArrayList<TileInfo> mTiles = new ArrayList<>();
-        private CharSequence mLabel;
-        private Drawable mIcon;
-
-        public TileGroup(String pkg, Context context) throws NameNotFoundException {
-            PackageManager pm = context.getPackageManager();
-            ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
-            mLabel = info.loadLabel(pm);
-            mIcon = info.loadIcon(pm);
-            Log.d(TAG, "Added " + mLabel);
+    private void recalcSpecs() {
+        if (mCurrentSpecs == null || mAllTiles == null) {
+            return;
         }
-
-        private void addTile(String spec, Drawable icon, CharSequence label) {
-            TileInfo info = new TileInfo();
-            info.label = label;
-            info.drawable = icon;
-            info.spec = spec;
-            mTiles.add(info);
+        mOtherTiles = new ArrayList<TileInfo>(mAllTiles);
+        mTiles.clear();
+        for (int i = 0; i < mCurrentSpecs.size(); i++) {
+            mTiles.add(getAndRemoveOther(mCurrentSpecs.get(i)));
         }
+        mTiles.add(null);
+        mTiles.addAll(mOtherTiles);
+        mDividerIndex = mTiles.indexOf(null);
+        notifyDataSetChanged();
+    }
 
-        private void addTile(String spec, Icon icon, CharSequence label, Context context) {
-            addTile(spec, icon != null ? icon.getDrawable(context) : null, label);
-        }
-
-        private View getView(Context context, View convertView, ViewGroup parent,
-                final TileSelectedListener listener) {
-            if (convertView == null) {
-                convertView = LayoutInflater.from(context).inflate(R.layout.tile_listing, parent,
-                        false);
+    private TileInfo getAndRemoveOther(String s) {
+        for (int i = 0; i < mOtherTiles.size(); i++) {
+            if (mOtherTiles.get(i).spec.equals(s)) {
+                return mOtherTiles.remove(i);
             }
-            ((TextView) convertView.findViewById(android.R.id.title)).setText(mLabel);
-            ((ImageView) convertView.findViewById(android.R.id.icon)).setImageDrawable(mIcon);
-            GridLayout grid = (GridLayout) convertView.findViewById(R.id.tile_grid);
-            final int N = mTiles.size();
-            if (grid.getChildCount() != N) {
-                grid.removeAllViews();
+        }
+        return null;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (mTiles.get(position) == null) {
+            return TYPE_EDIT;
+        }
+        return TYPE_TILE;
+    }
+
+    @Override
+    public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
+        final Context context = parent.getContext();
+        LayoutInflater inflater = LayoutInflater.from(context);
+        if (viewType == 1) {
+            return new Holder(inflater.inflate(R.layout.qs_customize_divider, parent, false));
+        }
+        FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
+                false);
+        frame.addView(new QSTileView(context, new QSIconView(context)));
+        return new Holder(frame);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mTiles.size();
+    }
+
+    @Override
+    public void onBindViewHolder(Holder holder, int position) {
+        if (holder.getItemViewType() == TYPE_EDIT) return;
+
+        TileInfo info = mTiles.get(position);
+        holder.mTileView.onStateChanged(info.state);
+    }
+
+    public SpanSizeLookup getSizeLookup() {
+        return mSizeLookup;
+    }
+
+    public class Holder extends ViewHolder {
+        private QSTileView mTileView;
+
+        public Holder(View itemView) {
+            super(itemView);
+            if (itemView instanceof FrameLayout) {
+                mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0);
             }
-            for (int i = 0; i < N; i++) {
-                if (grid.getChildCount() <= i) {
-                    grid.addView(createTile(context));
-                }
-                View view = grid.getChildAt(i);
-                final TileInfo tileInfo = mTiles.get(i);
-                ((ImageView) view.findViewById(R.id.tile_icon)).setImageDrawable(tileInfo.drawable);
-                ((TextView) view.findViewById(R.id.tile_label)).setText(tileInfo.label);
-                view.setClickable(true);
-                view.setOnClickListener(new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        listener.onTileSelected(tileInfo.spec);
-                    }
-                });
-            }
-            return convertView;
         }
 
-        private View createTile(Context context) {
-            return LayoutInflater.from(context).inflate(R.layout.qs_add_tile_layout, null);
+        public void startDrag() {
+            itemView.animate()
+                    .setDuration(DRAG_LENGTH)
+                    .scaleX(DRAG_SCALE)
+                    .scaleY(DRAG_SCALE);
+            mTileView.findViewById(R.id.tile_label).animate()
+                    .setDuration(DRAG_LENGTH)
+                    .alpha(0);
+        }
+
+        public void stopDrag() {
+            itemView.animate()
+                    .setDuration(DRAG_LENGTH)
+                    .scaleX(1)
+                    .scaleY(1);
+            mTileView.findViewById(R.id.tile_label).animate()
+                    .setDuration(DRAG_LENGTH)
+                    .alpha(1);
         }
     }
 
-    private static class TileInfo {
-        private String spec;
-        private Drawable drawable;
-        private CharSequence label;
-    }
-
-    private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
+    private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() {
         @Override
-        protected Collection<TileGroup> doInBackground(Void... params) {
-            HashMap<String, TileGroup> pkgMap = new HashMap<>();
-            PackageManager pm = mContext.getPackageManager();
-            // TODO: Handle userness.
-            List<ResolveInfo> services = pm.queryIntentServices(
-                    new Intent(TileService.ACTION_QS_TILE), 0);
-            for (ResolveInfo info : services) {
-                String packageName = info.serviceInfo.packageName;
-                ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
-                String spec = CustomTile.toSpec(componentName);
-                if (mCurrentTiles.contains(spec)) {
+        public int getSpanSize(int position) {
+            return getItemViewType(position) == TYPE_EDIT ? 3 : 1;
+        }
+    };
+
+    private final ItemDecoration mDecoration = new ItemDecoration() {
+        // TODO: Move this to resource.
+        private final ColorDrawable mDrawable = new ColorDrawable(0xff384248);
+
+        @Override
+        public void onDraw(Canvas c, RecyclerView parent, State state) {
+            super.onDraw(c, parent, state);
+
+            final int childCount = parent.getChildCount();
+            final int width = parent.getWidth();
+            final int bottom = parent.getBottom();
+            for (int i = 0; i < childCount; i++) {
+                final View child = parent.getChildAt(i);
+                final ViewHolder holder = parent.getChildViewHolder(child);
+                if (holder.getAdapterPosition() < mDividerIndex) {
                     continue;
                 }
-                try {
-                    TileGroup group = pkgMap.get(packageName);
-                    if (group == null) {
-                        group = new TileGroup(packageName, mContext);
-                        pkgMap.put(packageName, group);
-                    }
-                    Drawable icon = info.serviceInfo.loadIcon(pm);
-                    CharSequence label = info.serviceInfo.loadLabel(pm);
-                    group.addTile(spec, icon, label != null ? label.toString() : "null");
-                } catch (NameNotFoundException e) {
-                    Log.w(TAG, "Couldn't find resolved package... " + packageName, e);
-                }
+
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+                        .getLayoutParams();
+                final int top = child.getTop() + params.topMargin +
+                        Math.round(ViewCompat.getTranslationY(child));
+                // Draw full width, in case there aren't tiles all the way across.
+                mDrawable.setBounds(0, top, width, bottom);
+                mDrawable.draw(c);
+                break;
             }
-            return pkgMap.values();
+        }
+    };
+
+    private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
+
+        @Override
+        public boolean isLongPressDragEnabled() {
+            return true;
         }
 
         @Override
-        protected void onPostExecute(Collection<TileGroup> result) {
-            mGroups.addAll(result);
-            notifyDataSetChanged();
+        public boolean isItemViewSwipeEnabled() {
+            return false;
         }
-    }
 
-    public interface TileSelectedListener {
-        void onTileSelected(String spec);
-    }
+        @Override
+        public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
+            super.onSelectedChanged(viewHolder, actionState);
+            if (mCurrentDrag != null) {
+                mCurrentDrag.stopDrag();
+            }
+            if (viewHolder != null) {
+                mCurrentDrag = (Holder) viewHolder;
+                mCurrentDrag.startDrag();
+            }
+        }
+
+        @Override
+        public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
+            if (viewHolder.getItemViewType() == TYPE_EDIT) {
+                return makeMovementFlags(0, 0);
+            }
+            int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT
+                    | ItemTouchHelper.LEFT;
+            return makeMovementFlags(dragFlags, 0);
+        }
+
+        @Override
+        public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
+            int from = viewHolder.getAdapterPosition();
+            int to = target.getAdapterPosition();
+            if (to > mDividerIndex) {
+                if (from < mDividerIndex) {
+                    to = mDividerIndex;
+                } else {
+                    return false;
+                }
+            }
+            if (target.getItemViewType() == TYPE_EDIT && from < mDividerIndex) {
+                to++;
+            }
+            move(from, to, mTiles);
+            mDividerIndex = mTiles.indexOf(null);
+            notifyItemMoved(from, to);
+            return true;
+        }
+
+        private <T> void move(int from, int to, List<T> list) {
+            list.add(from > to ? to : to + 1, list.get(from));
+            list.remove(from > to ? from + 1 : from);
+        }
+
+        @Override
+        public void onSwiped(ViewHolder viewHolder, int direction) {
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
new file mode 100644
index 0000000..6840e08
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 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.systemui.qs.customize;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.service.quicksettings.TileService;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTile.DrawableIcon;
+import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.statusbar.phone.QSTileHost;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class TileQueryHelper {
+
+    private static final String TAG = "TileQueryHelper";
+
+    private final ArrayList<TileInfo> mTiles = new ArrayList<>();
+    private final Context mContext;
+    private TileStateListener mListener;
+
+    public TileQueryHelper(Context context, QSTileHost host) {
+        mContext = context;
+        addSystemTiles(host);
+        // TODO: Live?
+    }
+
+    private void addSystemTiles(QSTileHost host) {
+        boolean hasColorMod = host.getDisplayController().isEnabled();
+        String possible = mContext.getString(R.string.quick_settings_tiles_default)
+                + ",hotspot,inversion,saver" + (hasColorMod ? ",colors" : "");
+        String[] possibleTiles = possible.split(",");
+        final Handler qsHandler = new Handler(host.getLooper());
+        final Handler mainHandler = new Handler(Looper.getMainLooper());
+        for (int i = 0; i < possibleTiles.length; i++) {
+            final String spec = possibleTiles[i];
+            final QSTile<?> tile = host.createTile(spec);
+            if (tile == null) {
+                continue;
+            }
+            tile.setListening(true);
+            tile.clearState();
+            tile.refreshState();
+            tile.setListening(false);
+            qsHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    final QSTile.State state = tile.newTileState();
+                    tile.getState().copyTo(state);
+                    mainHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            addTile(spec, state, mTiles);
+                            mListener.onTilesChanged(mTiles);
+                        }
+                    });
+                }
+            });
+        }
+        qsHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                new QueryTilesTask().execute();
+            }
+        });
+    }
+
+    public void setListener(TileStateListener listener) {
+        mListener = listener;
+    }
+
+    private static void addTile(String spec, QSTile.State state, List<TileInfo> tiles) {
+        TileInfo info = new TileInfo();
+        info.state = state;
+        info.spec = spec;
+        tiles.add(info);
+    }
+
+    private static void addTile(String spec, Drawable drawable, CharSequence label, Context context,
+            List<TileInfo> tiles) {
+        QSTile.State state = new QSTile.State();
+        state.label = label;
+        state.contentDescription = label;
+        state.icon = new DrawableIcon(drawable);
+        addTile(spec, state, tiles);
+    }
+
+    public static class TileInfo {
+        public String spec;
+        public QSTile.State state;
+    }
+
+    private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileInfo>> {
+        @Override
+        protected Collection<TileInfo> doInBackground(Void... params) {
+            List<TileInfo> tiles = new ArrayList<>();
+            PackageManager pm = mContext.getPackageManager();
+            List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+                    new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
+            for (ResolveInfo info : services) {
+                String packageName = info.serviceInfo.packageName;
+                ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
+                String spec = CustomTile.toSpec(componentName);
+                Drawable icon = info.serviceInfo.loadIcon(pm);
+                if (icon != null) {
+                    icon.mutate();
+                    icon.setTint(mContext.getColor(android.R.color.white));
+                }
+                CharSequence label = info.serviceInfo.loadLabel(pm);
+                addTile(spec, icon, label != null ? label.toString() : "null", mContext, tiles);
+            }
+            return tiles;
+        }
+
+        @Override
+        protected void onPostExecute(Collection<TileInfo> result) {
+            mTiles.addAll(result);
+            mListener.onTilesChanged(mTiles);
+        }
+    }
+
+    public interface TileStateListener {
+        void onTilesChanged(List<TileInfo> tiles);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index df3b5de..3cd9e67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -151,7 +151,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new State();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index d78d6ff..5222e61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -51,7 +51,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index 64b3a6c..cd3e3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -54,7 +54,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new QSTile.State();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 874fc3e..1dce053 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -56,7 +56,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 18eb7a1..15e082a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -60,7 +60,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index aacdbc9..c3a2ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -54,7 +54,7 @@
     }
 
     @Override
-    protected SignalState newTileState() {
+    public SignalState newTileState() {
         return new SignalState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 6e843e9..e98734c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -54,7 +54,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 1aeb0fe..c6a98b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -30,7 +30,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f99a3e4..58872ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -94,7 +94,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 1d9f15b..f06634e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -47,7 +47,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 2f37943..943b502 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -44,7 +44,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index e1dc9f2..bdf95d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -75,7 +75,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new State();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 8328897..9f41f9a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -45,7 +45,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index f920d48..c94cf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -46,7 +46,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index 1565b6f..ba7ea4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -37,7 +37,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new QSTile.State();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 42296f2..ac4dfd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -60,7 +60,7 @@
     }
 
     @Override
-    protected SignalState newTileState() {
+    public SignalState newTileState() {
         return new SignalState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 508490f..a94973c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -53,7 +53,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
index d9276bf..3067714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
@@ -17,14 +17,12 @@
 package com.android.systemui.statusbar;
 
 import android.content.Context;
-import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
 
 import com.android.systemui.R;
 
 public class DismissView extends StackScrollerDecorView {
-    private boolean mDismissAllInProgress;
     private DismissViewButton mDismissButton;
 
     public DismissView(Context context, AttributeSet attrs) {
@@ -53,27 +51,7 @@
                 || touchY > mContent.getY() + mContent.getHeight();
     }
 
-    public void showClearButton() {
-        mDismissButton.showButton();
-    }
-
-    public void setDismissAllInProgress(boolean dismissAllInProgress) {
-        if (dismissAllInProgress) {
-            setClipBounds(null);
-        }
-        mDismissAllInProgress = dismissAllInProgress;
-    }
-
-    @Override
-    public void setClipBounds(Rect clipBounds) {
-        if (mDismissAllInProgress) {
-            // we don't want any clipping to happen!
-            return;
-        }
-        super.setClipBounds(clipBounds);
-    }
-
     public boolean isButtonVisible() {
-        return mDismissButton.isButtonStatic();
+        return mDismissButton.getAlpha() != 0.0f;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
index 46060f1..b608d67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
@@ -17,22 +17,13 @@
 package com.android.systemui.statusbar;
 
 import android.content.Context;
-import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.view.View;
 import android.view.ViewGroup;
-import android.widget.Button;
 
-import com.android.systemui.R;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
-public class DismissViewButton extends Button {
-    private AnimatedVectorDrawable mAnimatedDismissDrawable;
-    private final Drawable mStaticDismissDrawable;
-    private Drawable mActiveDrawable;
+public class DismissViewButton extends AlphaOptimizedButton {
 
     public DismissViewButton(Context context) {
         this(context, null);
@@ -49,55 +40,6 @@
     public DismissViewButton(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mAnimatedDismissDrawable = (AnimatedVectorDrawable) getContext().getDrawable(
-                R.drawable.dismiss_all_shape_animation).mutate();
-        mAnimatedDismissDrawable.setCallback(this);
-        mAnimatedDismissDrawable.setBounds(0,
-                0,
-                mAnimatedDismissDrawable.getIntrinsicWidth(),
-                mAnimatedDismissDrawable.getIntrinsicHeight());
-        mStaticDismissDrawable = getContext().getDrawable(R.drawable.dismiss_all_shape);
-        mStaticDismissDrawable.setBounds(0,
-                0,
-                mStaticDismissDrawable.getIntrinsicWidth(),
-                mStaticDismissDrawable.getIntrinsicHeight());
-        mStaticDismissDrawable.setCallback(this);
-        mActiveDrawable = mStaticDismissDrawable;
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        canvas.save();
-        int drawableHeight = mActiveDrawable.getBounds().height();
-        boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
-        int dx = isRtl ? getWidth() / 2 + drawableHeight / 2 : getWidth() / 2 - drawableHeight / 2;
-        canvas.translate(dx, getHeight() / 2.0f + drawableHeight /
-                2.0f);
-        canvas.scale(isRtl ? -1.0f : 1.0f, -1.0f);
-        mActiveDrawable.draw(canvas);
-        canvas.restore();
-    }
-
-    @Override
-    public boolean performClick() {
-        if (!mAnimatedDismissDrawable.isRunning()) {
-            mActiveDrawable = mAnimatedDismissDrawable;
-            mAnimatedDismissDrawable.start();
-        }
-        return super.performClick();
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return super.verifyDrawable(who)
-                || who == mAnimatedDismissDrawable
-                || who == mStaticDismissDrawable;
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
     }
 
     /**
@@ -118,16 +60,4 @@
         outRect.top += translationY;
         outRect.bottom += translationY;
     }
-
-    public void showButton() {
-        mActiveDrawable = mStaticDismissDrawable;
-        invalidate();
-    }
-
-    /**
-     * @return Whether the button is currently static and not being animated.
-     */
-    public boolean isButtonStatic() {
-        return mActiveDrawable == mStaticDismissDrawable;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 98a37f9..c70aad2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -74,8 +74,7 @@
         }
 
         private void applyToChild(View view, boolean shouldApply, int originalColor) {
-            if (view.getVisibility() == View.VISIBLE
-                    && originalColor != NotificationHeaderView.NO_COLOR) {
+            if (originalColor != NotificationHeaderView.NO_COLOR) {
                 ImageView imageView = (ImageView) view;
                 imageView.getDrawable().mutate();
                 if (shouldApply) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
index ec73935..81144d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
@@ -107,12 +107,13 @@
 
     public void bind(CharSequence title, CharSequence text) {
         mTitleView.setText(title);
-        if (TextUtils.isEmpty(title)) {
-            mTitleView.setVisibility(GONE);
-        }
-        mTextView.setText(text);
+        mTitleView.setVisibility(TextUtils.isEmpty(title) ? GONE : VISIBLE);
         if (TextUtils.isEmpty(text)) {
             mTextView.setVisibility(GONE);
+            mTextView.setText(null);
+        } else {
+            mTextView.setVisibility(VISIBLE);
+            mTextView.setText(text.toString());
         }
         requestLayout();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
index 285d53f..28bb66f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
@@ -18,8 +18,13 @@
 
 import android.app.Notification;
 import android.content.Context;
+import android.text.BidiFormatter;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
@@ -34,12 +39,12 @@
 
     private final Context mContext;
     private ViewGroup mParent;
-    private String mConcadenationString;
+    private String mDivider;
 
     public HybridNotificationViewManager(Context ctx, ViewGroup parent) {
         mContext = ctx;
         mParent = parent;
-        mConcadenationString = mContext.getString(R.string.group_summary_concadenation);
+        mDivider = " • ";
     }
 
     private HybridNotificationView inflateHybridView() {
@@ -83,7 +88,7 @@
         if (reusableView == null) {
             reusableView = inflateHybridView();
         }
-        CharSequence summary = null;
+        SpannableStringBuilder summary = new SpannableStringBuilder();
         int childCount = group.size();
         for (int i = startIndex; i < childCount; i++) {
             ExpandableNotificationRow child = group.get(i);
@@ -92,15 +97,18 @@
             if (titleText == null) {
                 continue;
             }
-            if (TextUtils.isEmpty(summary)) {
-                summary = titleText;
-            } else if (reusableView.isLayoutRtl()) {
-                summary = titleText + mConcadenationString + summary;
-            } else {
-                summary = summary + mConcadenationString + titleText;
+            if (!TextUtils.isEmpty(summary)) {
+                summary.append(mDivider,
+                        new TextAppearanceSpan(mContext, R.style.
+                                TextAppearance_Material_Notification_HybridNotificationDivider),
+                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
             }
+            summary.append(BidiFormatter.getInstance().unicodeWrap(titleText));
         }
-        reusableView.bind(summary);
+        // We want to force the same orientation as the layout RTL mode
+        BidiFormatter formater = BidiFormatter.getInstance(
+                reusableView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+        reusableView.bind(formater.unicodeWrap(summary));
         return reusableView;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
new file mode 100644
index 0000000..487a7a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 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.systemui.statusbar.notification;
+
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+import com.android.internal.widget.ImageFloatingTextView;
+import com.android.systemui.statusbar.TransformableView;
+
+/**
+ * Wraps a notification containing a big text template
+ */
+public class NotificationBigTextTemplateViewWrapper extends NotificationTemplateViewWrapper {
+
+    private ImageFloatingTextView mBigtext;
+
+    protected NotificationBigTextTemplateViewWrapper(Context ctx, View view) {
+        super(ctx, view);
+    }
+
+    private void resolveViews(StatusBarNotification notification) {
+        mBigtext = (ImageFloatingTextView) mView.findViewById(com.android.internal.R.id.big_text);
+    }
+
+    @Override
+    public void notifyContentUpdated(StatusBarNotification notification) {
+        // Reinspect the notification. Before the super call, because the super call also updates
+        // the transformation types and we need to have our values set by then.
+        resolveViews(notification);
+        super.notifyContentUpdated(notification);
+    }
+
+    @Override
+    protected void updateTransformedTypes() {
+        // This also clears the existing types
+        super.updateTransformedTypes();
+        if (mBigtext != null) {
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
+                    mBigtext);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index b060245..0c21f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -55,11 +55,12 @@
                         }
                         TransformState otherState = notification.getCurrentState(
                                 TRANSFORMING_VIEW_TITLE);
-                        CrossFadeHelper.fadeOut(mText, endRunnable);
+                        final View text = ownState.getTransformedView();
+                        CrossFadeHelper.fadeOut(text, endRunnable);
                         if (otherState != null) {
                             int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
                             int[] ownPosition = ownState.getLaidOutLocationOnScreen();
-                            mText.animate()
+                            text.animate()
                                     .translationY((otherStablePosition[1]
                                             + otherState.getTransformedView().getHeight()
                                             - ownPosition[1]) * 0.33f)
@@ -72,11 +73,11 @@
                                             if (endRunnable != null) {
                                                 endRunnable.run();
                                             }
-                                            TransformState.setClippingDeactivated(mText,
+                                            TransformState.setClippingDeactivated(text,
                                                     false);
                                         }
                                     });
-                            TransformState.setClippingDeactivated(mText, true);
+                            TransformState.setClippingDeactivated(text, true);
                             otherState.recycle();
                         }
                         return true;
@@ -90,17 +91,18 @@
                         }
                         TransformState otherState = notification.getCurrentState(
                                 TRANSFORMING_VIEW_TITLE);
-                        boolean isVisible = mText.getVisibility() == View.VISIBLE;
-                        CrossFadeHelper.fadeIn(mText);
+                        final View text = ownState.getTransformedView();
+                        boolean isVisible = text.getVisibility() == View.VISIBLE;
+                        CrossFadeHelper.fadeIn(text);
                         if (otherState != null) {
                             int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
                             int[] ownStablePosition = ownState.getLaidOutLocationOnScreen();
                             if (!isVisible) {
-                                mText.setTranslationY((otherStablePosition[1]
+                                text.setTranslationY((otherStablePosition[1]
                                         + otherState.getTransformedView().getHeight()
                                         - ownStablePosition[1]) * 0.33f);
                             }
-                            mText.animate()
+                            text.animate()
                                     .translationY(0)
                                     .setDuration(
                                             StackStateAnimator.ANIMATION_DURATION_STANDARD)
@@ -108,11 +110,11 @@
                                     .withEndAction(new Runnable() {
                                         @Override
                                         public void run() {
-                                            TransformState.setClippingDeactivated(mText,
+                                            TransformState.setClippingDeactivated(text,
                                                     false);
                                         }
                                     });
-                            TransformState.setClippingDeactivated(mText, true);
+                            TransformState.setClippingDeactivated(text, true);
                             otherState.recycle();
                         }
                         return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index f50b976..a2b4c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -37,6 +37,8 @@
         if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
             if ("bigPicture".equals(v.getTag())) {
                 return new NotificationBigPictureTemplateViewWrapper(ctx, v);
+            } else if ("bigText".equals(v.getTag())) {
+                return new NotificationBigTextTemplateViewWrapper(ctx, v);
             }
             return new NotificationTemplateViewWrapper(ctx, v);
         } else if (v instanceof NotificationHeaderView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
index 9a94d34..eade2a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
@@ -35,6 +35,7 @@
 
     public abstract int getCollapsedHeight();
     public abstract int getExpandedHeight();
+
     public abstract void setExpanded(boolean b);
     public abstract void setExpansion(float headerExpansionFraction);
     public abstract void setListening(boolean listening);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 3130eb9..d3681b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -154,10 +154,14 @@
         }
         mBarState = newState;
         if (mBarState == StatusBarState.KEYGUARD) {
-            for (NotificationGroup group : mGroupMap.values()) {
-                if (group.expanded) {
-                    setGroupExpanded(group, false);
-                }
+            collapseAllGroups();
+        }
+    }
+
+    public void collapseAllGroups() {
+        for (NotificationGroup group : mGroupMap.values()) {
+            if (group.expanded) {
+                setGroupExpanded(group, false);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index cbb71c5..fd28b09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.View;
@@ -55,6 +56,20 @@
     }
 
     @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        reloadWidth(mScrollView);
+        reloadWidth(mStackScroller);
+    }
+
+    private void reloadWidth(View view) {
+        LayoutParams params = (LayoutParams) view.getLayoutParams();
+        params.width = getContext().getResources().getDimensionPixelSize(
+                R.dimen.notification_panel_width);
+        view.setLayoutParams(params);
+    }
+
+    @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
         return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 9aeb1f8..fe46385 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -31,7 +31,6 @@
 import android.widget.Switch;
 import android.widget.TextView;
 import android.widget.Toast;
-
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
@@ -74,6 +73,9 @@
     private float mDateTimeTranslation;
     private MultiUserSwitch mMultiUserSwitch;
     private ImageView mMultiUserAvatar;
+    private View mQsDetailHeaderBack;
+
+    private final int[] mTmpInt2 = new int[2];
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -87,6 +89,7 @@
         mDateTimeTranslation = mContext.getResources().getDimension(
                 R.dimen.qs_date_anim_translation);
         mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
+        mDateTimeGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
         mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group);
 
         mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
@@ -100,6 +103,7 @@
 
         mQsDetailHeader = findViewById(R.id.qs_detail_header);
         mQsDetailHeader.setAlpha(0);
+        mQsDetailHeaderBack = mQsDetailHeader.findViewById(com.android.internal.R.id.up);
         mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
         mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
         mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress);
@@ -367,6 +371,15 @@
                         }
                     });
                 }
+                mQsDetailHeaderBack.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        v.getLocationInWindow(mTmpInt2);
+                        mTmpInt2[0] += v.getWidth() / 2;
+                        mTmpInt2[1] += v.getHeight() / 2;
+                        mQsPanel.showDetailAdapter(false, null, mTmpInt2);
+                    }
+                });
             } else {
                 mQsDetailHeader.setClickable(false);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 409dac1..5cfcd89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.stack;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -58,6 +59,7 @@
     private HybridNotificationView mGroupOverflowContainer;
     private ViewState mGroupOverFlowState;
     private int mRealHeight;
+    private int mLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
 
     public NotificationChildrenContainer(Context context) {
         this(context, null);
@@ -209,6 +211,16 @@
         }
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        int layoutDirection = getLayoutDirection();
+        if (layoutDirection != mLayoutDirection) {
+            updateGroupOverflow();
+            mLayoutDirection = layoutDirection;
+        }
+    }
+
     private View inflateDivider() {
         return LayoutInflater.from(mContext).inflate(
                 R.layout.notification_children_divider, this, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index d6276b8..cc0e67d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1648,6 +1648,9 @@
                 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
                 bottom = Math.min(bottom, getHeight());
             }
+        } else if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+            top = mTopPadding;
+            bottom = top;
         }
         mBackgroundBounds.top = Math.max(0, top);
         mBackgroundBounds.bottom = Math.min(getHeight(), bottom);
@@ -2093,12 +2096,6 @@
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
-        if (canChildBeDismissed(child)) {
-            // Make sure the dismissButton is visible and not in the animated state.
-            // We need to do this to avoid a race where a clearable notification is added after the
-            // dismiss animation is finished
-            mDismissView.showClearButton();
-        }
     }
 
     private void updateHideSensitiveForChild(View child) {
@@ -2608,6 +2605,9 @@
         mIsExpanded = isExpanded;
         mStackScrollAlgorithm.setIsExpanded(isExpanded);
         if (changed) {
+            if (!mIsExpanded) {
+                mGroupManager.collapseAllGroups();
+            }
             updateNotificationAnimationStates();
             updateChronometers();
         }
@@ -2977,7 +2977,6 @@
                     mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
                 } else {
                     dimissHideFinishRunnable.run();
-                    mDismissView.showClearButton();
                 }
             }
         }
@@ -2985,7 +2984,6 @@
 
     public void setDismissAllInProgress(boolean dismissAllInProgress) {
         mDismissAllInProgress = dismissAllInProgress;
-        mDismissView.setDismissAllInProgress(dismissAllInProgress);
         mAmbientState.setDismissAllInProgress(dismissAllInProgress);
         if (dismissAllInProgress) {
             disableClipOptimization();
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
index 7b06393..d8cf2e2 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
@@ -56,7 +56,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new State();
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
index 912e8e2..b79a7ba 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
@@ -25,6 +25,7 @@
 import android.gesture.GestureStroke;
 import android.gesture.Prediction;
 import android.util.Slog;
+import android.util.TypedValue;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -70,7 +71,7 @@
 
     // Indicates that we've collected enough data to be sure it could be a
     // gesture.
-    private boolean mGestureConfirmed;
+    private boolean mGestureStarted;
 
     // Indicates that motion events from the second pointer are being checked
     // for a double tap.
@@ -83,47 +84,39 @@
     // Policy flags of the previous event.
     private int mPolicyFlags;
 
-    // The X of the previous event.
-    private float mPreviousX;
+    // These values track the previous point that was saved to use for gesture
+    // detection.  They are only updated when the user moves more than the
+    // recognition threshold.
+    private float mPreviousGestureX;
+    private float mPreviousGestureY;
 
-    // The Y of the previous event.
-    private float mPreviousY;
-
-    // The X of the down event.
+    // These values track the previous point that was used to determine if there
+    // was a transition into or out of gesture detection.  They are updated when
+    // the user moves more than the detection threshold.
     private float mBaseX;
-
-    // The Y of the down event.
     private float mBaseY;
+    private long mBaseTime;
 
-    // Slop between the first and second tap to be a double tap.
-    private final int mDoubleTapSlop;
-
-    // The scaled velocity above which we detect gestures.
-    private final int mScaledGestureDetectionVelocity;
+    // This is the calculated movement threshold used track if the user is still
+    // moving their finger.
+    private final float mGestureDetectionThreshold;
 
     // Buffer for storing points for gesture detection.
     private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
 
-    // Helper to track gesture velocity.
-    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-
     // The minimal delta between moves to add a gesture point.
     private static final int TOUCH_TOLERANCE = 3;
 
     // The minimal score for accepting a predicted gesture.
     private static final float MIN_PREDICTION_SCORE = 2.0f;
 
-    // The velocity above which we detect gestures.  Expressed in DIPs/Second.
-    private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
-
-    // Constant used to calculate velocity in seconds.
-    private static final int VELOCITY_UNITS_SECONDS = 1000;
+    private static final int GESTURE_CONFIRM_MM = 10;
+    private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 1000;
+    private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 500;
 
     AccessibilityGestureDetector(Context context, Listener listener) {
         mListener = listener;
 
-        mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
-
         mGestureDetector = new GestureDetector(context, this);
         mGestureDetector.setOnDoubleTapListener(this);
 
@@ -132,15 +125,14 @@
         mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
         mGestureLibrary.load();
 
-        final float density = context.getResources().getDisplayMetrics().density;
-        mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
+        mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
+                context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
     }
 
     public boolean onMotionEvent(MotionEvent event, int policyFlags) {
-        mVelocityTracker.addMovement(event);
-
         final float x = event.getX();
         final float y = event.getY();
+        final long time = event.getEventTime();
 
         mPolicyFlags = policyFlags;
         switch (event.getActionMasked()) {
@@ -148,54 +140,56 @@
                 mDoubleTapDetected = false;
                 mSecondFingerDoubleTap = false;
                 mRecognizingGesture = true;
-                mGestureConfirmed = false;
+                mGestureStarted = false;
+                mPreviousGestureX = x;
+                mPreviousGestureY = y;
+                mStrokeBuffer.clear();
+                mStrokeBuffer.add(new GesturePoint(x, y, time));
+
                 mBaseX = x;
                 mBaseY = y;
-                mPreviousX = x;
-                mPreviousY = y;
-                mStrokeBuffer.clear();
-                mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
-                mVelocityTracker.clear();
-                mVelocityTracker.addMovement(event);
+                mBaseTime = time;
                 break;
 
             case MotionEvent.ACTION_MOVE:
                 if (mRecognizingGesture) {
-                    if (!mGestureConfirmed) {
-                        mVelocityTracker.addMovement(event);
-                        // It is *important* to use the distance traveled by the pointers
-                        // on the screen which may or may not be magnified.
-                        final float deltaX = mBaseX - event.getX(0);
-                        final float deltaY = mBaseY - event.getY(0);
-                        final double moveDelta = Math.hypot(deltaX, deltaY);
-                        // The user has moved enough for us to decide.
-                        if (moveDelta > mDoubleTapSlop) {
-                            // Check whether the user is performing a gesture. We
-                            // detect gestures if the pointer is moving above a
-                            // given velocity.
-                            mVelocityTracker.computeCurrentVelocity(VELOCITY_UNITS_SECONDS);
-                            final float maxAbsVelocity = Math.max(
-                                    Math.abs(mVelocityTracker.getXVelocity(0)),
-                                    Math.abs(mVelocityTracker.getYVelocity(0)));
-                            if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
-                                // We have to perform gesture detection, so
-                                // notify the listener.
-                                mGestureConfirmed = true;
-                                mListener.onGestureStarted();
-                            } else {
-                                // This won't match any gesture, so notify the
-                                // listener.
-                                cancelGesture();
-                                mListener.onGestureCancelled(event, policyFlags);
-                            }
+                    final float deltaX = mBaseX - x;
+                    final float deltaY = mBaseY - y;
+                    final double moveDelta = Math.hypot(deltaX, deltaY);
+                    if (moveDelta > mGestureDetectionThreshold) {
+                        // If the pointer has moved more than the threshold,
+                        // update the stored values.
+                        mBaseX = x;
+                        mBaseY = y;
+                        mBaseTime = time;
+
+                        // If this hasn't been confirmed as a gesture yet, send
+                        // the event.
+                        if (!mGestureStarted) {
+                            mGestureStarted = true;
+                            mListener.onGestureStarted();
+                        }
+                    } else {
+                        final long timeDelta = time - mBaseTime;
+                        final long threshold = mGestureStarted ?
+                            CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS :
+                            CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;
+
+                        // If the pointer hasn't moved for longer than the
+                        // timeout, cancel gesture detection.
+                        if (timeDelta > threshold) {
+                            cancelGesture();
+                            mListener.onGestureCancelled(event, policyFlags);
+                            return false;
                         }
                     }
-                    final float dX = Math.abs(x - mPreviousX);
-                    final float dY = Math.abs(y - mPreviousY);
+
+                    final float dX = Math.abs(x - mPreviousGestureX);
+                    final float dY = Math.abs(y - mPreviousGestureY);
                     if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
-                        mPreviousX = x;
-                        mPreviousY = y;
-                        mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+                        mPreviousGestureX = x;
+                        mPreviousGestureY = y;
+                        mStrokeBuffer.add(new GesturePoint(x, y, time));
                     }
                 }
                 break;
@@ -204,8 +198,8 @@
                 if (maybeFinishDoubleTap(event, policyFlags)) {
                     return true;
                 }
-                if (mGestureConfirmed) {
-                    mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+                if (mGestureStarted) {
+                    mStrokeBuffer.add(new GesturePoint(x, y, time));
 
                     if (!recognizeGesture()) {
                         mListener.onGestureCancelled(event, policyFlags);
@@ -223,7 +217,7 @@
                     // If this was the second finger, attempt to recognize double
                     // taps on it.
                     mSecondFingerDoubleTap = true;
-                    mSecondPointerDownTime = event.getEventTime();
+                    mSecondPointerDownTime = time;
                 } else {
                     // If there are more than two fingers down, stop watching
                     // for a double tap.
@@ -268,8 +262,8 @@
         mFirstTapDetected = false;
         mDoubleTapDetected = false;
         mSecondFingerDoubleTap = false;
+        mGestureStarted = false;
         cancelGesture();
-        mVelocityTracker.clear();
     }
 
     public boolean firstTapDetected() {
@@ -323,7 +317,7 @@
 
     private void cancelGesture() {
         mRecognizingGesture = false;
-        mGestureConfirmed = false;
+        mGestureStarted = false;
         mStrokeBuffer.clear();
     }
 
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index c535ebd..5e948b1 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2166,7 +2166,8 @@
             try {
                 ApplicationInfo app = pkg.applicationInfo;
                 if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0)
-                        || app.backupAgentName == null) {
+                        || app.backupAgentName == null
+                        || (app.flags&ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0) {
                     packages.remove(a);
                 }
                 else {
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 5f57a76..4667172 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -111,8 +111,9 @@
     
     static final boolean WAKEUP_STATS = false;
 
-    private static final Intent NEXT_ALARM_CLOCK_CHANGED_INTENT = new Intent(
-            AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+    private static final Intent NEXT_ALARM_CLOCK_CHANGED_INTENT =
+            new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)
+                    .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
 
     final LocalLog mLog = new LocalLog(TAG);
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 54c4ced..1506524 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5303,28 +5303,42 @@
 
     @Override
     public void killAllBackgroundProcesses() {
+        killAllBackgroundProcesses(-1);
+    }
+
+    /**
+     * Kills all background processes with targetSdkVersion below the specified
+     * target SDK version.
+     *
+     * @param targetSdkVersion the target SDK version below which to kill
+     *                         processes, or {@code -1} to kill all processes
+     */
+    private void killAllBackgroundProcesses(int targetSdkVersion) {
         if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES)
                 != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: killAllBackgroundProcesses() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
+            final String msg = "Permission Denial: killAllBackgroundProcesses() from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
                     + " requires " + android.Manifest.permission.KILL_BACKGROUND_PROCESSES;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
 
-        long callingId = Binder.clearCallingIdentity();
+        final long callingId = Binder.clearCallingIdentity();
         try {
-            synchronized(this) {
-                ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
+            synchronized (this) {
+                final ArrayList<ProcessRecord> procs = new ArrayList<>();
                 final int NP = mProcessNames.getMap().size();
-                for (int ip=0; ip<NP; ip++) {
-                    SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
+                for (int ip = 0; ip < NP; ip++) {
+                    final SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
                     final int NA = apps.size();
-                    for (int ia=0; ia<NA; ia++) {
-                        ProcessRecord app = apps.valueAt(ia);
+                    for (int ia = 0; ia < NA; ia++) {
+                        final ProcessRecord app = apps.valueAt(ia);
                         if (app.persistent) {
-                            // we don't kill persistent processes
+                            // We don't kill persistent processes.
+                            continue;
+                        }
+                        if (targetSdkVersion > 0
+                                && app.info.targetSdkVersion >= targetSdkVersion) {
                             continue;
                         }
                         if (app.removed) {
@@ -5336,11 +5350,13 @@
                     }
                 }
 
-                int N = procs.size();
-                for (int i=0; i<N; i++) {
+                final int N = procs.size();
+                for (int i = 0; i < N; i++) {
                     removeProcessLocked(procs.get(i), false, true, "kill all background");
                 }
+
                 mAllowLowerMemLevel = true;
+
                 updateOomAdjLocked();
                 doLowMemReportIfNeededLocked(null);
             }
@@ -17805,6 +17821,11 @@
                     mHandler.sendMessage(msg);
                 }
 
+                final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
+                if (isDensityChange) {
+                    killAllBackgroundProcesses(Build.VERSION_CODES.N);
+                }
+
                 for (int i=mLruProcesses.size()-1; i>=0; i--) {
                     ProcessRecord app = mLruProcesses.get(i);
                     try {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 64c0891..548f563 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3889,8 +3889,29 @@
             int index;
             if (mIsMuted) {
                 index = 0;
-            } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported)
-                    || ((device & mFullVolumeDevices) != 0)) {
+            } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) {
+                /* Special handling for Bluetooth Absolute Volume scenario
+                 * If we send full audio gain, some accessories are too loud even at its lowest
+                 * volume. We are not able to enumerate all such accessories, so here is the
+                 * workaround from phone side.
+                 * For the lowest volume steps 1 and 2, restrict audio gain to 50% and 75%.
+                 * For volume step 0, set audio gain to 0 as some accessories won't mute on their end.
+                 */
+                int i = (getIndex(device) + 5)/10;
+                if (i == 0) {
+                    // 0% for volume 0
+                    index = 0;
+                } else if (i == 1) {
+                    // 50% for volume 1
+                    index = (int)(mIndexMax * 0.5) /10;
+                } else if (i == 2) {
+                    // 75% for volume 2
+                    index = (int)(mIndexMax * 0.75) /10;
+                } else {
+                    // otherwise, full gain
+                    index = (mIndexMax + 5)/10;
+                }
+            } else if ((device & mFullVolumeDevices) != 0) {
                 index = (mIndexMax + 5)/10;
             } else {
                 index = (getIndex(device) + 5)/10;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 544f255..7f8099e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -192,7 +192,7 @@
  * enforcement.
  */
 public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
-    private static final String TAG = "NetworkPolicy";
+    static final String TAG = "NetworkPolicy";
     private static final boolean LOGD = false;
     private static final boolean LOGV = false;
 
@@ -1689,6 +1689,7 @@
             }
             writePolicy = true;
         }
+        updateRulesForGlobalChangeLocked(true);
 
         // Remove associated UID policies
         int[] uids = new int[0];
@@ -1862,8 +1863,8 @@
         Slog.i(TAG, "adding uid " + uid + " to restrict background whitelist");
         synchronized (mRulesLock) {
             mRestrictBackgroundWhitelistUids.append(uid, true);
+            updateRulesForGlobalChangeLocked(true);
             writePolicyLocked();
-            // TODO: call other update methods like updateNetworkRulesLocked?
         }
         mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget();
     }
@@ -1878,9 +1879,10 @@
         mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget();
     }
 
-    private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean writePolicy) {
+    private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean updateNow) {
         mRestrictBackgroundWhitelistUids.delete(uid);
-        if (writePolicy) {
+        if (updateNow) {
+            updateRulesForGlobalChangeLocked(true);
             writePolicyLocked();
         }
     }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
index 5830b0e..281c3d0 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
@@ -16,19 +16,24 @@
 
 package com.android.server.net;
 
-import java.io.PrintWriter;
+import static com.android.server.net.NetworkPolicyManagerService.TAG;
 
-import android.content.Intent;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
 import android.net.INetworkPolicyManager;
+import android.net.NetworkPolicy;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ShellCommand;
+import android.util.Log;
 
-public class NetworkPolicyManagerShellCommand extends ShellCommand {
+class NetworkPolicyManagerShellCommand extends ShellCommand {
 
     final INetworkPolicyManager mInterface;
 
-    NetworkPolicyManagerShellCommand(NetworkPolicyManagerService service) {
+    NetworkPolicyManagerShellCommand(INetworkPolicyManager service) {
         mInterface = service;
     }
 
@@ -66,16 +71,24 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println("");
-        pw.println("  get restrict-background");
-        pw.println("    Gets the global restrict background usage status.");
-        pw.println("  set restrict-background BOOLEAN");
-        pw.println("    Sets the global restrict background usage status.");
-        pw.println("  list restrict-background-whitelist");
-        pw.println("    Prints UID that are whitelisted for restrict background usage.");
         pw.println("  add restrict-background-whitelist UID");
         pw.println("    Adds a UID to the whitelist for restrict background usage.");
+        pw.println("  get metered-network ID");
+        pw.println("    Checks whether the given non-mobile network is metered or not.");
+        pw.println("  get restrict-background");
+        pw.println("    Gets the global restrict background usage status.");
+        pw.println("  list metered-networks [BOOLEAN]");
+        pw.println("    Lists all non-mobile networks and whether they are metered or not.");
+        pw.println("    If a boolean argument is passed, filters just the metered (or unmetered)");
+        pw.println("    networks.");
+        pw.println("  list restrict-background-whitelist");
+        pw.println("    Lists UIDs that are whitelisted for restrict background usage.");
         pw.println("  remove restrict-background-whitelist UID");
         pw.println("    Removes a UID from the whitelist for restrict background usage.");
+        pw.println("  set metered-network ID BOOLEAN");
+        pw.println("    Toggles whether the given non-mobile network is metered.");
+        pw.println("  set restrict-background BOOLEAN");
+        pw.println("    Sets the global restrict background usage status.");
     }
 
     private int runGet() throws RemoteException {
@@ -86,6 +99,8 @@
             return -1;
         }
         switch(type) {
+            case "metered-network":
+                return getNonMobileMeteredNetwork();
             case "restrict-background":
                 return getRestrictBackground();
         }
@@ -101,6 +116,8 @@
             return -1;
         }
         switch(type) {
+            case "metered-network":
+                return setNonMobileMeteredNetwork();
             case "restrict-background":
                 return setRestrictBackground();
         }
@@ -116,6 +133,8 @@
             return -1;
         }
         switch(type) {
+            case "metered-networks":
+                return listNonMobileMeteredNetworks();
             case "restrict-background-whitelist":
                 return runListRestrictBackgroundWhitelist();
         }
@@ -196,7 +215,12 @@
       if (uid < 0) {
           return uid;
       }
-      mInterface.addRestrictBackgroundWhitelistedUid(uid);
+      final long token = Binder.clearCallingIdentity();
+      try {
+          mInterface.addRestrictBackgroundWhitelistedUid(uid);
+      } finally {
+          Binder.restoreCallingIdentity(token);
+      }
       return 0;
     }
 
@@ -205,10 +229,95 @@
         if (uid < 0) {
             return uid;
         }
-        mInterface.removeRestrictBackgroundWhitelistedUid(uid);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mInterface.removeRestrictBackgroundWhitelistedUid(uid);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
         return 0;
     }
 
+    private int listNonMobileMeteredNetworks() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final String arg = getNextArg();
+        final Boolean filter = arg == null ? null : Boolean.valueOf(arg);
+        for (NetworkPolicy policy : getNonMobilePolicies()) {
+            if (filter != null && filter.booleanValue() != policy.metered) {
+                continue;
+            }
+            pw.print(getNetworkId(policy));
+            pw.print(';');
+            pw.println(policy.metered);
+        }
+        return 0;
+    }
+
+    private int getNonMobileMeteredNetwork() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final String id = getNextArg();
+        if (id == null) {
+            pw.println("Error: didn't specify ID");
+            return -1;
+        }
+        final List<NetworkPolicy> policies = getNonMobilePolicies();
+        for (NetworkPolicy policy: policies) {
+            if (id.equals(getNetworkId(policy))) {
+                pw.println(policy.metered);
+                return 0;
+            }
+        }
+        return 0;
+    }
+
+    private int setNonMobileMeteredNetwork() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final String id = getNextArg();
+        if (id == null) {
+            pw.println("Error: didn't specify ID");
+            return -1;
+        }
+        final String arg = getNextArg();
+        if (arg == null) {
+            pw.println("Error: didn't specify BOOLEAN");
+            return -1;
+        }
+        final boolean metered = Boolean.valueOf(arg);
+        final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
+        boolean changed = false;
+        for (NetworkPolicy policy : policies) {
+            if (policy.template.isMatchRuleMobile() || policy.metered == metered) {
+                continue;
+            }
+            final String networkId = getNetworkId(policy);
+            if (id.equals(networkId)) {
+                Log.i(TAG, "Changing " + networkId + " metered status to " + metered);
+                policy.metered = metered;
+                changed = true;
+            }
+        }
+        if (changed) {
+            mInterface.setNetworkPolicies(policies);
+        }
+        return 0;
+    }
+
+    private List<NetworkPolicy> getNonMobilePolicies() throws RemoteException {
+        final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
+        final List<NetworkPolicy> nonMobilePolicies = new ArrayList<NetworkPolicy>(policies.length);
+        for (NetworkPolicy policy: policies) {
+            if (!policy.template.isMatchRuleMobile()) {
+                nonMobilePolicies.add(policy);
+            }
+        }
+        return nonMobilePolicies;
+    }
+
+    private String getNetworkId(NetworkPolicy policy) {
+        // ids are typically enclosed on double quotes (")
+        return policy.template.getNetworkId().replaceAll("^\"|\"$", "");
+    }
+
     private int getNextBooleanArg() {
         final PrintWriter pw = getOutPrintWriter();
         final String arg = getNextArg();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1d91fb7..b7abce21 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -19,6 +19,8 @@
 import static android.media.AudioAttributes.USAGE_ALARM;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static android.media.AudioAttributes.USAGE_UNKNOWN;
+import static android.media.AudioAttributes.USAGE_VIRTUAL_SOURCE;
 
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
@@ -34,6 +36,7 @@
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
+import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
@@ -705,15 +708,20 @@
 
         // notification restrictions
         final boolean muteNotifications = mEffectsSuppressed;
-        applyRestrictions(muteNotifications, USAGE_NOTIFICATION);
-
         // call restrictions
         final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers;
-        applyRestrictions(muteCalls, USAGE_NOTIFICATION_RINGTONE);
+        // total silence restrictions
+        final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
 
-        // alarm restrictions
-        final boolean muteAlarms = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
-        applyRestrictions(muteAlarms, USAGE_ALARM);
+        for (int i = USAGE_UNKNOWN; i <= USAGE_VIRTUAL_SOURCE; i++) {
+            if (i == USAGE_NOTIFICATION) {
+                applyRestrictions(muteNotifications || muteEverything, i);
+            } else if (i == USAGE_NOTIFICATION_RINGTONE) {
+                applyRestrictions(muteCalls || muteEverything, i);
+            } else {
+                applyRestrictions(muteEverything, i);
+            }
+        }
     }
 
     private void applyRestrictions(boolean mute, int usage) {
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 2deb0d5..2f076d1 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -36,6 +36,9 @@
 
     public static final String TAG = "VrManagerService";
 
+    private static native void initializeNative();
+    private static native void setVrModeNative(boolean enabled);
+
     private final Object mLock = new Object();
     private boolean mVrModeEnabled = false;
     private ArraySet<VrStateListener> mListeners = new ArraySet<>();
@@ -68,6 +71,10 @@
 
     @Override
     public void onStart() {
+        synchronized(mLock) {
+            initializeNative();
+        }
+
         publishLocalService(VrManagerInternal.class, new LocalService());
     }
 
@@ -89,6 +96,7 @@
                 mVrModeEnabled = enabled;
                 // Log mode change event.
                 Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
+                setVrModeNative(mVrModeEnabled);
                 onVrModeChangedLocked();
             }
         }
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 5f97478..1bfdcce 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -22,6 +22,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.graphics.Rect;
 import android.util.ArrayMap;
@@ -50,13 +51,15 @@
         private final Rect mFrom;
         private final Rect mTo;
         private final Rect mTmpRect;
+        private final boolean mMoveToFullScreen;
 
-        BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to) {
+        BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen) {
             super();
             mTarget = target;
             mFrom = from;
             mTo = to;
             mTmpRect = new Rect();
+            mMoveToFullScreen = moveToFullScreen;
             addUpdateListener(this);
             addListener(this);
         }
@@ -88,6 +91,9 @@
         @Override
         public void onAnimationEnd(Animator animation) {
             finishAnimation();
+            if (mMoveToFullScreen) {
+                mTarget.moveToFullscreen();
+            }
         }
 
         @Override
@@ -125,14 +131,25 @@
          * necessary cleanup.
          */
         void finishBoundsAnimation();
+
+        void moveToFullscreen();
+
+        void getFullScreenBounds(Rect bounds);
     }
 
-    void animateBounds(AnimateBoundsUser target, Rect from, Rect to) {
+    void animateBounds(final AnimateBoundsUser target, Rect from, Rect to) {
+        boolean moveToFullscreen = false;
+        if (to == null) {
+            to = new Rect();
+            target.getFullScreenBounds(to);
+            moveToFullscreen = true;
+        }
+
         final BoundsAnimator existing = mRunningAnimations.get(target);
         if (existing != null) {
             existing.cancel();
         }
-        BoundsAnimator animator = new BoundsAnimator(target, from, to);
+        BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen);
         mRunningAnimations.put(target, animator);
         animator.setFloatValues(0f, 1f);
         animator.setDuration(DEFAULT_APP_TRANSITION_DURATION);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 4659131..ecc1364 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -927,4 +927,18 @@
             }
         }
     }
+
+    @Override
+    public void moveToFullscreen() {
+        try {
+            mService.mActivityManager.moveTasksToFullscreenStack(mStackId, true);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void getFullScreenBounds(Rect bounds) {
+        getDisplayContent().getContentRect(bounds);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index 2cf2618..d843a8c 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -16,12 +16,14 @@
 
 package com.android.server.wm;
 
-import android.app.ActivityManager.StackId;
 import android.util.Slog;
 import android.view.Display;
 
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
 
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -55,10 +57,10 @@
     }
 
     private int mHighestApplicationLayer = 0;
-    private WindowState mPinnedWindow = null;
-    private WindowState mDockedWindow = null;
+    private ArrayDeque<WindowState> mPinnedWindows = new ArrayDeque<>();
+    private ArrayDeque<WindowState> mDockedWindows = new ArrayDeque<>();
     private WindowState mDockDivider = null;
-    private WindowState mReplacingWindow = null;
+    private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>();
 
     final void assignLayersLocked(WindowList windows) {
         if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows,
@@ -169,43 +171,54 @@
 
     private void clear() {
         mHighestApplicationLayer = 0;
-        mPinnedWindow = null;
-        mDockedWindow = null;
+        mPinnedWindows.clear();
+        mDockedWindows.clear();
+        mReplacingWindows.clear();
         mDockDivider = null;
     }
 
     private void collectSpecialWindows(WindowState w) {
         if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
             mDockDivider = w;
-        } else {
-            final TaskStack stack = w.getStack();
-            if (stack == null) {
-                return;
-            }
-            if (stack.mStackId == StackId.PINNED_STACK_ID) {
-                mPinnedWindow = w;
-            } else if (stack.mStackId == StackId.DOCKED_STACK_ID) {
-                mDockedWindow = w;
-            }
+            return;
+        }
+        if (w.mWillReplaceWindow) {
+            mReplacingWindows.add(w);
+        }
+        final TaskStack stack = w.getStack();
+        if (stack == null) {
+            return;
+        }
+        if (stack.mStackId == PINNED_STACK_ID) {
+            mPinnedWindows.add(w);
+        } else if (stack.mStackId == DOCKED_STACK_ID) {
+            mDockedWindows.add(w);
         }
     }
 
     private void adjustSpecialWindows() {
         int layer = mHighestApplicationLayer + 1;
-        // For pinned and docked stack window, we want to make them above other windows
-        // also when these windows are animating.
-        layer = assignAndIncreaseLayerIfNeeded(mDockedWindow, layer);
+        // For pinned and docked stack window, we want to make them above other windows also when
+        // these windows are animating.
+        while (!mDockedWindows.isEmpty()) {
+            layer = assignAndIncreaseLayerIfNeeded(mDockedWindows.remove(), layer);
+        }
 
         // Leave some space here so the dim layer while dismissing docked/fullscreen stack has space
         // below the divider but above the app windows. It needs to be below the divider in because
         // the divider sometimes overlaps the app windows.
         layer++;
         layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
-        // We know that we will be animating a relaunching window in the near future,
-        // which will receive a z-order increase. We want the replaced window to
-        // immediately receive the same treatment, e.g. to be above the dock divider.
-        layer = assignAndIncreaseLayerIfNeeded(mReplacingWindow, layer);
-        layer = assignAndIncreaseLayerIfNeeded(mPinnedWindow, layer);
+        // We know that we will be animating a relaunching window in the near future, which will
+        // receive a z-order increase. We want the replaced window to immediately receive the same
+        // treatment, e.g. to be above the dock divider.
+        while (!mReplacingWindows.isEmpty()) {
+            layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer);
+        }
+
+        while (!mPinnedWindows.isEmpty()) {
+            layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer);
+        }
     }
 
     private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 66aa863..0979cd3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -35,10 +35,10 @@
 
     static final boolean DEBUG_RESIZE = false;
     static final boolean DEBUG = false;
-    static final boolean DEBUG_ADD_REMOVE = true;
+    static final boolean DEBUG_ADD_REMOVE = false;
     static final boolean DEBUG_FOCUS = false;
     static final boolean DEBUG_FOCUS_LIGHT = DEBUG_FOCUS || false;
-    static final boolean DEBUG_ANIM = true;
+    static final boolean DEBUG_ANIM = false;
     static final boolean DEBUG_KEYGUARD = false;
     static final boolean DEBUG_LAYOUT = false;
     static final boolean DEBUG_LAYERS = false;
@@ -50,7 +50,7 @@
     static final boolean DEBUG_ORIENTATION = false;
     static final boolean DEBUG_APP_ORIENTATION = false;
     static final boolean DEBUG_CONFIGURATION = false;
-    static final boolean DEBUG_APP_TRANSITIONS = true;
+    static final boolean DEBUG_APP_TRANSITIONS = false;
     static final boolean DEBUG_STARTING_WINDOW = false;
     static final boolean DEBUG_WALLPAPER = false;
     static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9f833d1..a26430e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2856,6 +2856,9 @@
             WindowStateAnimator winAnimator, int attrChanges, int oldVisibility) {
         result |= !win.isVisibleLw() ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0;
         if (win.mExiting) {
+            Slog.d(TAG, "relayoutVisibleWindow: " + win + " mExiting=true, mRemoveOnExit="
+                    + win.mRemoveOnExit + ", mDestroying=" + win.mDestroying);
+
             winAnimator.cancelExitAnimationForNextAnimationLocked();
             win.mExiting = false;
         }
@@ -8106,7 +8109,9 @@
                         final WindowState imeWin = mInputMethodWindow;
                         final TaskStack focusedStack =
                                 mCurrentFocus != null ? mCurrentFocus.getStack() : null;
-                        if (imeWin != null && focusedStack != null && imeWin.isVisibleNow()
+                        if (imeWin != null && imeWin.isVisibleNow()
+                                && isStackVisibleLocked(DOCKED_STACK_ID)
+                                && focusedStack != null
                                 && focusedStack.getDockSide() == DOCKED_BOTTOM){
                             final ArrayList<TaskStack> stacks = displayContent.getStacks();
                             for (int i = stacks.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1dbca96..c7c9cbf 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -572,7 +572,8 @@
     WindowSurfaceController createSurfaceLocked() {
         final WindowState w = mWin;
         if (w.hasSavedSurface()) {
-            Slog.i(TAG, "***** createSurface: " + this + ": called when we had a saved surface");
+            if (DEBUG_ANIM) Slog.i(TAG,
+                    "createSurface: " + this + ": called when we had a saved surface");
             w.restoreSavedSurface();
             return mSurfaceController;
         }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index e14cfdf..3219bfe 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -130,7 +130,7 @@
 
     void destroyInTransaction() {
         //        if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
-        Slog.i(TAG, "Destroying surface " + this + " called by " + Debug.getCallers(3));
+        Slog.i(TAG, "Destroying surface " + this + " called by " + Debug.getCallers(4));
         //        }
         try {
             mSurfaceControl.destroy();
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index d1d4721..4e1b644 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -647,14 +647,6 @@
 
             for (int i = windows.size() - 1; i >= 0; i--) {
                 WindowState w = windows.get(i);
-                Task task = w.getTask();
-                if (task == null && w.getDisplayContent().getHomeStack() == null
-                        && w.getAttrs().type != TYPE_PRIVATE_PRESENTATION) {
-                    // TODO: Understand what the use case is here and see if the conditions can be
-                    // simplified.
-                    continue;
-                }
-
                 final boolean obscuredChanged = w.mObscured != mObscured;
 
                 // Update effect.
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index d1b8648..bb571c3 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -28,6 +28,7 @@
     $(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
     $(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \
+    $(LOCAL_REL_DIR)/com_android_server_vr_VrManagerService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
     $(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \
     $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
diff --git a/services/core/jni/com_android_server_vr_VrManagerService.cpp b/services/core/jni/com_android_server_vr_VrManagerService.cpp
new file mode 100644
index 0000000..1aba43b2
--- /dev/null
+++ b/services/core/jni/com_android_server_vr_VrManagerService.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "VrManagerService"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <jni.h>
+#include <JNIHelp.h>
+
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <hardware/hardware.h>
+#include <hardware/vr.h>
+
+namespace android {
+
+static vr_module_t *gVrHardwareModule = NULL;
+
+
+static void init_native(JNIEnv* /* env */, jclass /* clazz */) {
+    if (gVrHardwareModule != NULL) {
+        // This call path should never be hit.
+        ALOGE("%s: May not initialize VR hardware module more than once!", __FUNCTION__);
+        return;
+    }
+
+    int err = hw_get_module(VR_HARDWARE_MODULE_ID, (hw_module_t const**)&gVrHardwareModule);
+    if (err) {
+        ALOGW("%s: Could not open VR hardware module, error %s (%d).", __FUNCTION__,
+                strerror(-err), err);
+        return;
+    }
+
+    // Call init method if implemented.
+    if (gVrHardwareModule->init) {
+        gVrHardwareModule->init(gVrHardwareModule);
+    }
+}
+
+static void setVrMode_native(JNIEnv* /* env */, jclass /* clazz */, jboolean enabled) {
+    if (gVrHardwareModule == NULL) {
+        // There is no VR hardware module implemented, do nothing.
+        return;
+    }
+
+    // Call set_vr_mode method, this must be implemented if the HAL exists.
+    gVrHardwareModule->set_vr_mode(gVrHardwareModule, static_cast<bool>(enabled));
+}
+
+static const JNINativeMethod method_table[] = {
+    { "initializeNative", "()V", (void*)init_native },
+    { "setVrModeNative", "(Z)V", (void*)setVrMode_native },
+};
+
+int register_android_server_vr_VrManagerService(JNIEnv *env)
+{
+    return jniRegisterNativeMethods(env, "com/android/server/vr/VrManagerService",
+            method_table, NELEM(method_table));
+}
+
+}; // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index a7010bc..ef5c56c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -35,6 +35,7 @@
 int register_android_server_UsbDeviceManager(JNIEnv* env);
 int register_android_server_UsbMidiDevice(JNIEnv* env);
 int register_android_server_UsbHostManager(JNIEnv* env);
+int register_android_server_vr_VrManagerService(JNIEnv* env);
 int register_android_server_VibratorService(JNIEnv* env);
 int register_android_server_location_GnssLocationProvider(JNIEnv* env);
 int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
@@ -69,6 +70,7 @@
     register_android_server_UsbDeviceManager(env);
     register_android_server_UsbMidiDevice(env);
     register_android_server_UsbHostManager(env);
+    register_android_server_vr_VrManagerService(env);
     register_android_server_VibratorService(env);
     register_android_server_SystemServer(env);
     register_android_server_location_GnssLocationProvider(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 908d2f0..f296d68 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4132,7 +4132,7 @@
     public void choosePrivateKeyAlias(final int uid, final Uri uri, final String alias,
             final IBinder response) {
         // Caller UID needs to be trusted, so we restrict this method to SYSTEM_UID callers.
-        if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+        if (!isCallerWithSystemUid()) {
             return;
         }
 
@@ -5860,8 +5860,7 @@
         }
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
-        if (hasUserSetupCompleted(userHandle)
-                && !UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)) {
+        if (hasUserSetupCompleted(userHandle) && !isCallerWithSystemUid()) {
             throw new IllegalStateException("Cannot set the profile owner on a user which is "
                     + "already set-up");
         }
@@ -5921,8 +5920,7 @@
 
     private void enforceManageUsers() {
         final int callingUid = mInjector.binderGetCallingUid();
-        if (!(UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
-                || callingUid == Process.ROOT_UID)) {
+        if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         }
     }
@@ -5945,8 +5943,7 @@
         if (userHandle == UserHandle.getUserId(callingUid)) {
             return;
         }
-        if (!(UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
-                || callingUid == Process.ROOT_UID)) {
+        if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
             mContext.enforceCallingOrSelfPermission(permission,
                     "Must be system or have " + permission + " permission");
         }
@@ -5964,6 +5961,10 @@
         }
     }
 
+    private boolean isCallerWithSystemUid() {
+        return UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID);
+    }
+
     private int getProfileParentId(int userHandle) {
         final long ident = mInjector.binderClearCallingIdentity();
         try {
@@ -6248,7 +6249,7 @@
     @Override
     public ComponentName getRestrictionsProvider(int userHandle) {
         synchronized (this) {
-            if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+            if (!isCallerWithSystemUid()) {
                 throw new SecurityException("Only the system can query the permission provider");
             }
             DevicePolicyData userData = getUserData(userHandle);
@@ -6321,8 +6322,7 @@
      * permittedList or are a system app.
      */
     private boolean checkPackagesInPermittedListOrSystem(List<String> enabledPackages,
-            List<String> permittedList) {
-        int userIdToCheck = UserHandle.getCallingUserId();
+            List<String> permittedList, int userIdToCheck) {
         long id = mInjector.binderClearCallingIdentity();
         try {
             // If we have an enabled packages list for a managed profile the packages
@@ -6389,7 +6389,8 @@
                 for (AccessibilityServiceInfo service : enabledServices) {
                     enabledPackages.add(service.getResolveInfo().serviceInfo.packageName);
                 }
-                if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList)) {
+                if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList,
+                        userId)) {
                     Slog.e(LOG_TAG, "Cannot set permitted accessibility services, "
                             + "because it contains already enabled accesibility services.");
                     return false;
@@ -6481,6 +6482,28 @@
         }
     }
 
+    @Override
+    public boolean isAccessibilityServicePermittedByAdmin(ComponentName who, String packageName,
+            int userHandle) {
+        if (!mHasFeature) {
+            return true;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkStringNotEmpty(packageName, "packageName is null");
+        if (!isCallerWithSystemUid()){
+            throw new SecurityException(
+                    "Only the system can query if an accessibility service is disabled by admin");
+        }
+        synchronized (this) {
+            ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
+            if (admin.permittedAccessiblityServices == null) {
+                return true;
+            }
+            return checkPackagesInPermittedListOrSystem(Arrays.asList(packageName),
+                    admin.permittedAccessiblityServices, userHandle);
+        }
+    }
+
     private boolean checkCallerIsCurrentUserOrProfile() {
         int callingUserId = UserHandle.getCallingUserId();
         long token = mInjector.binderClearCallingIdentity();
@@ -6536,7 +6559,8 @@
                 for (InputMethodInfo ime : enabledImes) {
                     enabledPackages.add(ime.getPackageName());
                 }
-                if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList)) {
+                if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList,
+                        mInjector.binderGetCallingUserHandle().getIdentifier())) {
                     Slog.e(LOG_TAG, "Cannot set permitted input methods, "
                             + "because it contains already enabled input method.");
                     return false;
@@ -6629,6 +6653,28 @@
     }
 
     @Override
+    public boolean isInputMethodPermittedByAdmin(ComponentName who, String packageName,
+            int userHandle) {
+        if (!mHasFeature) {
+            return true;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkStringNotEmpty(packageName, "packageName is null");
+        if (!isCallerWithSystemUid()) {
+            throw new SecurityException(
+                    "Only the system can query if an input method is disabled by admin");
+        }
+        synchronized (this) {
+            ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
+            if (admin.permittedInputMethods == null) {
+                return true;
+            }
+            return checkPackagesInPermittedListOrSystem(Arrays.asList(packageName),
+                    admin.permittedInputMethods, userHandle);
+        }
+    }
+
+    @Override
     public UserHandle createUser(ComponentName who, String name) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         synchronized (this) {
@@ -7425,7 +7471,7 @@
 
     @Override
     public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
-        if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+        if (!isCallerWithSystemUid()) {
             throw new SecurityException("notifyLockTaskModeChanged can only be called by system");
         }
         synchronized (this) {
@@ -8180,7 +8226,7 @@
             return null;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
-        if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+        if (!isCallerWithSystemUid()) {
             throw new SecurityException("Only the system can query support message for user");
         }
         synchronized (this) {
@@ -8198,7 +8244,7 @@
             return null;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
-        if (!UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID)) {
+        if (!isCallerWithSystemUid()) {
             throw new SecurityException("Only the system can query support message for user");
         }
         synchronized (this) {
diff --git a/services/usb/java/com/android/server/usb/MtpNotificationManager.java b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
index 203d35e..17039bb 100644
--- a/services/usb/java/com/android/server/usb/MtpNotificationManager.java
+++ b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
@@ -30,7 +30,6 @@
 import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbManager;
 import android.os.UserHandle;
-import android.util.Log;
 
 /**
  * Manager for MTP storage notification.
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 72ff272..c122c5a 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1414,6 +1414,23 @@
         return result;
     }
 
+    /**
+     * Launches the {@link android.app.Activity} to manage blocked numbers.
+     * <p> This method displays the UI to manage blocked numbers only if
+     * {@link android.provider.BlockedNumberContract#canCurrentUserBlockNumbers(Context)} returns
+     * {@code true} for the current user.
+     */
+    public void launchManageBlockedNumbersActivity() {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                service.launchManageBlockedNumbersActivity(mContext.getPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecomService#manageBlockedNumbers", e);
+            }
+        }
+    }
+
     private ITelecomService getTelecomService() {
         if (mTelecomServiceOverride != null) {
             return mTelecomServiceOverride;
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index d1d6e0d..6229ed9 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -25,6 +25,7 @@
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.graphics.Typeface;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.DisplayMetrics;
@@ -338,7 +339,7 @@
     public static String givePrintableIccid(String iccId) {
         String iccIdToPrint = null;
         if (iccId != null) {
-            if (iccId.length() > 9) {
+            if (iccId.length() > 9 && !Build.IS_DEBUGGABLE) {
                 iccIdToPrint = iccId.substring(0, 9) + "XXXXXXXXXXX";
             } else {
                 iccIdToPrint = iccId;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index b028ce6..de7b9c2 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -356,6 +356,15 @@
         </activity>
 
         <activity
+                android:name="MovingSurfaceViewActivity"
+                android:label="SurfaceView/Animated Movement">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
                 android:name="GLTextureViewActivity"
                 android:label="TextureView/OpenGL">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MovingSurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MovingSurfaceViewActivity.java
new file mode 100644
index 0000000..cd15ef1
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MovingSurfaceViewActivity.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 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.test.hwui;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.animation.LinearInterpolator;
+import android.widget.FrameLayout;
+
+public class MovingSurfaceViewActivity extends Activity implements Callback {
+    static final String TAG = "MovingSurfaceView";
+    SurfaceView mSurfaceView;
+    ObjectAnimator mAnimator;
+
+    class MySurfaceView extends SurfaceView {
+        boolean mSlowToggled;
+
+        public MySurfaceView(Context context) {
+            super(context);
+            setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mSlowToggled = !mSlowToggled;
+                    Log.d(TAG, "SLOW MODE: " + mSlowToggled);
+                    invalidate();
+                }
+            });
+            setWillNotDraw(false);
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            super.draw(canvas);
+            if (mSlowToggled) {
+                try {
+                    Thread.sleep(16);
+                } catch (InterruptedException e) {}
+            }
+        }
+
+        public void setMyTranslationY(float ty) {
+            setTranslationY(ty);
+            if (mSlowToggled) {
+                invalidate();
+            }
+        }
+
+        public float getMyTranslationY() {
+            return getTranslationY();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FrameLayout content = new FrameLayout(this);
+
+        mSurfaceView = new MySurfaceView(this);
+        mSurfaceView.getHolder().addCallback(this);
+
+        final float density = getResources().getDisplayMetrics().density;
+        int size = (int) (200 * density);
+
+        content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+                size, size, Gravity.CENTER));
+        mAnimator = ObjectAnimator.ofFloat(mSurfaceView, "myTranslationY",
+                0, size);
+        mAnimator.setRepeatMode(ObjectAnimator.REVERSE);
+        mAnimator.setRepeatCount(ObjectAnimator.INFINITE);
+        mAnimator.setDuration(200);
+        mAnimator.setInterpolator(new LinearInterpolator());
+        setContentView(content);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        Canvas canvas = holder.lockCanvas();
+        canvas.drawARGB(0xFF, 0x00, 0xFF, 0x00);
+        holder.unlockCanvasAndPost(canvas);
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mAnimator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        mAnimator.pause();
+        super.onPause();
+    }
+}
diff --git a/wifi/java/android/net/wifi/AnqpInformationElement.java b/wifi/java/android/net/wifi/AnqpInformationElement.java
new file mode 100644
index 0000000..47b7129
--- /dev/null
+++ b/wifi/java/android/net/wifi/AnqpInformationElement.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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.net.wifi;
+
+/**
+ * This object contains the payload of an ANQP element.
+ * Vendor id is the vendor ID for the element, or 0 if it is an 802.11(u) element.
+ * Hotspot 2.0 uses the WFA Vendor ID which is 0x506f9a
+ * The payload contains the bytes of the payload, starting after the length octet(s).
+ * @hide
+ */
+public class AnqpInformationElement {
+    public static final int HOTSPOT20_VENDOR_ID = 0x506f9a;
+
+    public static final int ANQP_QUERY_LIST = 256;
+    public static final int ANQP_CAPABILITY_LIST = 257;
+    public static final int ANQP_VENUE_NAME = 258;
+    public static final int ANQP_EMERGENCY_NUMBER = 259;
+    public static final int ANQP_NWK_AUTH_TYPE = 260;
+    public static final int ANQP_ROAMING_CONSORTIUM = 261;
+    public static final int ANQP_IP_ADDR_AVAILABILITY = 262;
+    public static final int ANQP_NAI_REALM = 263;
+    public static final int ANQP_3GPP_NETWORK = 264;
+    public static final int ANQP_GEO_LOC = 265;
+    public static final int ANQP_CIVIC_LOC = 266;
+    public static final int ANQP_LOC_URI = 267;
+    public static final int ANQP_DOM_NAME = 268;
+    public static final int ANQP_EMERGENCY_ALERT = 269;
+    public static final int ANQP_TDLS_CAP = 270;
+    public static final int ANQP_EMERGENCY_NAI = 271;
+    public static final int ANQP_NEIGHBOR_REPORT = 272;
+    public static final int ANQP_VENDOR_SPEC = 56797;
+
+    public static final int HS_QUERY_LIST = 1;
+    public static final int HS_CAPABILITY_LIST = 2;
+    public static final int HS_FRIENDLY_NAME = 3;
+    public static final int HS_WAN_METRICS = 4;
+    public static final int HS_CONN_CAPABILITY = 5;
+    public static final int HS_NAI_HOME_REALM_QUERY = 6;
+    public static final int HS_OPERATING_CLASS = 7;
+    public static final int HS_OSU_PROVIDERS = 8;
+    public static final int HS_ICON_REQUEST = 10;
+    public static final int HS_ICON_FILE = 11;
+
+    private final int mVendorId;
+    private final int mElementId;
+    private final byte[] mPayload;
+
+    public AnqpInformationElement(int vendorId, int elementId, byte[] payload) {
+        mVendorId = vendorId;
+        mElementId = elementId;
+        mPayload = payload;
+    }
+
+    public int getVendorId() {
+        return mVendorId;
+    }
+
+    public int getElementId() {
+        return mElementId;
+    }
+
+    public byte[] getPayload() {
+        return mPayload;
+    }
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index bad4e9c..1c7a308 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -20,7 +20,7 @@
 import android.net.wifi.WifiInfo;
 import android.net.wifi.ScanSettings;
 import android.net.wifi.ScanResult;
-import android.net.wifi.ScanInfo;
+import android.net.wifi.PasspointManagementObjectDefinition;
 import android.net.wifi.WifiConnectionStatistics;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.Network;
@@ -50,6 +50,17 @@
 
     int addOrUpdateNetwork(in WifiConfiguration config);
 
+    int addPasspointManagementObject(String mo);
+
+    int modifyPasspointManagementObject(String fqdn,
+                                        in List<PasspointManagementObjectDefinition> mos);
+
+    void queryPasspointIcon(long bssid, String fileName);
+
+    int matchProviderWithCurrentNetwork(String fqdn);
+
+    void deauthenticateNetwork(long holdoff, boolean ess);
+
     boolean removeNetwork(int netId);
 
     boolean enableNetwork(int netId, boolean disableOthers);
@@ -64,10 +75,6 @@
 
     void disconnect();
 
-    List<ScanInfo> getScanInfos(String callingPackage);
-
-    void setOsuSelection(int osuID);
-
     void reconnect();
 
     void reassociate();
diff --git a/wifi/java/android/net/wifi/ScanInfo.aidl b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl
similarity index 84%
rename from wifi/java/android/net/wifi/ScanInfo.aidl
rename to wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl
index 18ae508..eb7cc39 100644
--- a/wifi/java/android/net/wifi/ScanInfo.aidl
+++ b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2015, The Android Open Source Project
+ * 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.
@@ -16,4 +16,4 @@
 
 package android.net.wifi;
 
-parcelable ScanInfo;
+parcelable PasspointManagementObjectDefinition;
diff --git a/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
new file mode 100644
index 0000000..611ed15
--- /dev/null
+++ b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 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.net.wifi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This object describes a partial tree structure in the Hotspot 2.0 release 2 management object.
+ * The object is used during subscription remediation to modify parts of an existing PPS MO
+ * tree (Hotspot 2.0 specification section 9.1).
+ * @hide
+ */
+public class PasspointManagementObjectDefinition implements Parcelable {
+    private final String mBaseUri;
+    private final String mUrn;
+    private final String mMoTree;
+
+    public PasspointManagementObjectDefinition(String baseUri, String urn, String moTree) {
+        mBaseUri = baseUri;
+        mUrn = urn;
+        mMoTree = moTree;
+    }
+
+    public String getmBaseUri() {
+        return mBaseUri;
+    }
+
+    public String getmUrn() {
+        return mUrn;
+    }
+
+    public String getmMoTree() {
+        return mMoTree;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mBaseUri);
+        dest.writeString(mUrn);
+        dest.writeString(mMoTree);
+    }
+
+    /**
+     * Implement the Parcelable interface {@hide}
+     */
+    public static final Creator<PasspointManagementObjectDefinition> CREATOR =
+            new Creator<PasspointManagementObjectDefinition>() {
+                public PasspointManagementObjectDefinition createFromParcel(Parcel in) {
+                    return new PasspointManagementObjectDefinition(
+                            in.readString(),    /* base URI */
+                            in.readString(),    /* URN */
+                            in.readString()     /* Tree XML */
+                    );
+                }
+
+                public PasspointManagementObjectDefinition[] newArray(int size) {
+                    return new PasspointManagementObjectDefinition[size];
+                }
+            };
+}
+
diff --git a/wifi/java/android/net/wifi/ScanInfo.java b/wifi/java/android/net/wifi/ScanInfo.java
deleted file mode 100644
index 39186fa..0000000
--- a/wifi/java/android/net/wifi/ScanInfo.java
+++ /dev/null
@@ -1,189 +0,0 @@
-package android.net.wifi;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class ScanInfo implements Parcelable {
-    private final ScanResult mScanResult;
-
-    private final long mBSSID;          // The BSSID of the best AP with an SSID matching the OSU
-    private final int mRSSI;            // RSSI of the AP with BSSID
-    private final String mSSID;         // The SSID to connect to for an OSU connection.
-    private final String mName;
-    private final String mServiceDescription;
-    private final String mIconType;
-    private final byte[] mIconData;
-    private final int mOSUIdentity;
-
-    public ScanInfo(ScanResult scanResult) {
-        mScanResult = scanResult;
-
-        mBSSID = -1;
-        mRSSI = -1;
-        mSSID = null;
-        mName = null;
-        mServiceDescription = null;
-        mIconType = null;
-        mIconData = null;
-        mOSUIdentity = -1;
-    }
-
-    public ScanInfo(long BSSID, int rssi, String SSID, String name, String serviceDescription,
-                    String iconType, byte[] iconData, int OSUIdentity) {
-        mBSSID = BSSID;
-        mRSSI = rssi;
-        mSSID = SSID;
-        mName = name;
-        mServiceDescription = serviceDescription;
-        mIconType = iconType;
-        mIconData = iconData;
-        mOSUIdentity = OSUIdentity;
-
-        mScanResult = null;
-    }
-
-    /**
-     * Get the scan result of this ScanInfo.
-     * @return The ScanResult, if this ScanInfo contains a one. If the ScanInfo contains
-     * OSU information getScanResult will return null.
-     */
-    public ScanResult getScanResult() {
-        return mScanResult;
-    }
-
-    /**
-     * OSU only: The BSSID of the AP who advertises the OSU SSID. This value is not guaranteed to
-     * be correct; In the somewhat unlikely case that multiple APs advertise OSU SSIDs that matches
-     * an OSU information element returned through ANQP and one of those is not related to an OSU
-     * there is a (slight) risk that the BSSID is for a "spoof" OSU.
-     * The matching algorithm that produces the ScanInfo objects makes a best effort to get the
-     * matching right though and since it is (a) fair to assume that the OSU SSID resides on the
-     * same AP as the one advertising the OSU information, and (b) BSSIDs for multi-SSID APs are
-     * typically adjacent to each other, matching will prefer the BSSID closest to the advertising
-     * APs BSSID if multiple SSIDs match.
-     * @return The BSSID.
-     */
-    public long getBssid() {
-        return mBSSID;
-    }
-
-    /**
-     * OSU only.
-     * @return The signal level of the AP associated with the BSSID from getBSSID.
-     */
-    public int getRssi() {
-        return mRSSI;
-    }
-
-    /**
-     * OSU only.
-     * @return The SSID of the AP to which to associate to establish an OSU connection.
-     */
-    public String getSsid() {
-        return mSSID;
-    }
-
-    /**
-     * OSU only.
-     * @return The name of the Service Provider of the OSU.
-     */
-    public String getName() {
-        return mName;
-    }
-
-    /**
-     * OSU only.
-     * @return The service description of the OSU.
-     */
-    public String getServiceDescription() {
-        return mServiceDescription;
-    }
-
-    /**
-     * OSU only.
-     * Get the type of icon that icon data represents, e.g. JPG, PNG etc. This field is formatted
-     * using standard MIME encodings per RFC-4288 and IANA MIME media types.
-     * @return The icon type in icon data.
-     */
-    public String getIconType() {
-        return mIconType;
-    }
-
-    /**
-     * OSU only.
-     * @return The binary data of the icon.
-     */
-    public byte[] getIconData() {
-        return mIconData;
-    }
-
-    /**
-     * OSU only.
-     * @return a unique identity for the OSU. This value is generated by the framework and should
-     * be used to uniquely identify a specific OSU. Please note that values may be reused after
-     * a very long time-span (in any normal scenario, likely years) and implementations should make
-     * sure to not rely on any long term persisted values.
-     */
-    public int getOsuIdentity() {
-        return mOSUIdentity;
-    }
-
-    private static final int ScanResultMarker = 0;
-    private static final int OSUMarker = 1;
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface {@hide} */
-    public static final Creator<ScanInfo> CREATOR =
-            new Creator<ScanInfo>() {
-                @Override
-                public ScanInfo createFromParcel(Parcel source) {
-                    int marker = source.readInt();
-                    if (marker == ScanResultMarker) {
-                        return new ScanInfo(ScanResult.CREATOR.createFromParcel(source));
-                    }
-                    else if (marker == OSUMarker) {
-                        return new ScanInfo(
-                                source.readLong(),
-                                source.readInt(),
-                                source.readString(),
-                                source.readString(),
-                                source.readString(),
-                                source.readString(),
-                                source.createByteArray(),
-                                source.readInt()
-                                );
-                    }
-                    else {
-                        throw new RuntimeException("Bad ScanInfo data");
-                    }
-                }
-
-                @Override
-                public ScanInfo[] newArray(int size) {
-                    return new ScanInfo[0];
-                }
-            };
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        if (mScanResult != null) {
-            dest.writeInt(ScanResultMarker);
-            mScanResult.writeToParcel(dest, flags);
-            return;
-        }
-
-        dest.writeInt(OSUMarker);
-        dest.writeLong(mBSSID);
-        dest.writeInt(mRSSI);
-        dest.writeString(mSSID);
-        dest.writeString(mName);
-        dest.writeString(mServiceDescription);
-        dest.writeString(mIconType);
-        dest.writeByteArray(mIconData);
-        dest.writeInt(mOSUIdentity);
-    }
-}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 1f05339..4a86c59 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -43,6 +43,19 @@
      * The address of the access point.
      */
     public String BSSID;
+
+    /**
+     * The HESSID from the beacon.
+     * @hide
+     */
+    public long hessid;
+
+    /**
+     * The ANQP Domain ID from the Hotspot 2.0 Indication element, if present.
+     * @hide
+     */
+    public int anqpDomainId;
+
     /**
      * Describes the authentication, key management, and encryption schemes
      * supported by the access point.
@@ -341,14 +354,27 @@
     /** information elements found in the beacon
      * @hide
      */
-    public InformationElement informationElements[];
+    public InformationElement[] informationElements;
+
+    /** ANQP response elements.
+     * @hide
+     */
+    public AnqpInformationElement[] anqpElements;
 
     /** {@hide} */
-    public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
-            long tsf) {
+    public ScanResult(WifiSsid wifiSsid, String BSSID, long hessid, int anqpDomainId,
+            byte[] osuProviders, String caps, int level, int frequency, long tsf) {
         this.wifiSsid = wifiSsid;
         this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
         this.BSSID = BSSID;
+        this.hessid = hessid;
+        this.anqpDomainId = anqpDomainId;
+        if (osuProviders != null) {
+            this.anqpElements = new AnqpInformationElement[1];
+            this.anqpElements[0] =
+                    new AnqpInformationElement(AnqpInformationElement.HOTSPOT20_VENDOR_ID,
+                            AnqpInformationElement.HS_OSU_PROVIDERS, osuProviders);
+        }
         this.capabilities = caps;
         this.level = level;
         this.frequency = frequency;
@@ -380,11 +406,14 @@
     }
 
     /** {@hide} */
-    public ScanResult(String Ssid, String BSSID, String caps, int level, int frequency,
+    public ScanResult(String Ssid, String BSSID, long hessid, int anqpDomainId, String caps,
+            int level, int frequency,
             long tsf, int distCm, int distSdCm, int channelWidth, int centerFreq0, int centerFreq1,
             boolean is80211McRTTResponder) {
         this.SSID = Ssid;
         this.BSSID = BSSID;
+        this.hessid = hessid;
+        this.anqpDomainId = anqpDomainId;
         this.capabilities = caps;
         this.level = level;
         this.frequency = frequency;
@@ -402,11 +431,12 @@
     }
 
     /** {@hide} */
-    public ScanResult(WifiSsid wifiSsid, String Ssid, String BSSID, String caps, int level,
+    public ScanResult(WifiSsid wifiSsid, String Ssid, String BSSID, long hessid, int anqpDomainId,
+                  String caps, int level,
                   int frequency, long tsf, int distCm, int distSdCm, int channelWidth,
                   int centerFreq0, int centerFreq1, boolean is80211McRTTResponder) {
-        this(Ssid, BSSID, caps,level, frequency, tsf, distCm, distSdCm, channelWidth, centerFreq0,
-                centerFreq1, is80211McRTTResponder);
+        this(Ssid, BSSID, hessid, anqpDomainId, caps, level, frequency, tsf, distCm,
+                distSdCm, channelWidth, centerFreq0, centerFreq1, is80211McRTTResponder);
         this.wifiSsid = wifiSsid;
     }
 
@@ -416,6 +446,10 @@
             wifiSsid = source.wifiSsid;
             SSID = source.SSID;
             BSSID = source.BSSID;
+            hessid = source.hessid;
+            anqpDomainId = source.anqpDomainId;
+            informationElements = source.informationElements;
+            anqpElements = source.anqpElements;
             capabilities = source.capabilities;
             level = source.level;
             frequency = source.frequency;
@@ -496,6 +530,8 @@
         }
         dest.writeString(SSID);
         dest.writeString(BSSID);
+        dest.writeLong(hessid);
+        dest.writeInt(anqpDomainId);
         dest.writeString(capabilities);
         dest.writeInt(level);
         dest.writeInt(frequency);
@@ -532,6 +568,18 @@
             for (int i = 0; i < anqpLines.size(); i++) {
                 dest.writeString(anqpLines.get(i));
             }
+        }
+        else {
+            dest.writeInt(0);
+        }
+        if (anqpElements != null) {
+            dest.writeInt(anqpElements.length);
+            for (AnqpInformationElement element : anqpElements) {
+                dest.writeInt(element.getVendorId());
+                dest.writeInt(element.getElementId());
+                dest.writeInt(element.getPayload().length);
+                dest.writeByteArray(element.getPayload());
+            }
         } else {
             dest.writeInt(0);
         }
@@ -546,19 +594,22 @@
                     wifiSsid = WifiSsid.CREATOR.createFromParcel(in);
                 }
                 ScanResult sr = new ScanResult(
-                    wifiSsid,
-                    in.readString(),                    /* SSID  */
-                    in.readString(),                    /* BSSID */
-                    in.readString(),                    /* capabilities */
-                    in.readInt(),                       /* level */
-                    in.readInt(),                       /* frequency */
-                    in.readLong(),                      /* timestamp */
-                    in.readInt(),                       /* distanceCm */
-                    in.readInt(),                       /* distanceSdCm */
-                    in.readInt(),                       /* channelWidth */
-                    in.readInt(),                       /* centerFreq0 */
-                    in.readInt(),                       /* centerFreq1 */
-                    false                               /* rtt responder, fixed with flags below */
+                        wifiSsid,
+                        in.readString(),                    /* SSID  */
+                        in.readString(),                    /* BSSID */
+                        in.readLong(),                      /* HESSID */
+                        in.readInt(),                       /* ANQP Domain ID */
+                        in.readString(),                    /* capabilities */
+                        in.readInt(),                       /* level */
+                        in.readInt(),                       /* frequency */
+                        in.readLong(),                      /* timestamp */
+                        in.readInt(),                       /* distanceCm */
+                        in.readInt(),                       /* distanceSdCm */
+                        in.readInt(),                       /* channelWidth */
+                        in.readInt(),                       /* centerFreq0 */
+                        in.readInt(),                       /* centerFreq1 */
+                        false                               /* rtt responder,
+                                                               fixed with flags below */
                 );
 
                 sr.seen = in.readLong();
@@ -590,6 +641,19 @@
                         sr.anqpLines.add(in.readString());
                     }
                 }
+                n = in.readInt();
+                if (n != 0) {
+                    sr.anqpElements = new AnqpInformationElement[n];
+                    for (int i = 0; i < n; i++) {
+                        int vendorId = in.readInt();
+                        int elementId = in.readInt();
+                        int len = in.readInt();
+                        byte[] payload = new byte[len];
+                        in.readByteArray(payload);
+                        sr.anqpElements[i] =
+                                new AnqpInformationElement(vendorId, elementId, payload);
+                    }
+                }
                 return sr;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 4c22460..a9132a5 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -58,6 +58,9 @@
     /** {@hide} */
     public static final int INVALID_NETWORK_ID = -1;
 
+    /** {@hide} */
+    private String mPasspointManagementObjectTree;
+
     /**
      * Recognized key management schemes.
      */
@@ -80,11 +83,16 @@
           */
         @SystemApi
         public static final int WPA2_PSK = 4;
+        /**
+         * Hotspot 2.0 r2 OSEN:
+         * @hide
+         */
+        public static final int OSEN = 5;
 
         public static final String varName = "key_mgmt";
 
         public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X",
-                "WPA2_PSK" };
+                "WPA2_PSK", "OSEN" };
     }
 
     /**
@@ -97,10 +105,14 @@
         public static final int WPA = 0;
         /** WPA2/IEEE 802.11i */
         public static final int RSN = 1;
+        /** HS2.0 r2 OSEN
+         * @hide
+         */
+        public static final int OSEN = 2;
 
         public static final String varName = "proto";
 
-        public static final String[] strings = { "WPA", "RSN" };
+        public static final String[] strings = { "WPA", "RSN", "OSEN" };
     }
 
     /**
@@ -159,10 +171,15 @@
         public static final int TKIP = 2;
         /** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */
         public static final int CCMP = 3;
+        /** Hotspot 2.0 r2 OSEN
+         * @hide
+         */
+        public static final int GTK_NOT_USED = 4;
 
         public static final String varName = "group";
 
-        public static final String[] strings = { "WEP40", "WEP104", "TKIP", "CCMP" };
+        public static final String[] strings =
+                { "WEP40", "WEP104", "TKIP", "CCMP", "GTK_NOT_USED" };
     }
 
     /** Possible status of a network configuration. */
@@ -1735,6 +1752,16 @@
         return shared || (UserHandle.getUserId(creatorUid) == userId);
     }
 
+    /** @hide */
+    public void setPasspointManagementObjectTree(String passpointManagementObjectTree) {
+        mPasspointManagementObjectTree = passpointManagementObjectTree;
+    }
+
+    /** @hide */
+    public String getMoTree() {
+        return mPasspointManagementObjectTree;
+    }
+
     /** copy constructor {@hide} */
     public WifiConfiguration(WifiConfiguration source) {
         if (source != null) {
@@ -1886,6 +1913,7 @@
         dest.writeInt(numNoInternetAccessReports);
         dest.writeInt(noInternetAccessExpected ? 1 : 0);
         dest.writeInt(shared ? 1 : 0);
+        dest.writeString(mPasspointManagementObjectTree);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -1954,6 +1982,7 @@
                 config.numNoInternetAccessReports = in.readInt();
                 config.noInternetAccessExpected = in.readInt() != 0;
                 config.shared = in.readInt() != 0;
+                config.mPasspointManagementObjectTree = in.readString();
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 605e8c0..a0dbd85 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -273,8 +273,11 @@
         public static final int AKA     = 5;
         /** EAP-Authentication and Key Agreement Prime */
         public static final int AKA_PRIME = 6;
+        /** Hotspot 2.0 r2 OSEN */
+        public static final int UNAUTH_TLS = 7;
         /** @hide */
-        public static final String[] strings = { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'" };
+        public static final String[] strings =
+                { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'", "WFA-UNAUTH-TLS" };
 
         /** Prevent initialization */
         private Eap() {}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index db40cd8..2ad3c2e 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -26,28 +26,26 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.wifi.ScanSettings;
 import android.os.Binder;
 import android.os.Build;
-import android.os.IBinder;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Messenger;
 import android.os.RemoteException;
 import android.os.WorkSource;
-import android.os.Messenger;
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.net.InetAddress;
-import java.util.concurrent.CountDownLatch;
-
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 
+import java.net.InetAddress;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * This class provides the primary API for managing all aspects of Wi-Fi
@@ -114,6 +112,51 @@
     public static final int WIFI_CREDENTIAL_FORGOT = 1;
 
     /**
+     * Broadcast intent action indicating that the a Passpoint release 2 icon has been received.
+     * @hide
+     */
+    public static final String PASSPOINT_ICON_RECEIVED_ACTION =
+            "android.net.wifi.PASSPOINT_ICON_RECEIVED";
+    /** @hide */
+    public static final String EXTRA_PASSPOINT_ICON_BSSID = "bssid";
+    /** @hide */
+    public static final String EXTRA_PASSPOINT_ICON_FILE = "file";
+    /** @hide */
+    public static final String EXTRA_PASSPOINT_ICON_DATA = "icon";
+
+    /**
+     * Broadcast intent action indicating that the a Passpoint release
+     * 2 WNM frame has been received.
+     * @hide
+     */
+    public static final String PASSPOINT_WNM_FRAME_RECEIVED_ACTION =
+            "android.net.wifi.PASSPOINT_WNM_FRAME_RECEIVED";
+    /**
+     * Originating BSS
+     * @hide */
+    public static final String EXTRA_PASSPOINT_WNM_BSSID = "bssid";
+    /**
+     * SOAP-XML or OMA-DM
+     * @hide */
+    public static final String EXTRA_PASSPOINT_WNM_METHOD = "method";
+    /**
+     * Type of Passpoint match
+     * @hide */
+    public static final String EXTRA_PASSPOINT_WNM_PPOINT_MATCH = "match";
+    /**
+     * String
+     * @hide */
+    public static final String EXTRA_PASSPOINT_WNM_URL = "url";
+    /**
+     * Boolean true=ess, false=bss
+     * @hide */
+    public static final String EXTRA_PASSPOINT_WNM_ESS = "ess";
+    /**
+     * Delay in seconds
+     * @hide */
+    public static final String EXTRA_PASSPOINT_WNM_DELAY = "delay";
+
+    /**
      * Broadcast intent action indicating that Wi-Fi has been enabled, disabled,
      * enabling, disabling, or unknown. One extra provides this state as an int.
      * Another extra provides the previous state, if available.
@@ -779,6 +822,76 @@
     }
 
     /**
+     * Add a Hotspot 2.0 release 2 Management Object
+     * @param mo The MO in XML form
+     * @return -1 for failure
+     * @hide
+     */
+    public int addPasspointManagementObject(String mo) {
+        try {
+            return mService.addPasspointManagementObject(mo);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Modify a Hotspot 2.0 release 2 Management Object
+     * @param fqdn The FQDN of the service provider
+     * @param mos A List of MO definitions to be updated
+     * @return the number of nodes updated, or -1 for failure
+     * @hide
+     */
+    public int modifyPasspointManagementObject(String fqdn,
+                                               List<PasspointManagementObjectDefinition> mos) {
+        try {
+            return mService.modifyPasspointManagementObject(fqdn, mos);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Query for a Hotspot 2.0 release 2 OSU icon
+     * @param bssid The BSSID of the AP
+     * @param fileName Icon file name
+     * @hide
+     */
+    public void queryPasspointIcon(long bssid, String fileName) {
+        try {
+            mService.queryPasspointIcon(bssid, fileName);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Match the currently associated network against the SP matching the given FQDN
+     * @param fqdn FQDN of the SP
+     * @return ordinal [HomeProvider, RoamingProvider, Incomplete, None, Declined]
+     * @hide
+     */
+    public int matchProviderWithCurrentNetwork(String fqdn) {
+        try {
+            return mService.matchProviderWithCurrentNetwork(fqdn);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Deauthenticate and set the re-authentication hold off time for the current network
+     * @param holdoff hold off time in milliseconds
+     * @param ess set if the hold off pertains to an ESS rather than a BSS
+     * @hide
+     */
+    public void deauthenticateNetwork(long holdoff, boolean ess) {
+        try {
+            mService.deauthenticateNetwork(holdoff, ess);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Remove the specified network from the list of configured networks.
      * This may result in the asynchronous delivery of state change
      * events.
@@ -1199,30 +1312,6 @@
     }
 
     /**
-     * An augmented version of getScanResults that returns ScanResults as well as OSU information
-     * wrapped in ScanInfo objects.
-     * @return
-     */
-    public List<ScanInfo> getScanInfos() {
-        try {
-            return mService.getScanInfos(mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Notify the OSU framework about the currently selected OSU.
-     * @param osuID The OSU ID from ScanInfo.getOsuIdentity()
-     */
-    public void setOsuSelection(int osuID) {
-        try {
-            mService.setOsuSelection(osuID);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
      * Check if scanning is always available.
      *
      * If this return {@code true}, apps can issue {@link #startScan} and fetch scan results