Merge "AAPT2: Allow specifying non-final IDS when building 'legacy' style libraries"
diff --git a/api/current.txt b/api/current.txt
index 9a5ffdc..e0b92f4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5807,6 +5807,7 @@
     method public java.lang.String getLongSupportMessage(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
+    method public int getOrganizationColor(android.content.ComponentName);
     method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String);
     method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
     method public long getPasswordExpiration(android.content.ComponentName);
@@ -5875,6 +5876,7 @@
     method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
     method public void setMaximumTimeToLock(android.content.ComponentName, long);
+    method public void setOrganizationColor(android.content.ComponentName, int);
     method public boolean setPackageSuspended(android.content.ComponentName, java.lang.String, boolean);
     method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
     method public void setPasswordHistoryLength(android.content.ComponentName, int);
@@ -8427,6 +8429,7 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
+    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -19297,6 +19300,7 @@
     method public void adjustVolume(int, int);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
+    method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public int getMode();
     method public java.lang.String getParameters(java.lang.String);
@@ -19319,6 +19323,7 @@
     method public void playSoundEffect(int);
     method public void playSoundEffect(int, float);
     method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
+    method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
     method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
@@ -19342,6 +19347,7 @@
     method public void stopBluetoothSco();
     method public void unloadSoundEffects();
     method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
+    method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
     method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void unregisterRemoteControlClient(android.media.RemoteControlClient);
@@ -19438,6 +19444,11 @@
     field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
   }
 
+  public static abstract class AudioManager.AudioRecordingCallback {
+    ctor public AudioManager.AudioRecordingCallback();
+    method public void onRecordConfigChanged();
+  }
+
   public static abstract interface AudioManager.OnAudioFocusChangeListener {
     method public abstract void onAudioFocusChange(int);
   }
@@ -19508,6 +19519,14 @@
     method public abstract void onRoutingChanged(android.media.AudioRecord);
   }
 
+  public class AudioRecordConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAudioSessionId();
+    method public int getClientAudioSource();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR;
+  }
+
   public abstract interface AudioRouting {
     method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public abstract android.media.AudioDeviceInfo getPreferredDevice();
@@ -22742,6 +22761,7 @@
     method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
     method public deprecated int getNetworkPreference();
     method public static deprecated android.net.Network getProcessDefaultNetwork();
+    method public int getRestrictBackgroundStatus();
     method public boolean isActiveNetworkMetered();
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
@@ -22776,6 +22796,9 @@
     field public static final java.lang.String EXTRA_NO_CONNECTIVITY = "noConnectivity";
     field public static final java.lang.String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
     field public static final java.lang.String EXTRA_REASON = "reason";
+    field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+    field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+    field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
     field public static final int TYPE_BLUETOOTH = 7; // 0x7
     field public static final int TYPE_DUMMY = 8; // 0x8
     field public static final int TYPE_ETHERNET = 9; // 0x9
@@ -22802,6 +22825,9 @@
     method public abstract void onNetworkActive();
   }
 
+  public static abstract class ConnectivityManager.RestrictBackgroundStatus implements java.lang.annotation.Annotation {
+  }
+
   public class Credentials {
     ctor public Credentials(int, int, int);
     method public int getGid();
@@ -43590,6 +43616,7 @@
     field public android.os.Bundle extras;
     field public int fieldId;
     field public java.lang.String fieldName;
+    field public android.util.LocaleList hintLocales;
     field public java.lang.CharSequence hintText;
     field public int imeOptions;
     field public int initialCapsMode;
@@ -43597,7 +43624,6 @@
     field public int initialSelStart;
     field public int inputType;
     field public java.lang.CharSequence label;
-    field public android.util.LocaleList locales;
     field public java.lang.String packageName;
     field public java.lang.String privateImeOptions;
   }
@@ -44074,6 +44100,30 @@
     method public abstract android.view.View getFullScreenView(int, android.content.Context);
   }
 
+  public class ServiceWorkerClient {
+    ctor public ServiceWorkerClient();
+    method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerController {
+    ctor public ServiceWorkerController();
+    method public static android.webkit.ServiceWorkerController getInstance();
+    method public abstract android.webkit.ServiceWorkerWebSettings getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(android.webkit.ServiceWorkerClient);
+  }
+
+  public abstract class ServiceWorkerWebSettings {
+    ctor public ServiceWorkerWebSettings();
+    method public abstract boolean getAllowContentAccess();
+    method public abstract boolean getAllowFileAccess();
+    method public abstract boolean getBlockNetworkLoads();
+    method public abstract int getCacheMode();
+    method public abstract void setAllowContentAccess(boolean);
+    method public abstract void setAllowFileAccess(boolean);
+    method public abstract void setBlockNetworkLoads(boolean);
+    method public abstract void setCacheMode(int);
+  }
+
   public class SslErrorHandler extends android.os.Handler {
     method public void cancel();
     method public void proceed();
@@ -46844,6 +46894,7 @@
     method public int getHyphenationFrequency();
     method public int getImeActionId();
     method public java.lang.CharSequence getImeActionLabel();
+    method public android.util.LocaleList getImeHintLocales();
     method public int getImeOptions();
     method public boolean getIncludeFontPadding();
     method public android.os.Bundle getInputExtras(boolean);
@@ -46950,6 +47001,7 @@
     method public void setHorizontallyScrolling(boolean);
     method public void setHyphenationFrequency(int);
     method public void setImeActionLabel(java.lang.CharSequence, int);
+    method public void setImeHintLocales(android.util.LocaleList);
     method public void setImeOptions(int);
     method public void setIncludeFontPadding(boolean);
     method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -47903,10 +47955,8 @@
   }
 
   public final class Console implements java.io.Flushable {
-    method public static java.io.Console console();
     method public void flush();
     method public java.io.Console format(java.lang.String, java.lang.Object...);
-    method public static synchronized java.io.Console getConsole();
     method public java.io.Console printf(java.lang.String, java.lang.Object...);
     method public java.lang.String readLine(java.lang.String, java.lang.Object...);
     method public java.lang.String readLine();
@@ -48190,7 +48240,6 @@
   public class InterruptedIOException extends java.io.IOException {
     ctor public InterruptedIOException();
     ctor public InterruptedIOException(java.lang.String);
-    ctor public InterruptedIOException(java.lang.Throwable);
     field public int bytesTransferred;
   }
 
@@ -48861,7 +48910,6 @@
     method public long longValue();
     method public static byte parseByte(java.lang.String, int) throws java.lang.NumberFormatException;
     method public static byte parseByte(java.lang.String) throws java.lang.NumberFormatException;
-    method public static java.lang.String toHexString(byte, boolean);
     method public static java.lang.String toString(byte);
     method public static java.lang.Byte valueOf(byte);
     method public static java.lang.Byte valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -51013,7 +51061,6 @@
   public class BindException extends java.net.SocketException {
     ctor public BindException(java.lang.String);
     ctor public BindException();
-    ctor public BindException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract class CacheRequest {
@@ -51031,7 +51078,6 @@
   public class ConnectException extends java.net.SocketException {
     ctor public ConnectException(java.lang.String);
     ctor public ConnectException();
-    ctor public ConnectException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract class ContentHandler {
@@ -51111,7 +51157,6 @@
     method public void disconnect();
     method public synchronized boolean getBroadcast() throws java.net.SocketException;
     method public java.nio.channels.DatagramChannel getChannel();
-    method public final java.io.FileDescriptor getFileDescriptor$();
     method public java.net.InetAddress getInetAddress();
     method public java.net.InetAddress getLocalAddress();
     method public int getLocalPort();
@@ -51188,7 +51233,6 @@
     method public boolean hasExpired();
     method public boolean isHttpOnly();
     method public static java.util.List<java.net.HttpCookie> parse(java.lang.String);
-    method public static java.util.List<java.net.HttpCookie> parse(java.lang.String, boolean);
     method public void setComment(java.lang.String);
     method public void setCommentURL(java.lang.String);
     method public void setDiscard(boolean);
@@ -51281,9 +51325,6 @@
   }
 
   public final class Inet4Address extends java.net.InetAddress {
-    field public static final java.net.InetAddress ALL;
-    field public static final java.net.InetAddress ANY;
-    field public static final java.net.InetAddress LOOPBACK;
   }
 
   public final class Inet6Address extends java.net.InetAddress {
@@ -51292,8 +51333,6 @@
     method public int getScopeId();
     method public java.net.NetworkInterface getScopedInterface();
     method public boolean isIPv4CompatibleAddress();
-    field public static final java.net.InetAddress ANY;
-    field public static final java.net.InetAddress LOOPBACK;
   }
 
   public class InetAddress implements java.io.Serializable {
@@ -51421,13 +51460,11 @@
   public class PortUnreachableException extends java.net.SocketException {
     ctor public PortUnreachableException(java.lang.String);
     ctor public PortUnreachableException();
-    ctor public PortUnreachableException(java.lang.String, java.lang.Throwable);
   }
 
   public class ProtocolException extends java.io.IOException {
     ctor public ProtocolException(java.lang.String);
     ctor public ProtocolException();
-    ctor public ProtocolException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract interface ProtocolFamily {
@@ -51562,8 +51599,6 @@
   public class SocketException extends java.io.IOException {
     ctor public SocketException(java.lang.String);
     ctor public SocketException();
-    ctor public SocketException(java.lang.Throwable);
-    ctor public SocketException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract class SocketImpl implements java.net.SocketOptions {
@@ -51633,8 +51668,6 @@
   public class SocketTimeoutException extends java.io.InterruptedIOException {
     ctor public SocketTimeoutException(java.lang.String);
     ctor public SocketTimeoutException();
-    ctor public SocketTimeoutException(java.lang.Throwable);
-    ctor public SocketTimeoutException(java.lang.String, java.lang.Throwable);
   }
 
   public final class StandardProtocolFamily extends java.lang.Enum implements java.net.ProtocolFamily {
@@ -59579,7 +59612,6 @@
     ctor public JarFile(java.io.File, boolean, int) throws java.io.IOException;
     method public java.util.jar.JarEntry getJarEntry(java.lang.String);
     method public java.util.jar.Manifest getManifest() throws java.io.IOException;
-    method public boolean hasClassPathAttribute() throws java.io.IOException;
     field public static final java.lang.String MANIFEST_NAME = "META-INF/MANIFEST.MF";
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index 3bafa01..6b84f56 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5947,6 +5947,7 @@
     method public java.lang.String getLongSupportMessage(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
+    method public int getOrganizationColor(android.content.ComponentName);
     method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String);
     method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
     method public long getPasswordExpiration(android.content.ComponentName);
@@ -6021,6 +6022,7 @@
     method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
     method public void setMaximumTimeToLock(android.content.ComponentName, long);
+    method public void setOrganizationColor(android.content.ComponentName, int);
     method public boolean setPackageSuspended(android.content.ComponentName, java.lang.String, boolean);
     method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
     method public void setPasswordHistoryLength(android.content.ComponentName, int);
@@ -6293,6 +6295,7 @@
     method public static void dataChanged(java.lang.String);
     method public long getAvailableRestoreToken(java.lang.String);
     method public java.lang.String getCurrentTransport();
+    method public boolean isAppEligibleForBackup(java.lang.String);
     method public boolean isBackupEnabled();
     method public java.lang.String[] listAllTransports();
     method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver);
@@ -6342,6 +6345,7 @@
     method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor);
     method public int getRestoreData(android.os.ParcelFileDescriptor);
     method public int initializeDevice();
+    method public boolean isAppEligibleForBackup(android.content.pm.PackageInfo, boolean);
     method public java.lang.String name();
     method public android.app.backup.RestoreDescription nextRestorePackage();
     method public int performBackup(android.content.pm.PackageInfo, android.os.ParcelFileDescriptor, int);
@@ -8726,6 +8730,7 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
+    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -20623,6 +20628,7 @@
     method public void adjustVolume(int, int);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
+    method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public int getMode();
     method public java.lang.String getParameters(java.lang.String);
@@ -20647,6 +20653,7 @@
     method public void playSoundEffect(int, float);
     method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
     method public int registerAudioPolicy(android.media.audiopolicy.AudioPolicy);
+    method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
     method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
@@ -20673,6 +20680,7 @@
     method public void unloadSoundEffects();
     method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
     method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy);
+    method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
     method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void unregisterRemoteControlClient(android.media.RemoteControlClient);
@@ -20772,6 +20780,11 @@
     field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
   }
 
+  public static abstract class AudioManager.AudioRecordingCallback {
+    ctor public AudioManager.AudioRecordingCallback();
+    method public void onRecordConfigChanged();
+  }
+
   public static abstract interface AudioManager.OnAudioFocusChangeListener {
     method public abstract void onAudioFocusChange(int);
   }
@@ -20845,6 +20858,14 @@
     method public abstract void onRoutingChanged(android.media.AudioRecord);
   }
 
+  public class AudioRecordConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAudioSessionId();
+    method public int getClientAudioSource();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR;
+  }
+
   public abstract interface AudioRouting {
     method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public abstract android.media.AudioDeviceInfo getPreferredDevice();
@@ -24328,6 +24349,7 @@
     method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
     method public deprecated int getNetworkPreference();
     method public static deprecated android.net.Network getProcessDefaultNetwork();
+    method public int getRestrictBackgroundStatus();
     method public boolean isActiveNetworkMetered();
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
@@ -24362,6 +24384,9 @@
     field public static final java.lang.String EXTRA_NO_CONNECTIVITY = "noConnectivity";
     field public static final java.lang.String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
     field public static final java.lang.String EXTRA_REASON = "reason";
+    field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+    field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+    field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
     field public static final int TYPE_BLUETOOTH = 7; // 0x7
     field public static final int TYPE_DUMMY = 8; // 0x8
     field public static final int TYPE_ETHERNET = 9; // 0x9
@@ -24388,6 +24413,9 @@
     method public abstract void onNetworkActive();
   }
 
+  public static abstract class ConnectivityManager.RestrictBackgroundStatus implements java.lang.annotation.Annotation {
+  }
+
   public class Credentials {
     ctor public Credentials(int, int, int);
     method public int getGid();
@@ -46018,6 +46046,7 @@
     field public android.os.Bundle extras;
     field public int fieldId;
     field public java.lang.String fieldName;
+    field public android.util.LocaleList hintLocales;
     field public java.lang.CharSequence hintText;
     field public int imeOptions;
     field public int initialCapsMode;
@@ -46025,7 +46054,6 @@
     field public int initialSelStart;
     field public int inputType;
     field public java.lang.CharSequence label;
-    field public android.util.LocaleList locales;
     field public java.lang.String packageName;
     field public java.lang.String privateImeOptions;
   }
@@ -46556,6 +46584,30 @@
     method public abstract android.view.View getFullScreenView(int, android.content.Context);
   }
 
+  public class ServiceWorkerClient {
+    ctor public ServiceWorkerClient();
+    method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerController {
+    ctor public ServiceWorkerController();
+    method public static android.webkit.ServiceWorkerController getInstance();
+    method public abstract android.webkit.ServiceWorkerWebSettings getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(android.webkit.ServiceWorkerClient);
+  }
+
+  public abstract class ServiceWorkerWebSettings {
+    ctor public ServiceWorkerWebSettings();
+    method public abstract boolean getAllowContentAccess();
+    method public abstract boolean getAllowFileAccess();
+    method public abstract boolean getBlockNetworkLoads();
+    method public abstract int getCacheMode();
+    method public abstract void setAllowContentAccess(boolean);
+    method public abstract void setAllowFileAccess(boolean);
+    method public abstract void setBlockNetworkLoads(boolean);
+    method public abstract void setCacheMode(int);
+  }
+
   public class SslErrorHandler extends android.os.Handler {
     ctor public SslErrorHandler();
     method public void cancel();
@@ -47195,6 +47247,7 @@
     method public abstract android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
     method public abstract android.webkit.CookieManager getCookieManager();
     method public abstract android.webkit.GeolocationPermissions getGeolocationPermissions();
+    method public abstract android.webkit.ServiceWorkerController getServiceWorkerController();
     method public abstract android.webkit.WebViewFactoryProvider.Statics getStatics();
     method public abstract android.webkit.TokenBindingService getTokenBindingService();
     method public abstract android.webkit.WebIconDatabase getWebIconDatabase();
@@ -49598,6 +49651,7 @@
     method public int getHyphenationFrequency();
     method public int getImeActionId();
     method public java.lang.CharSequence getImeActionLabel();
+    method public android.util.LocaleList getImeHintLocales();
     method public int getImeOptions();
     method public boolean getIncludeFontPadding();
     method public android.os.Bundle getInputExtras(boolean);
@@ -49704,6 +49758,7 @@
     method public void setHorizontallyScrolling(boolean);
     method public void setHyphenationFrequency(int);
     method public void setImeActionLabel(java.lang.CharSequence, int);
+    method public void setImeHintLocales(android.util.LocaleList);
     method public void setImeOptions(int);
     method public void setIncludeFontPadding(boolean);
     method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -50657,10 +50712,8 @@
   }
 
   public final class Console implements java.io.Flushable {
-    method public static java.io.Console console();
     method public void flush();
     method public java.io.Console format(java.lang.String, java.lang.Object...);
-    method public static synchronized java.io.Console getConsole();
     method public java.io.Console printf(java.lang.String, java.lang.Object...);
     method public java.lang.String readLine(java.lang.String, java.lang.Object...);
     method public java.lang.String readLine();
@@ -50944,7 +50997,6 @@
   public class InterruptedIOException extends java.io.IOException {
     ctor public InterruptedIOException();
     ctor public InterruptedIOException(java.lang.String);
-    ctor public InterruptedIOException(java.lang.Throwable);
     field public int bytesTransferred;
   }
 
@@ -51615,7 +51667,6 @@
     method public long longValue();
     method public static byte parseByte(java.lang.String, int) throws java.lang.NumberFormatException;
     method public static byte parseByte(java.lang.String) throws java.lang.NumberFormatException;
-    method public static java.lang.String toHexString(byte, boolean);
     method public static java.lang.String toString(byte);
     method public static java.lang.Byte valueOf(byte);
     method public static java.lang.Byte valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -53767,7 +53818,6 @@
   public class BindException extends java.net.SocketException {
     ctor public BindException(java.lang.String);
     ctor public BindException();
-    ctor public BindException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract class CacheRequest {
@@ -53785,7 +53835,6 @@
   public class ConnectException extends java.net.SocketException {
     ctor public ConnectException(java.lang.String);
     ctor public ConnectException();
-    ctor public ConnectException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract class ContentHandler {
@@ -53865,7 +53914,6 @@
     method public void disconnect();
     method public synchronized boolean getBroadcast() throws java.net.SocketException;
     method public java.nio.channels.DatagramChannel getChannel();
-    method public final java.io.FileDescriptor getFileDescriptor$();
     method public java.net.InetAddress getInetAddress();
     method public java.net.InetAddress getLocalAddress();
     method public int getLocalPort();
@@ -53942,7 +53990,6 @@
     method public boolean hasExpired();
     method public boolean isHttpOnly();
     method public static java.util.List<java.net.HttpCookie> parse(java.lang.String);
-    method public static java.util.List<java.net.HttpCookie> parse(java.lang.String, boolean);
     method public void setComment(java.lang.String);
     method public void setCommentURL(java.lang.String);
     method public void setDiscard(boolean);
@@ -54035,9 +54082,6 @@
   }
 
   public final class Inet4Address extends java.net.InetAddress {
-    field public static final java.net.InetAddress ALL;
-    field public static final java.net.InetAddress ANY;
-    field public static final java.net.InetAddress LOOPBACK;
   }
 
   public final class Inet6Address extends java.net.InetAddress {
@@ -54046,8 +54090,6 @@
     method public int getScopeId();
     method public java.net.NetworkInterface getScopedInterface();
     method public boolean isIPv4CompatibleAddress();
-    field public static final java.net.InetAddress ANY;
-    field public static final java.net.InetAddress LOOPBACK;
   }
 
   public class InetAddress implements java.io.Serializable {
@@ -54175,13 +54217,11 @@
   public class PortUnreachableException extends java.net.SocketException {
     ctor public PortUnreachableException(java.lang.String);
     ctor public PortUnreachableException();
-    ctor public PortUnreachableException(java.lang.String, java.lang.Throwable);
   }
 
   public class ProtocolException extends java.io.IOException {
     ctor public ProtocolException(java.lang.String);
     ctor public ProtocolException();
-    ctor public ProtocolException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract interface ProtocolFamily {
@@ -54316,8 +54356,6 @@
   public class SocketException extends java.io.IOException {
     ctor public SocketException(java.lang.String);
     ctor public SocketException();
-    ctor public SocketException(java.lang.Throwable);
-    ctor public SocketException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract class SocketImpl implements java.net.SocketOptions {
@@ -54387,8 +54425,6 @@
   public class SocketTimeoutException extends java.io.InterruptedIOException {
     ctor public SocketTimeoutException(java.lang.String);
     ctor public SocketTimeoutException();
-    ctor public SocketTimeoutException(java.lang.Throwable);
-    ctor public SocketTimeoutException(java.lang.String, java.lang.Throwable);
   }
 
   public final class StandardProtocolFamily extends java.lang.Enum implements java.net.ProtocolFamily {
@@ -62333,7 +62369,6 @@
     ctor public JarFile(java.io.File, boolean, int) throws java.io.IOException;
     method public java.util.jar.JarEntry getJarEntry(java.lang.String);
     method public java.util.jar.Manifest getManifest() throws java.io.IOException;
-    method public boolean hasClassPathAttribute() throws java.io.IOException;
     field public static final java.lang.String MANIFEST_NAME = "META-INF/MANIFEST.MF";
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 5f76739..f003875 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5809,6 +5809,7 @@
     method public java.lang.String getLongSupportMessage(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
+    method public int getOrganizationColor(android.content.ComponentName);
     method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String);
     method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
     method public long getPasswordExpiration(android.content.ComponentName);
@@ -5877,6 +5878,7 @@
     method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
     method public void setMaximumTimeToLock(android.content.ComponentName, long);
+    method public void setOrganizationColor(android.content.ComponentName, int);
     method public boolean setPackageSuspended(android.content.ComponentName, java.lang.String, boolean);
     method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
     method public void setPasswordHistoryLength(android.content.ComponentName, int);
@@ -8432,6 +8434,7 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
+    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -19305,6 +19308,7 @@
     method public void adjustVolume(int, int);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
+    method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public int getMode();
     method public java.lang.String getParameters(java.lang.String);
@@ -19327,6 +19331,7 @@
     method public void playSoundEffect(int);
     method public void playSoundEffect(int, float);
     method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
+    method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
     method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
@@ -19350,6 +19355,7 @@
     method public void stopBluetoothSco();
     method public void unloadSoundEffects();
     method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
+    method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
     method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void unregisterRemoteControlClient(android.media.RemoteControlClient);
@@ -19446,6 +19452,11 @@
     field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
   }
 
+  public static abstract class AudioManager.AudioRecordingCallback {
+    ctor public AudioManager.AudioRecordingCallback();
+    method public void onRecordConfigChanged();
+  }
+
   public static abstract interface AudioManager.OnAudioFocusChangeListener {
     method public abstract void onAudioFocusChange(int);
   }
@@ -19516,6 +19527,14 @@
     method public abstract void onRoutingChanged(android.media.AudioRecord);
   }
 
+  public class AudioRecordConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAudioSessionId();
+    method public int getClientAudioSource();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR;
+  }
+
   public abstract interface AudioRouting {
     method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public abstract android.media.AudioDeviceInfo getPreferredDevice();
@@ -22750,6 +22769,7 @@
     method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
     method public deprecated int getNetworkPreference();
     method public static deprecated android.net.Network getProcessDefaultNetwork();
+    method public int getRestrictBackgroundStatus();
     method public boolean isActiveNetworkMetered();
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
@@ -22784,6 +22804,9 @@
     field public static final java.lang.String EXTRA_NO_CONNECTIVITY = "noConnectivity";
     field public static final java.lang.String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
     field public static final java.lang.String EXTRA_REASON = "reason";
+    field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+    field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+    field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
     field public static final int TYPE_BLUETOOTH = 7; // 0x7
     field public static final int TYPE_DUMMY = 8; // 0x8
     field public static final int TYPE_ETHERNET = 9; // 0x9
@@ -22810,6 +22833,9 @@
     method public abstract void onNetworkActive();
   }
 
+  public static abstract class ConnectivityManager.RestrictBackgroundStatus implements java.lang.annotation.Annotation {
+  }
+
   public class Credentials {
     ctor public Credentials(int, int, int);
     method public int getGid();
@@ -43606,6 +43632,7 @@
     field public android.os.Bundle extras;
     field public int fieldId;
     field public java.lang.String fieldName;
+    field public android.util.LocaleList hintLocales;
     field public java.lang.CharSequence hintText;
     field public int imeOptions;
     field public int initialCapsMode;
@@ -43613,7 +43640,6 @@
     field public int initialSelStart;
     field public int inputType;
     field public java.lang.CharSequence label;
-    field public android.util.LocaleList locales;
     field public java.lang.String packageName;
     field public java.lang.String privateImeOptions;
   }
@@ -44090,6 +44116,30 @@
     method public abstract android.view.View getFullScreenView(int, android.content.Context);
   }
 
+  public class ServiceWorkerClient {
+    ctor public ServiceWorkerClient();
+    method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerController {
+    ctor public ServiceWorkerController();
+    method public static android.webkit.ServiceWorkerController getInstance();
+    method public abstract android.webkit.ServiceWorkerWebSettings getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(android.webkit.ServiceWorkerClient);
+  }
+
+  public abstract class ServiceWorkerWebSettings {
+    ctor public ServiceWorkerWebSettings();
+    method public abstract boolean getAllowContentAccess();
+    method public abstract boolean getAllowFileAccess();
+    method public abstract boolean getBlockNetworkLoads();
+    method public abstract int getCacheMode();
+    method public abstract void setAllowContentAccess(boolean);
+    method public abstract void setAllowFileAccess(boolean);
+    method public abstract void setBlockNetworkLoads(boolean);
+    method public abstract void setCacheMode(int);
+  }
+
   public class SslErrorHandler extends android.os.Handler {
     method public void cancel();
     method public void proceed();
@@ -46860,6 +46910,7 @@
     method public int getHyphenationFrequency();
     method public int getImeActionId();
     method public java.lang.CharSequence getImeActionLabel();
+    method public android.util.LocaleList getImeHintLocales();
     method public int getImeOptions();
     method public boolean getIncludeFontPadding();
     method public android.os.Bundle getInputExtras(boolean);
@@ -46966,6 +47017,7 @@
     method public void setHorizontallyScrolling(boolean);
     method public void setHyphenationFrequency(int);
     method public void setImeActionLabel(java.lang.CharSequence, int);
+    method public void setImeHintLocales(android.util.LocaleList);
     method public void setImeOptions(int);
     method public void setIncludeFontPadding(boolean);
     method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -47919,10 +47971,8 @@
   }
 
   public final class Console implements java.io.Flushable {
-    method public static java.io.Console console();
     method public void flush();
     method public java.io.Console format(java.lang.String, java.lang.Object...);
-    method public static synchronized java.io.Console getConsole();
     method public java.io.Console printf(java.lang.String, java.lang.Object...);
     method public java.lang.String readLine(java.lang.String, java.lang.Object...);
     method public java.lang.String readLine();
@@ -48206,7 +48256,6 @@
   public class InterruptedIOException extends java.io.IOException {
     ctor public InterruptedIOException();
     ctor public InterruptedIOException(java.lang.String);
-    ctor public InterruptedIOException(java.lang.Throwable);
     field public int bytesTransferred;
   }
 
@@ -48877,7 +48926,6 @@
     method public long longValue();
     method public static byte parseByte(java.lang.String, int) throws java.lang.NumberFormatException;
     method public static byte parseByte(java.lang.String) throws java.lang.NumberFormatException;
-    method public static java.lang.String toHexString(byte, boolean);
     method public static java.lang.String toString(byte);
     method public static java.lang.Byte valueOf(byte);
     method public static java.lang.Byte valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -51029,7 +51077,6 @@
   public class BindException extends java.net.SocketException {
     ctor public BindException(java.lang.String);
     ctor public BindException();
-    ctor public BindException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract class CacheRequest {
@@ -51047,7 +51094,6 @@
   public class ConnectException extends java.net.SocketException {
     ctor public ConnectException(java.lang.String);
     ctor public ConnectException();
-    ctor public ConnectException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract class ContentHandler {
@@ -51127,7 +51173,6 @@
     method public void disconnect();
     method public synchronized boolean getBroadcast() throws java.net.SocketException;
     method public java.nio.channels.DatagramChannel getChannel();
-    method public final java.io.FileDescriptor getFileDescriptor$();
     method public java.net.InetAddress getInetAddress();
     method public java.net.InetAddress getLocalAddress();
     method public int getLocalPort();
@@ -51204,7 +51249,6 @@
     method public boolean hasExpired();
     method public boolean isHttpOnly();
     method public static java.util.List<java.net.HttpCookie> parse(java.lang.String);
-    method public static java.util.List<java.net.HttpCookie> parse(java.lang.String, boolean);
     method public void setComment(java.lang.String);
     method public void setCommentURL(java.lang.String);
     method public void setDiscard(boolean);
@@ -51297,9 +51341,6 @@
   }
 
   public final class Inet4Address extends java.net.InetAddress {
-    field public static final java.net.InetAddress ALL;
-    field public static final java.net.InetAddress ANY;
-    field public static final java.net.InetAddress LOOPBACK;
   }
 
   public final class Inet6Address extends java.net.InetAddress {
@@ -51308,8 +51349,6 @@
     method public int getScopeId();
     method public java.net.NetworkInterface getScopedInterface();
     method public boolean isIPv4CompatibleAddress();
-    field public static final java.net.InetAddress ANY;
-    field public static final java.net.InetAddress LOOPBACK;
   }
 
   public class InetAddress implements java.io.Serializable {
@@ -51437,13 +51476,11 @@
   public class PortUnreachableException extends java.net.SocketException {
     ctor public PortUnreachableException(java.lang.String);
     ctor public PortUnreachableException();
-    ctor public PortUnreachableException(java.lang.String, java.lang.Throwable);
   }
 
   public class ProtocolException extends java.io.IOException {
     ctor public ProtocolException(java.lang.String);
     ctor public ProtocolException();
-    ctor public ProtocolException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract interface ProtocolFamily {
@@ -51578,8 +51615,6 @@
   public class SocketException extends java.io.IOException {
     ctor public SocketException(java.lang.String);
     ctor public SocketException();
-    ctor public SocketException(java.lang.Throwable);
-    ctor public SocketException(java.lang.String, java.lang.Throwable);
   }
 
   public abstract class SocketImpl implements java.net.SocketOptions {
@@ -51649,8 +51684,6 @@
   public class SocketTimeoutException extends java.io.InterruptedIOException {
     ctor public SocketTimeoutException(java.lang.String);
     ctor public SocketTimeoutException();
-    ctor public SocketTimeoutException(java.lang.Throwable);
-    ctor public SocketTimeoutException(java.lang.String, java.lang.Throwable);
   }
 
   public final class StandardProtocolFamily extends java.lang.Enum implements java.net.ProtocolFamily {
@@ -59595,7 +59628,6 @@
     ctor public JarFile(java.io.File, boolean, int) throws java.io.IOException;
     method public java.util.jar.JarEntry getJarEntry(java.lang.String);
     method public java.util.jar.Manifest getManifest() throws java.io.IOException;
-    method public boolean hasClassPathAttribute() throws java.io.IOException;
     field public static final java.lang.String MANIFEST_NAME = "META-INF/MANIFEST.MF";
   }
 
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 9728e38..67d63a4 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -16,8 +16,11 @@
 
 package com.android.commands.bmgr;
 
+import android.app.backup.BackupManager;
+import android.app.backup.BackupProgress;
 import android.app.backup.RestoreSet;
 import android.app.backup.IBackupManager;
+import android.app.backup.IBackupObserver;
 import android.app.backup.IRestoreObserver;
 import android.app.backup.IRestoreSession;
 import android.os.RemoteException;
@@ -108,6 +111,11 @@
             return;
         }
 
+        if ("backupnow".equals(op)) {
+            doBackupNow();
+            return;
+        }
+
         System.err.println("Unknown command");
         showUsage();
     }
@@ -189,6 +197,88 @@
         }
     }
 
+    class BackupObserver extends IBackupObserver.Stub {
+        boolean done = false;
+
+        @Override
+        public void onUpdate(String currentPackage, BackupProgress backupProgress) {
+            System.out.println(
+                "onUpdate: " + currentPackage + " with progress: " + backupProgress.bytesTransferred
+                    + "/" + backupProgress.bytesExpected);
+        }
+
+        @Override
+        public void onResult(String currentPackage, int status) {
+            System.out.println("onResult: " + currentPackage + " with result: "
+                    + convertBackupStatusToString(status));
+        }
+
+        @Override
+        public void backupFinished(int status) {
+            System.out.println("backupFinished: " + convertBackupStatusToString(status));
+            synchronized (this) {
+                done = true;
+                this.notify();
+            }
+        }
+
+        public void waitForCompletion() {
+            // The backupFinished() callback will throw the 'done' flag; we
+            // just sit and wait on that notification.
+            synchronized (this) {
+                while (!this.done) {
+                    try {
+                        this.wait();
+                    } catch (InterruptedException ex) {
+                    }
+                }
+            }
+        }
+
+    }
+
+    private static String convertBackupStatusToString(int errorCode) {
+        switch (errorCode) {
+            case BackupManager.SUCCESS:
+                return "Success";
+            case BackupManager.ERROR_BACKUP_NOT_ALLOWED:
+                return "Backup is not allowed";
+            case BackupManager.ERROR_PACKAGE_NOT_FOUND:
+                return "Package not found";
+            case BackupManager.ERROR_TRANSPORT_ABORTED:
+                return "Transport error";
+            case BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED:
+                return "Transport rejected package";
+            case BackupManager.ERROR_AGENT_FAILURE:
+                return "Agent error";
+            default:
+                return "Unknown error";
+        }
+    }
+
+    private void doBackupNow() {
+        String pkg;
+        ArrayList<String> allPkgs = new ArrayList<String>();
+        while ((pkg = nextArg()) != null) {
+            allPkgs.add(pkg);
+        }
+        if (allPkgs.size() > 0) {
+            try {
+                BackupObserver observer = new BackupObserver();
+                int err = mBmgr.requestBackup(allPkgs.toArray(new String[allPkgs.size()]), observer);
+                if (err == 0) {
+                    // Off and running -- wait for the backup to complete
+                    observer.waitForCompletion();
+                } else {
+                    System.err.println("Unable to run backup");
+                }
+            } catch (RemoteException e) {
+                System.err.println(e.toString());
+                System.err.println(BMGR_NOT_RUNNING_ERR);
+            }
+        }
+    }
+
     private void doTransport() {
         try {
             String which = nextArg();
@@ -528,5 +618,10 @@
         System.err.println("");
         System.err.println("The 'fullbackup' command induces a full-data stream backup for one or more");
         System.err.println("packages.  The data is sent via the currently active transport.");
+        System.err.println("");
+        System.err.println("The 'backupnow' command runs an immediate backup for one or more packages.");
+        System.err.println("For each package it will run key/value or full data backup ");
+        System.err.println("depending on the package's manifest declarations.");
+        System.err.println("The data is sent via the currently active transport.");
     }
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 93122dd..7b01953 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -83,6 +83,7 @@
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
+import android.util.LocaleList;
 import android.util.Log;
 import android.util.LogPrinter;
 import android.util.Pair;
@@ -128,7 +129,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.TimeZone;
@@ -4887,9 +4887,9 @@
         TimeZone.setDefault(null);
 
         /*
-         * Initialize the default locale in this process for the reasons we set the time zone.
+         * Initialize the default locales in this process for the reasons we set the time zone.
          */
-        Locale.setDefault(data.config.locale);
+        LocaleList.setDefault(data.config.getLocales());
 
         /*
          * Update the system configuration since its preloaded and might not
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0afca9d..220fb607 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1099,13 +1099,24 @@
 
     @Override
     public Drawable getUserBadgeForDensity(UserHandle user, int density) {
+        return getManagedProfileIconForDensity(user, density,
+                com.android.internal.R.drawable.ic_corp_badge);
+    }
+
+    @Override
+    public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
+        return getManagedProfileIconForDensity(user, density,
+                com.android.internal.R.drawable.ic_corp_badge_no_background);
+    }
+
+    private Drawable getManagedProfileIconForDensity(UserHandle user, int density,
+            int drawableId) {
         UserInfo userInfo = getUserIfProfile(user.getIdentifier());
         if (userInfo != null && userInfo.isManagedProfile()) {
             if (density <= 0) {
                 density = mContext.getResources().getDisplayMetrics().densityDpi;
             }
-            return Resources.getSystem().getDrawableForDensity(
-                    com.android.internal.R.drawable.ic_corp_badge, density);
+            return Resources.getSystem().getDrawableForDensity(drawableId, density);
         }
         return null;
     }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 7184337..24a3470 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -545,7 +545,7 @@
          * returning the resulting activity or till the timeOut period expires.
          * If the timeOut expires before the activity is started, return null. 
          * 
-         * @param timeOut Time to wait before the activity is created.
+         * @param timeOut Time to wait in milliseconds before the activity is created.
          * 
          * @return Activity
          */
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 3cda973..f33af39 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.WorkerThread;
+import android.annotation.Nullable;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -113,7 +114,7 @@
     }
 
     @Override
-    public void onStart(Intent intent, int startId) {
+    public void onStart(@Nullable Intent intent, int startId) {
         Message msg = mServiceHandler.obtainMessage();
         msg.arg1 = startId;
         msg.obj = intent;
@@ -127,7 +128,7 @@
      * @see android.app.Service#onStartCommand
      */
     @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
+    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
         onStart(intent, startId);
         return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
     }
@@ -139,10 +140,11 @@
 
     /**
      * Unless you provide binding for your service, you don't need to implement this
-     * method, because the default implementation returns null. 
+     * method, because the default implementation returns null.
      * @see android.app.Service#onBind
      */
     @Override
+    @Nullable
     public IBinder onBind(Intent intent) {
         return null;
     }
@@ -158,7 +160,11 @@
      *
      * @param intent The value passed to {@link
      *               android.content.Context#startService(Intent)}.
+     *               This may be null if the service is being restarted after
+     *               its process has gone away; see
+     *               {@link android.app.Service#onStartCommand}
+     *               for details.
      */
     @WorkerThread
-    protected abstract void onHandleIntent(Intent intent);
+    protected abstract void onHandleIntent(@Nullable Intent intent);
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a392abd..0f3aad9 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2998,7 +2998,7 @@
         private Drawable getProfileBadgeDrawable() {
             // Note: This assumes that the current user can read the profile badge of the
             // originating user.
-            return mContext.getPackageManager().getUserBadgeForDensity(
+            return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
                     new UserHandle(mContext.getUserId()), 0);
         }
 
@@ -3016,24 +3016,13 @@
             return bitmap;
         }
 
-        private boolean addProfileBadge(RemoteViews contentView, int resId) {
+        private void bindProfileBadge(RemoteViews contentView) {
             Bitmap profileBadge = getProfileBadge();
 
-            contentView.setViewVisibility(R.id.profile_badge_large_template, View.GONE);
-            contentView.setViewVisibility(R.id.profile_badge_line3, View.GONE);
-
             if (profileBadge != null) {
-                contentView.setImageViewBitmap(resId, profileBadge);
-                contentView.setViewVisibility(resId, View.VISIBLE);
-
-                // Make sure Line 3 is visible. As badge will be here if there
-                // is no text to display.
-                if (resId == R.id.profile_badge_line3) {
-                    contentView.setViewVisibility(R.id.line3, View.VISIBLE);
-                }
-                return true;
+                contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
+                contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
             }
-            return false;
         }
 
         private void resetStandardTemplate(RemoteViews contentView) {
@@ -3042,9 +3031,10 @@
             contentView.setViewVisibility(R.id.right_icon, View.GONE);
             contentView.setViewVisibility(R.id.title, View.GONE);
             contentView.setTextViewText(R.id.title, null);
+            contentView.setViewVisibility(R.id.text, View.GONE);
             contentView.setTextViewText(R.id.text, null);
-            contentView.setViewVisibility(R.id.line3, View.GONE);
             contentView.setViewVisibility(R.id.text_line_1, View.GONE);
+            contentView.setTextViewText(R.id.text_line_1, null);
             contentView.setViewVisibility(R.id.progress, View.GONE);
         }
 
@@ -3062,11 +3052,13 @@
             contentView.setViewVisibility(R.id.sub_text_divider, View.GONE);
             contentView.setViewVisibility(R.id.content_info_divider, View.GONE);
             contentView.setViewVisibility(R.id.time_divider, View.GONE);
+            contentView.setImageViewIcon(R.id.profile_badge, null);
+            contentView.setViewVisibility(R.id.profile_badge, View.GONE);
         }
 
         private void resetContentMargins(RemoteViews contentView) {
             contentView.setViewLayoutMarginEnd(R.id.line1, 0);
-            contentView.setViewLayoutMarginEnd(R.id.line3, 0);
+            contentView.setViewLayoutMarginEnd(R.id.text, 0);
         }
 
         private RemoteViews applyStandardTemplate(int resId) {
@@ -3081,7 +3073,6 @@
 
             resetStandardTemplate(contentView);
 
-            boolean showLine3 = false;
             final Bundle ex = mN.extras;
 
             bindNotificationHeader(contentView);
@@ -3093,19 +3084,13 @@
             }
             boolean showProgress = handleProgressBar(hasProgress, contentView, ex);
             if (ex.getCharSequence(EXTRA_TEXT) != null) {
-                contentView.setTextViewText(showProgress ? R.id.text_line_1 : R.id.text,
-                        processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
-                if (showProgress) {
-                    contentView.setViewVisibility(R.id.text_line_1, View.VISIBLE);
-                }
-                showLine3 = !showProgress;
+                int textId = showProgress ? com.android.internal.R.id.text_line_1
+                        : com.android.internal.R.id.text;
+                contentView.setTextViewText(textId, processLegacyText(
+                        ex.getCharSequence(EXTRA_TEXT)));
+                contentView.setViewVisibility(textId, View.VISIBLE);
             }
-            // We want to add badge to first line of text.
-            if (addProfileBadge(contentView, R.id.profile_badge_line3)) {
-                showLine3 = true;
-            }
-            // Note getStandardView may hide line 3 again.
-            contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
+
             setContentMinHeight(contentView, showProgress || mN.mLargeIcon != null);
 
             return contentView;
@@ -3157,7 +3142,7 @@
                 int endMargin = mContext.getResources().getDimensionPixelSize(
                         R.dimen.notification_content_picture_margin);
                 contentView.setViewLayoutMarginEnd(R.id.line1, endMargin);
-                contentView.setViewLayoutMarginEnd(R.id.line3, endMargin);
+                contentView.setViewLayoutMarginEnd(R.id.text, endMargin);
                 contentView.setViewLayoutMarginEnd(R.id.progress, endMargin);
             }
         }
@@ -3170,6 +3155,7 @@
             bindContentInfo(contentView);
             bindHeaderChronometerAndTime(contentView);
             bindExpandButton(contentView);
+            bindProfileBadge(contentView);
         }
 
         private void bindChildCountColor(RemoteViews contentView) {
@@ -3729,10 +3715,6 @@
                 contentView.setViewVisibility(R.id.line1, View.VISIBLE);
             }
 
-            // Clear text in case we use the line to show the profile badge.
-            contentView.setTextViewText(com.android.internal.R.id.text, "");
-            contentView.setViewVisibility(com.android.internal.R.id.line3, View.GONE);
-
             return contentView;
         }
 
@@ -3939,7 +3921,7 @@
             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
             if (mSummaryTextSet) {
                 contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
-                contentView.setViewVisibility(R.id.line3, View.VISIBLE);
+                contentView.setViewVisibility(R.id.text, View.VISIBLE);
             }
             mBuilder.setContentMinHeight(contentView, mBuilder.mN.mLargeIcon != null);
 
@@ -3948,8 +3930,6 @@
             }
 
             contentView.setImageViewBitmap(R.id.big_picture, mPicture);
-
-            mBuilder.addProfileBadge(contentView, R.id.profile_badge_line3);
             return contentView;
         }
 
@@ -4083,9 +4063,6 @@
             contentView.setViewVisibility(R.id.big_text,
                     TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
             contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines());
-
-            mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
-
             contentView.setBoolean(R.id.big_text, "setHasImage", mBuilder.mN.mLargeIcon != null);
 
             return contentView;
@@ -4183,7 +4160,7 @@
          * @hide
          */
         public RemoteViews makeBigContentView() {
-            // Remove the content text so line3 disappears unless you have a summary
+            // Remove the content text so it disappears unless you have a summary
             // Nasty
             CharSequence oldBuilderContentText = mBuilder.mN.extras.getCharSequence(EXTRA_TEXT);
             mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
@@ -4222,7 +4199,6 @@
                 }
                 i++;
             }
-            mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
 
             handleInboxImageMargin(contentView, rowIds[0]);
 
@@ -4433,7 +4409,7 @@
         private void handleImage(RemoteViews contentView) {
             if (mBuilder.mN.mLargeIcon != null) {
                 contentView.setViewLayoutMarginEnd(R.id.line1, 0);
-                contentView.setViewLayoutMarginEnd(R.id.line3, 0);
+                contentView.setViewLayoutMarginEnd(R.id.text, 0);
             }
         }
 
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 3187984..260216c 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -27,6 +27,7 @@
 import android.hardware.display.DisplayManagerGlobal;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
+import android.util.LocaleList;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -34,7 +35,6 @@
 import android.view.DisplayAdjustments;
 
 import java.lang.ref.WeakReference;
-import java.util.Locale;
 
 /** @hide */
 public class ResourcesManager {
@@ -284,8 +284,9 @@
         }
 
         // set it for java, this also affects newly created Resources
-        if (config.locale != null) {
-            Locale.setDefault(config.locale);
+        final LocaleList localeList = config.getLocales();
+        if (!localeList.isEmpty()) {
+            LocaleList.setDefault(localeList);
         }
 
         Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 20cacff..a655bfd 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5142,4 +5142,55 @@
             return null;
         }
     }
+
+    /**
+     * Called by a profile owner of a managed profile to set the color used for customization.
+     * This color is used as background color of the confirm credentials screen for that user.
+     * The default color is {@link android.graphics.Color#GRAY}.
+     *
+     * <p>The confirm credentials screen can be created using
+     * {@link android.app.KeyguardManager#createConfirmDeviceCredentialIntent}.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param color The 32bit representation of the color to be used.
+     */
+    public void setOrganizationColor(@NonNull ComponentName admin, int color) {
+        try {
+            mService.setOrganizationColor(admin, color);
+        } catch (RemoteException re) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+        }
+    }
+
+    /**
+     * Called by a profile owner of a managed profile to retrieve the color used for customization.
+     * This color is used as background color of the confirm credentials screen for that user.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @return The 32bit representation of the color to be used.
+     */
+    public int getOrganizationColor(@NonNull ComponentName admin) {
+        try {
+            return mService.getOrganizationColor(admin);
+        } catch (RemoteException re) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+            return 0;
+        }
+    }
+
+    /**
+     * @hide
+     * Retrieve the customization color for a given user.
+     *
+     * @param userHandle The user id of the user we're interested in.
+     * @return The 32bit representation of the color to be used.
+     */
+    public int getOrganizationColorForUser(int userHandle) {
+        try {
+            return mService.getOrganizationColorForUser(userHandle);
+        } catch (RemoteException re) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+            return 0;
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 08cab88..82115a2 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -263,4 +263,8 @@
     String getLongSupportMessageForUser(in ComponentName admin, int userHandle);
 
     boolean isSeparateProfileChallengeAllowed(int userHandle);
+
+    void setOrganizationColor(in ComponentName admin, in int color);
+    int getOrganizationColor(in ComponentName admin);
+    int getOrganizationColorForUser(int userHandle);
 }
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 193a0b2c..c27eaa4f 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -426,6 +426,29 @@
     }
 
     /**
+     * Ask the framework whether this app is eligible for backup.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @param packageName The name of the package.
+     * @return Whether this app is eligible for backup.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isAppEligibleForBackup(String packageName) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                return sService.isAppEligibleForBackup(packageName);
+            } catch (RemoteException e) {
+                Log.e(TAG, "isAppEligibleForBackup(pkg) couldn't connect");
+            }
+        }
+        return false;
+    }
+    
+    /**
      * Request an immediate backup, providing an observer to which results of the backup operation
      * will be published. The Android backup system will decide for each package whether it will
      * be full app data backup or key/value-pair-based backup.
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 4363604..aca115f 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -482,6 +482,18 @@
                 "Transport cancelFullBackup() not implemented");
     }
 
+    /**
+     * Ask the transport whether this app is eligible for backup.
+     *
+     * @param targetPackage The identity of the application.
+     * @param isFullBackup If set, transport should check if app is eligible for full data backup,
+     *   otherwise to check if eligible for key-value backup.
+     * @return Whether this app is eligible for backup.
+     */
+    public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) {
+        return true;
+    }
+
     // ------------------------------------------------------------------------------------
     // Full restore interfaces
 
@@ -659,6 +671,12 @@
         }
 
         @Override
+        public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup)
+                throws RemoteException {
+            return BackupTransport.this.isAppEligibleForBackup(targetPackage, isFullBackup);
+        }
+
+        @Override
         public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
             return BackupTransport.this.getNextFullRestoreDataChunk(socket);
         }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 2a1c00f..5d4cc6f 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -329,6 +329,16 @@
     long getAvailableRestoreToken(String packageName);
 
     /**
+     * Ask the framework whether this app is eligible for backup.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @param packageName The name of the package.
+     * @return Whether this app is eligible for backup.
+     */
+    boolean isAppEligibleForBackup(String packageName);
+
+    /**
      * Request an immediate backup, providing an observer to which results of the backup operation
      * will be published. The Android backup system will decide for each package whether it will
      * be full app data backup or key/value-pair-based backup.
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
index 002f63f..c73bc3c 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -196,7 +196,7 @@
 
     public String toString(boolean loggable) {
         StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mDevice: ");
-        builder.append(loggable ? mDevice.hashCode() : mDevice);
+        builder.append(loggable ? mDevice : mDevice.hashCode());
         builder.append(", mId: ");
         builder.append(mId);
         builder.append(", mUUID: ");
@@ -214,7 +214,7 @@
             default: builder.append(mState); break;
         }
         builder.append(", mNumber: ");
-        builder.append(loggable ? mNumber.hashCode() : mNumber);
+        builder.append(loggable ? mNumber : mNumber.hashCode());
         builder.append(", mMultiParty: ");
         builder.append(mMultiParty);
         builder.append(", mOutgoing: ");
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1e7512d..381ea90 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -41,6 +41,7 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -3186,6 +3187,38 @@
     public static final String
             ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
 
+    /**
+     * Activity Action: Give access to a standard storage directory after obtaining the user's
+     * approval.
+     * <p>
+     * When invoked, the system will ask the user to grant access to the requested directory (and
+     * its descendants).
+     * <p>
+     * To gain access to descendant (child, grandchild, etc) documents, use
+     * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)} and
+     * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
+     * <p>
+     * Input: full path to a standard directory, in the form of
+     * {@code STORAGE_ROOT + STANDARD_DIRECTORY}, where {@code STORAGE_ROOT} is the physical path of
+     * a storage container, and {@code STANDARD_DIRECTORY} is one of
+     * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
+     * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
+     * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
+     * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
+     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
+     * <p>
+     * For example, to open the "Pictures" folder in the default external storage, the intent's data
+     * would be: {@code Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
+     * Environment.DIRECTORY_PICTURES))}.
+     * <p>
+     * Output: The URI representing the requested directory tree.
+     *
+     * @see DocumentsContract
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String
+            ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
+
     /** {@hide} */
     public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR";
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ba4d14c..89f6870 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4268,6 +4268,22 @@
 
     /**
      * If the target user is a managed profile of the calling user or the caller
+     * is itself a managed profile, then this returns a drawable to use as a small
+     * icon to include in a view to distinguish it from the original icon. This version
+     * doesn't have background protection and should be used over a light background instead of
+     * a badge.
+     *
+     * @param user The target user.
+     * @param density The optional desired density for the badge as per
+     *         {@link android.util.DisplayMetrics#densityDpi}. If not provided
+     *         the density of the current display is used.
+     * @return the drawable or null if no drawable is required.
+     * @hide
+     */
+    public abstract Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density);
+
+    /**
+     * If the target user is a managed profile of the calling user or the caller
      * is itself a managed profile, then this returns a copy of the label with
      * badging for accessibility services like talkback. E.g. passing in "Email"
      * and it might return "Work Email" for Email in the work profile.
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 5d969b1..c4ee347 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -25,6 +25,8 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.PointerIcon;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 /** @hide */
 interface IInputManager {
@@ -59,6 +61,11 @@
             String keyboardLayoutDescriptor);
     void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor);
+    KeyboardLayout getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
+            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+    void setKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
+            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype,
+            String keyboardLayoutDescriptor);
 
     // Registers an input devices changed listener.
     void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9972f49..cbe3412 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,7 +16,7 @@
 
 package android.hardware.input;
 
-import android.view.PointerIcon;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 
@@ -40,6 +40,9 @@
 import android.util.SparseArray;
 import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.PointerIcon;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -643,6 +646,51 @@
         }
     }
 
+
+    /**
+     * Gets the keyboard layout for the specified input device and IME subtype.
+     *
+     * @param identifier The identifier for the input device.
+     * @param inputMethodInfo The input method.
+     * @param inputMethodSubtype The input method subtype.
+     *
+     * @return The associated {@link KeyboardLayout}, or null if one has not been set.
+     *
+     * @hide
+     */
+    public KeyboardLayout getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            InputMethodInfo inputMethodInfo, InputMethodSubtype inputMethodSubtype) {
+        try {
+            return mIm.getKeyboardLayoutForInputDevice(
+                    identifier, inputMethodInfo, inputMethodSubtype);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not set keyboard layout.", ex);
+            return null;
+        }
+    }
+
+    /**
+     * Sets the keyboard layout for the specified input device and IME subtype pair.
+     *
+     * @param identifier The identifier for the input device.
+     * @param inputMethodInfo The input method with which to associate the keyboard layout.
+     * @param inputMethodSubtype The input method subtype which which to associate the keyboard
+     *                           layout.
+     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to set
+     *
+     * @hide
+     */
+    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            InputMethodInfo inputMethodInfo, InputMethodSubtype inputMethodSubtype,
+            String keyboardLayoutDescriptor) {
+        try {
+            mIm.setKeyboardLayoutForInputDevice(identifier, inputMethodInfo,
+                    inputMethodSubtype, keyboardLayoutDescriptor);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not set keyboard layout.", ex);
+        }
+    }
+
     /**
      * Gets the TouchCalibration applied to the specified input device's coordinates.
      *
diff --git a/core/java/android/hardware/input/TouchCalibration.java b/core/java/android/hardware/input/TouchCalibration.java
index 025fad0..15503ed 100644
--- a/core/java/android/hardware/input/TouchCalibration.java
+++ b/core/java/android/hardware/input/TouchCalibration.java
@@ -123,4 +123,10 @@
                Float.floatToIntBits(mYScale)  ^
                Float.floatToIntBits(mYOffset);
     }
+
+    @Override
+    public String toString() {
+        return String.format("[%f, %f, %f, %f, %f, %f]",
+                mXScale, mXYMix, mXOffset, mYXMix, mYScale, mYOffset);
+    }
 }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c4f0847..5584cde 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -16,7 +16,7 @@
 package android.net;
 
 import static com.android.internal.util.Preconditions.checkNotNull;
-
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -49,6 +49,8 @@
 
 import libcore.net.event.NetworkEventDispatcher;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
 import java.util.HashMap;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -512,6 +514,7 @@
     private final Context mContext;
 
     private INetworkManagementService mNMService;
+    private INetworkPolicyManager mNPManager;
 
     /**
      * Tests if a given integer represents a valid network type.
@@ -3025,4 +3028,58 @@
         return NetworkUtils.bindProcessToNetworkForHostResolution(
                 network == null ? NETID_UNSET : network.netId);
     }
+
+    /**
+     * Device is not restricting metered network activity while application is running on
+     * background.
+     */
+    public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1;
+
+    /**
+     * Device is restricting metered network activity while application is running on background,
+     * but application is allowed to bypass it.
+     * <p>
+     * In this state, application should take action to mitigate metered network access.
+     * For example, a music streaming application should switch to a low-bandwidth bitrate.
+     */
+    public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2;
+
+    /**
+     * Device is restricting metered network activity while application is running on background.
+     * In this state, application should not try to use the network while running on background,
+     * because it would be denied.
+     */
+    public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3;
+
+    @IntDef(flag = false, value = {
+            RESTRICT_BACKGROUND_STATUS_DISABLED,
+            RESTRICT_BACKGROUND_STATUS_WHITELISTED,
+            RESTRICT_BACKGROUND_STATUS_ENABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RestrictBackgroundStatus {
+    }
+
+    private INetworkPolicyManager getNetworkPolicyManager() {
+        synchronized (this) {
+            if (mNPManager != null) {
+                return mNPManager;
+            }
+            mNPManager = INetworkPolicyManager.Stub.asInterface(ServiceManager
+                    .getService(Context.NETWORK_POLICY_SERVICE));
+            return mNPManager;
+        }
+    }
+
+    /**
+     * Determines if the calling application is subject to metered network restrictions while
+     * running on background.
+     */
+    public @RestrictBackgroundStatus int getRestrictBackgroundStatus() {
+        try {
+            return getNetworkPolicyManager().getRestrictBackgroundByCaller();
+        } catch (RemoteException e) {
+            return RESTRICT_BACKGROUND_STATUS_DISABLED;
+        }
+    }
 }
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 06aa616..a57fac3 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -56,6 +56,12 @@
     void addRestrictBackgroundWhitelistedUid(int uid);
     void removeRestrictBackgroundWhitelistedUid(int uid);
     int[] getRestrictBackgroundWhitelistedUids();
+    /** Gets the restrict background status based on the caller's UID:
+        1 - disabled
+        2 - whitelisted
+        3 - enabled
+    */
+    int getRestrictBackgroundByCaller();
 
     void setDeviceIdleMode(boolean enabled);
 
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 2ca9ab8a..ba215bb 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -390,7 +390,7 @@
      * type.
      */
     public static String DIRECTORY_MUSIC = "Music";
-    
+
     /**
      * Standard directory in which to place any audio files that should be
      * in the list of podcasts that the user can select (not as regular
@@ -479,6 +479,37 @@
     public static String DIRECTORY_DOCUMENTS = "Documents";
 
     /**
+     * List of standard storage directories.
+     * <p>
+     * Each of its values have its own constant:
+     * <ul>
+     *   <li>{@link #DIRECTORY_MUSIC}
+     *   <li>{@link #DIRECTORY_PODCASTS}
+     *   <li>{@link #DIRECTORY_ALARMS}
+     *   <li>{@link #DIRECTORY_RINGTONES}
+     *   <li>{@link #DIRECTORY_NOTIFICATIONS}
+     *   <li>{@link #DIRECTORY_PICTURES}
+     *   <li>{@link #DIRECTORY_MOVIES}
+     *   <li>{@link #DIRECTORY_DOWNLOADS}
+     *   <li>{@link #DIRECTORY_DCIM}
+     *   <li>{@link #DIRECTORY_DOCUMENTS}
+     * </ul>
+     * @hide
+     */
+    public static final String[] STANDARD_DIRECTORIES = {
+            DIRECTORY_MUSIC,
+            DIRECTORY_PODCASTS,
+            DIRECTORY_RINGTONES,
+            DIRECTORY_ALARMS,
+            DIRECTORY_NOTIFICATIONS,
+            DIRECTORY_PICTURES,
+            DIRECTORY_MOVIES,
+            DIRECTORY_DOWNLOADS,
+            DIRECTORY_DCIM,
+            DIRECTORY_DOCUMENTS
+    };
+
+    /**
      * Get a top-level shared/external storage directory for placing files of a
      * particular type. This is where the user will typically place and manage
      * their own files, so you should be careful about what you put here to
@@ -495,13 +526,13 @@
      * </p>
      * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
      * public_picture}
-     * 
+     *
      * @param type The type of storage directory to return. Should be one of
      *            {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
      *            {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS},
      *            {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES},
-     *            {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS}, or
-     *            {@link #DIRECTORY_DCIM}. May not be null.
+     *            {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS},
+     *            {@link #DIRECTORY_DCIM}, or {@link #DIRECTORY_DOCUMENTS}. May not be null.
      * @return Returns the File path for the directory. Note that this directory
      *         may not yet exist, so you must make sure it exists before using
      *         it such as with {@link File#mkdirs File.mkdirs()}.
@@ -657,7 +688,7 @@
 
     /**
      * Returns the current state of the primary shared/external storage media.
-     * 
+     *
      * @see #getExternalStorageDirectory()
      * @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
      *         {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING},
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 6a5d857c..e7c4a07 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -38,6 +38,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.internal.telephony.CallerInfo;
 import com.android.internal.telephony.PhoneConstants;
@@ -49,6 +50,7 @@
  */
 public class CallLog {
     private static final String LOG_TAG = "CallLog";
+    private static final boolean VERBOSE_LOG = false; // DON'T SUBMIT WITH TRUE.
 
     public static final String AUTHORITY = "call_log";
 
@@ -58,6 +60,17 @@
     public static final Uri CONTENT_URI =
         Uri.parse("content://" + AUTHORITY);
 
+
+    /**
+     * The "shadow" provider stores calllog when the real calllog provider is encrypted.  The
+     * real provider will alter copy from it when it starts, and remove the entries in the shadow.
+     *
+     * <p>See the comment in {@link Calls#addCall} for the details.
+     *
+     * @hide
+     */
+    public static final String SHADOW_AUTHORITY = "call_log_shadow";
+
     /**
      * Contains the recent calls.
      */
@@ -68,6 +81,10 @@
         public static final Uri CONTENT_URI =
                 Uri.parse("content://call_log/calls");
 
+        /** @hide */
+        public static final Uri SHADOW_CONTENT_URI =
+                Uri.parse("content://call_log_shadow/calls");
+
         /**
          * The content:// style URL for filtering this table on phone numbers
          */
@@ -458,8 +475,10 @@
         public static Uri addCall(CallerInfo ci, Context context, String number,
                 int presentation, int callType, int features, PhoneAccountHandle accountHandle,
                 long start, int duration, Long dataUsage) {
-            return addCall(ci, context, number, "", presentation, callType, features, accountHandle,
-                    start, duration, dataUsage, false, null, false);
+            return addCall(ci, context, number, /* postDialDigits =*/ "", presentation,
+                    callType, features, accountHandle,
+                    start, duration, dataUsage, /* addForAllUsers =*/ false,
+                    /* userToBeInsertedTo =*/ null, /* is_read =*/ false);
         }
 
 
@@ -495,7 +514,7 @@
                 boolean addForAllUsers, UserHandle userToBeInsertedTo) {
             return addCall(ci, context, number, postDialDigits, presentation, callType, features,
                     accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo,
-                    false);
+                    /* is_read =*/ false);
         }
 
         /**
@@ -526,13 +545,18 @@
          *                Used for call log restore of missed calls.
          *
          * @result The URI of the call log entry belonging to the user that made or received this
-         *        call.
+         *        call.  This could be of the shadow provider.  Do not return it to non-system apps,
+         *        as they don't have permissions.
          * {@hide}
          */
         public static Uri addCall(CallerInfo ci, Context context, String number,
                 String postDialDigits, int presentation, int callType, int features,
                 PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
                 boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean is_read) {
+            if (VERBOSE_LOG) {
+                Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s",
+                        number, userToBeInsertedTo, addForAllUsers));
+            }
             final ContentResolver resolver = context.getContentResolver();
             int numberPresentation = PRESENTATION_ALLOWED;
 
@@ -647,41 +671,104 @@
                 }
             }
 
+            /*
+                Writing the calllog works in the following way:
+                - All user entries
+                    - if user-0 is encrypted, insert to user-0's shadow only.
+                      (other users should also be encrypted, so nothing to do for other users.)
+                    - if user-0 is decrypted, insert to user-0's real provider, as well as
+                      all other users that are running and decrypted and should have calllog.
+
+                - Single user entry.
+                    - If the target user is encryted, insert to its shadow.
+                    - Otherwise insert to its real provider.
+
+                When the (real) calllog provider starts, it copies entries that it missed from
+                elsewhere.
+                - When user-0's (real) provider starts, it copies from user-0's shadow, and clears
+                  the shadow.
+
+                - When other users (real) providers start, unless it shouldn't have calllog entries,
+                     - Copy from the user's shadow, and clears the shadow.
+                     - Copy from user-0's entries that are FOR_ALL_USERS = 1.  (and don't clear it.)
+             */
+
             Uri result = null;
 
+            final UserManager userManager = context.getSystemService(UserManager.class);
+            final int currentUserId = userManager.getUserHandle();
+
             if (addForAllUsers) {
-                // Insert the entry for all currently running users, in order to trigger any
-                // ContentObservers currently set on the call log.
-                final UserManager userManager = (UserManager) context.getSystemService(
-                        Context.USER_SERVICE);
-                List<UserInfo> users = userManager.getUsers(true);
-                final int currentUserId = userManager.getUserHandle();
+                // First, insert to the system user.
+                final Uri uriForSystem = addEntryAndRemoveExpiredEntries(
+                        context, userManager, UserHandle.SYSTEM, values);
+                if (uriForSystem == null
+                        || SHADOW_AUTHORITY.equals(uriForSystem.getAuthority())) {
+                    // This means the system user is still encrypted and the entry has inserted
+                    // into the shadow.  This means other users are still all encrypted.
+                    // Nothing further to do; just return null.
+                    return null;
+                }
+                if (UserHandle.USER_SYSTEM == currentUserId) {
+                    result = uriForSystem;
+                }
+
+                // Otherwise, insert to all other users that are running and unlocked.
+
+                final List<UserInfo> users = userManager.getUsers(true);
+
                 final int count = users.size();
                 for (int i = 0; i < count; i++) {
-                    final UserInfo user = users.get(i);
-                    final UserHandle userHandle = user.getUserHandle();
+                    final UserInfo userInfo = users.get(i);
+                    final UserHandle userHandle = userInfo.getUserHandle();
+                    final int userId = userHandle.getIdentifier();
+
+                    if (userHandle.isSystem()) {
+                        // Already written.
+                        continue;
+                    }
+
+                    if (!shouldHaveSharedCallLogEntries(context, userManager, userId)) {
+                        // Shouldn't have calllog entries.
+                        continue;
+                    }
+
+                    // For other users, we write only when they're running *and* decrypted.
+                    // Other providers will copy from the system user's real provider, when they
+                    // start.
                     if (userManager.isUserRunning(userHandle)
-                            && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                                    userHandle)
-                            && !user.isManagedProfile()) {
-                        Uri uri = addEntryAndRemoveExpiredEntries(context,
-                                ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values);
-                        if (user.id == currentUserId) {
+                            && userManager.isUserUnlocked(userHandle)) {
+                        final Uri uri = addEntryAndRemoveExpiredEntries(context, userManager,
+                                userHandle, values);
+                        if (userId == currentUserId) {
                             result = uri;
                         }
                     }
                 }
             } else {
-                Uri uri = CONTENT_URI;
-                if (userToBeInsertedTo != null) {
-                    uri = ContentProvider
-                            .maybeAddUserId(CONTENT_URI, userToBeInsertedTo.getIdentifier());
-                }
-                result = addEntryAndRemoveExpiredEntries(context, uri, values);
+                // Single-user entry. Just write to that user, assuming it's running.  If the
+                // user is encrypted, we write to the shadow calllog.
+
+                final UserHandle targetUserHandle = userToBeInsertedTo != null
+                        ? userToBeInsertedTo
+                        : UserHandle.of(currentUserId);
+                result = addEntryAndRemoveExpiredEntries(context, userManager, targetUserHandle,
+                        values);
             }
             return result;
         }
 
+        /** @hide */
+        public static boolean shouldHaveSharedCallLogEntries(Context context,
+                UserManager userManager, int userId) {
+            if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+                    UserHandle.of(userId))) {
+                return false;
+            }
+            final UserInfo userInfo = userManager.getUserInfo(userId);
+            return userInfo != null && !userInfo.isManagedProfile();
+        }
+
         /**
          * Query the call log database for the last dialed number.
          * @param context Used to get the content resolver.
@@ -707,14 +794,31 @@
             }
         }
 
-        private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri,
-                ContentValues values) {
+        private static Uri addEntryAndRemoveExpiredEntries(Context context, UserManager userManager,
+                UserHandle user, ContentValues values) {
             final ContentResolver resolver = context.getContentResolver();
-            Uri result = resolver.insert(uri, values);
-            resolver.delete(uri, "_id IN " +
-                    "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
-                    + " LIMIT -1 OFFSET 500)", null);
-            return result;
+
+            final Uri uri = ContentProvider.maybeAddUserId(
+                    userManager.isUserUnlocked(user) ? CONTENT_URI : SHADOW_CONTENT_URI,
+                    user.getIdentifier());
+
+            if (VERBOSE_LOG) {
+                Log.v(LOG_TAG, String.format("Inserting to %s", uri));
+            }
+
+            try {
+                final Uri result = resolver.insert(uri, values);
+                resolver.delete(uri, "_id IN " +
+                        "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
+                        + " LIMIT -1 OFFSET 500)", null);
+                return result;
+            } catch (IllegalArgumentException e) {
+                Log.w(LOG_TAG, "Failed to insert calllog", e);
+                // Even though we make sure the target user is running and decrypted before calling
+                // this method, there's a chance that the user just got shut down, in which case
+                // we'll still get "IllegalArgumentException: Unknown URL content://call_log/calls".
+                return null;
+            }
         }
 
         private static void updateDataUsageStatForData(ContentResolver resolver, String dataId) {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a401ac2..88cc8a2 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -62,7 +62,7 @@
  * All client apps must hold a valid URI permission grant to access documents,
  * typically issued when a user makes a selection through
  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
- * or {@link Intent#ACTION_OPEN_DOCUMENT_TREE}.
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or {@link Intent#ACTION_OPEN_EXTERNAL_DIRECTORY}.
  *
  * @see DocumentsProvider
  */
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
index 24883e3..8a2d015 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -301,10 +301,19 @@
             // is a pseudo-locale. So this is not a match.
             return 0;
         }
+        final String supportedScr = getLikelyScript(supported);
+        if (supportedScr.isEmpty()) {
+            // If we can't guess a script, we don't know enough about the locales' language to find
+            // if the locales match. So we fall back to old behavior of matching, which considered
+            // locales with different regions different.
+            final String supportedRegion = supported.getCountry();
+            return (supportedRegion.isEmpty() ||
+                    supportedRegion.equals(desired.getCountry()))
+                    ? 1 : 0;
+        }
+        final String desiredScr = getLikelyScript(desired);
         // There is no match if the two locales use different scripts. This will most imporantly
         // take care of traditional vs simplified Chinese.
-        final String supportedScr = getLikelyScript(supported);
-        final String desiredScr = getLikelyScript(desired);
         return supportedScr.equals(desiredScr) ? 1 : 0;
     }
 
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 54fa764..1c0ea0f 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -25,6 +25,8 @@
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
+import com.android.internal.R;
+
 import java.util.ArrayList;
 
 /**
@@ -44,6 +46,7 @@
     private ImageView mExpandButton;
     private View mIcon;
     private TextView mChildCount;
+    private View mProfileBadge;
     private int mIconColor;
     private int mOriginalNotificationColor;
     private boolean mGroupHeader;
@@ -76,6 +79,7 @@
         mExpandButton = (ImageView) findViewById(com.android.internal.R.id.expand_button);
         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);
     }
 
     @Override
@@ -120,12 +124,29 @@
             }
             totalWidth = givenWidth;
         }
+        if (mProfileBadge.getVisibility() != View.GONE) {
+            totalWidth = givenWidth;
+        }
         setMeasuredDimension(totalWidth, givenHeight);
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
+        if (mProfileBadge.getVisibility() != View.GONE) {
+            int paddingEnd = getPaddingEnd();
+            if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+                mProfileBadge.layout(paddingEnd,
+                        mProfileBadge.getTop(),
+                        paddingEnd + mProfileBadge.getMeasuredWidth(),
+                        mProfileBadge.getBottom());
+            } else {
+                mProfileBadge.layout(getWidth() - paddingEnd - mProfileBadge.getMeasuredWidth(),
+                        mProfileBadge.getTop(),
+                        getWidth() - paddingEnd,
+                        mProfileBadge.getBottom());
+            }
+        }
         updateTouchListener();
     }
 
@@ -305,4 +326,20 @@
         }
         return this;
     }
+
+    public ImageView getExpandButton() {
+        return mExpandButton;
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    public boolean isInTouchRect(float x, float y) {
+        if (mExpandClickListener == null) {
+            return false;
+        }
+        return mTouchListener.isInside(x, y);
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b5b0baa..2a7b92d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24,6 +24,7 @@
 import android.annotation.FloatRange;
 import android.annotation.IdRes;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -21563,6 +21564,11 @@
         private static final int MODE_SHIFT = 30;
         private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
 
+        /** @hide */
+        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface MeasureSpecMode {}
+
         /**
          * Measure specification mode: The parent has not imposed any constraint
          * on the child. It can be whatever size it wants.
@@ -21603,7 +21609,8 @@
          * @param mode the mode of the measure specification
          * @return the measure specification based on size and mode
          */
-        public static int makeMeasureSpec(int size, int mode) {
+        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
+                                          @MeasureSpecMode int mode) {
             if (sUseBrokenMakeMeasureSpec) {
                 return size + mode;
             } else {
@@ -21632,7 +21639,9 @@
          *         {@link android.view.View.MeasureSpec#AT_MOST} or
          *         {@link android.view.View.MeasureSpec#EXACTLY}
          */
+        @MeasureSpecMode
         public static int getMode(int measureSpec) {
+            //noinspection ResourceType
             return (measureSpec & MODE_MASK);
         }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 868ddda..27e2ea3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1401,6 +1401,10 @@
                 root.setDragFocus(target);
 
                 final int action = event.mAction;
+                // Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED.
+                event.mX = 0;
+                event.mY = 0;
+
                 // If we've dragged off of a child view or this window, send it the EXITED message
                 if (mCurrentDragView != null) {
                     final View view = mCurrentDragView;
@@ -1429,6 +1433,8 @@
                     }
                 }
                 event.mAction = action;  // restore the event's original state
+                event.mX = tx;
+                event.mY = ty;
             }
 
             // Dispatch the actual drag location notice, localized into its coordinates
@@ -1653,10 +1659,9 @@
                     && isChildrenDrawingOrderEnabled();
             final View[] children = mChildren;
             for (int i = childrenCount - 1; i >= 0; i--) {
-                final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
-                final View child = (preorderedList == null)
-                        ? children[childIndex] : preorderedList.get(childIndex);
-                PointF point = getLocalPoint();
+                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+                final PointF point = getLocalPoint();
                 if (isTransformedTouchPointInView(x, y, child, point)) {
                     final PointerIcon pointerIcon = child.getPointerIcon(event, point.x, point.y);
                     if (pointerIcon != null) {
@@ -1672,6 +1677,22 @@
         return super.getPointerIcon(event, x, y);
     }
 
+    private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
+        final int childIndex;
+        if (customOrder) {
+            final int childIndex1 = getChildDrawingOrder(childrenCount, i);
+            if (childIndex1 >= childrenCount) {
+                throw new IndexOutOfBoundsException("getChildDrawingOrder() "
+                        + "returned invalid index " + childIndex1
+                        + " (child count is " + childrenCount + ")");
+            }
+            childIndex = childIndex1;
+        } else {
+            childIndex = i;
+        }
+        return childIndex;
+    }
+
     @SuppressWarnings({"ConstantConditions"})
     @Override
     protected boolean dispatchHoverEvent(MotionEvent event) {
@@ -1699,9 +1720,10 @@
                 final View[] children = mChildren;
                 HoverTarget lastHoverTarget = null;
                 for (int i = childrenCount - 1; i >= 0; i--) {
-                    int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
-                    final View child = (preorderedList == null)
-                            ? children[childIndex] : preorderedList.get(childIndex);
+                    final int childIndex = getAndVerifyPreorderedIndex(
+                            childrenCount, i, customOrder);
+                    final View child = getAndVerifyPreorderedView(
+                            preorderedList, children, childIndex);
                     if (!canViewReceivePointerEvents(child)
                             || !isTransformedTouchPointInView(x, y, child, null)) {
                         continue;
@@ -1983,9 +2005,8 @@
                     && isChildrenDrawingOrderEnabled();
             final View[] children = mChildren;
             for (int i = childrenCount - 1; i >= 0; i--) {
-                int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
-                final View child = (preorderedList == null)
-                        ? children[childIndex] : preorderedList.get(childIndex);
+                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                 if (!canViewReceivePointerEvents(child)
                         || !isTransformedTouchPointInView(x, y, child, null)) {
                     continue;
@@ -2132,10 +2153,10 @@
                                 && isChildrenDrawingOrderEnabled();
                         final View[] children = mChildren;
                         for (int i = childrenCount - 1; i >= 0; i--) {
-                            final int childIndex = customOrder
-                                    ? getChildDrawingOrder(childrenCount, i) : i;
-                            final View child = (preorderedList == null)
-                                    ? children[childIndex] : preorderedList.get(childIndex);
+                            final int childIndex = getAndVerifyPreorderedIndex(
+                                    childrenCount, i, customOrder);
+                            final View child = getAndVerifyPreorderedView(
+                                    preorderedList, children, childIndex);
 
                             // If there is a view that has accessibility focus we want it
                             // to get the event first and if not handled we will perform a
@@ -2313,7 +2334,7 @@
      * Resets the cancel next up flag.
      * Returns true if the flag was previously set.
      */
-    private static boolean resetCancelNextUpFlag(View view) {
+    private static boolean resetCancelNextUpFlag(@NonNull View view) {
         if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
             view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
             return true;
@@ -2366,7 +2387,7 @@
      * Gets the touch target for specified child view.
      * Returns null if not found.
      */
-    private TouchTarget getTouchTarget(View child) {
+    private TouchTarget getTouchTarget(@NonNull View child) {
         for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
             if (target.child == child) {
                 return target;
@@ -2379,8 +2400,8 @@
      * Adds a touch target for specified child to the beginning of the list.
      * Assumes the target child is not already present.
      */
-    private TouchTarget addTouchTarget(View child, int pointerIdBits) {
-        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
+    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
+        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
         target.next = mFirstTouchTarget;
         mFirstTouchTarget = target;
         return target;
@@ -2442,7 +2463,7 @@
      * Returns true if a child view can receive pointer events.
      * @hide
      */
-    private static boolean canViewReceivePointerEvents(View child) {
+    private static boolean canViewReceivePointerEvents(@NonNull View child) {
         return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                 || child.getAnimation() != null;
     }
@@ -2888,7 +2909,7 @@
                     for (int i=0; i<childrenCount; i++) {
                         int childIndex;
                         try {
-                            childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+                            childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                         } catch (IndexOutOfBoundsException e) {
                             childIndex = i;
                             if (mContext.getApplicationInfo().targetSdkVersion
@@ -2933,9 +2954,10 @@
                                 throw e;
                             }
                         }
-                        final View child = (preorderedList == null)
-                                ? children[childIndex] : preorderedList.get(childIndex);
-                        ViewStructure cstructure = structure.newChild(i);
+
+                        final View child = getAndVerifyPreorderedView(
+                                preorderedList, children, childIndex);
+                        final ViewStructure cstructure = structure.newChild(i);
                         child.dispatchProvideStructure(cstructure);
                     }
                 }
@@ -2943,6 +2965,21 @@
         }
     }
 
+    private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
+            int childIndex) {
+        final View child;
+        if (preorderedList != null) {
+            child = preorderedList.get(childIndex);
+            if (child == null) {
+                throw new RuntimeException("Invalid preorderedList contained null child at index "
+                        + childIndex);
+            }
+        } else {
+            child = children[childIndex];
+        }
+        return child;
+    }
+
     /** @hide */
     @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
@@ -3380,9 +3417,9 @@
                     transientIndex = -1;
                 }
             }
-            int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
-            final View child = (preorderedList == null)
-                    ? children[childIndex] : preorderedList.get(childIndex);
+
+            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
             if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                 more |= drawChild(canvas, child, drawingTime);
             }
@@ -3502,21 +3539,21 @@
      * children.
      */
     ArrayList<View> buildOrderedChildList() {
-        final int count = mChildrenCount;
-        if (count <= 1 || !hasChildWithZ()) return null;
+        final int childrenCount = mChildrenCount;
+        if (childrenCount <= 1 || !hasChildWithZ()) return null;
 
         if (mPreSortedChildren == null) {
-            mPreSortedChildren = new ArrayList<View>(count);
+            mPreSortedChildren = new ArrayList<>(childrenCount);
         } else {
-            mPreSortedChildren.ensureCapacity(count);
+            mPreSortedChildren.ensureCapacity(childrenCount);
         }
 
-        final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
-        for (int i = 0; i < mChildrenCount; i++) {
+        final boolean customOrder = isChildrenDrawingOrderEnabled();
+        for (int i = 0; i < childrenCount; i++) {
             // add next child (in child order) to end of list
-            int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i;
-            View nextChild = mChildren[childIndex];
-            float currentZ = nextChild.getZ();
+            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+            final View nextChild = mChildren[childIndex];
+            final float currentZ = nextChild.getZ();
 
             // insert ahead of any Views with greater Z
             int insertIndex = i;
@@ -6053,6 +6090,7 @@
             }
             break;
         }
+        //noinspection ResourceType
         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
     }
 
@@ -7475,7 +7513,11 @@
         private TouchTarget() {
         }
 
-        public static TouchTarget obtain(View child, int pointerIdBits) {
+        public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
+            if (child == null) {
+                throw new IllegalArgumentException("child must be non-null");
+            }
+
             final TouchTarget target;
             synchronized (sRecycleLock) {
                 if (sRecycleBin == null) {
@@ -7493,6 +7535,10 @@
         }
 
         public void recycle() {
+            if (child == null) {
+                throw new IllegalStateException("already recycled once");
+            }
+
             synchronized (sRecycleLock) {
                 if (sRecycledCount < MAX_RECYCLED) {
                     next = sRecycleBin;
@@ -7522,7 +7568,11 @@
         private HoverTarget() {
         }
 
-        public static HoverTarget obtain(View child) {
+        public static HoverTarget obtain(@NonNull View child) {
+            if (child == null) {
+                throw new IllegalArgumentException("child must be non-null");
+            }
+
             final HoverTarget target;
             synchronized (sRecycleLock) {
                 if (sRecycleBin == null) {
@@ -7530,7 +7580,7 @@
                 } else {
                     target = sRecycleBin;
                     sRecycleBin = target.next;
-                     sRecycledCount--;
+                    sRecycledCount--;
                     target.next = null;
                 }
             }
@@ -7539,6 +7589,10 @@
         }
 
         public void recycle() {
+            if (child == null) {
+                throw new IllegalStateException("already recycled once");
+            }
+
             synchronized (sRecycleLock) {
                 if (sRecycledCount < MAX_RECYCLED) {
                     next = sRecycleBin;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 4a1142f..6e38b32 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -441,12 +441,6 @@
         public int getCameraLensCoverState();
 
         /**
-         * Switch the keyboard layout for the given device.
-         * Direction should be +1 or -1 to go to the next or previous keyboard layout.
-         */
-        public void switchKeyboardLayout(int deviceId, int direction);
-
-        /**
          * Switch the input method, to be precise, input method subtype.
          *
          * @param forwardDirection {@code true} to rotate in a forward direction.
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 3ff9522..85893b0 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -341,20 +342,26 @@
     public Bundle extras;
 
     /**
-     * Additional context information that tells what languages are expected by the user.
+     * List of the languages that the user is supposed to switch to no matter what input method
+     * subtype is currently used.  This special "hint" can be used mainly for, but not limited to,
+     * multilingual users who want IMEs to switch language context automatically.
      *
-     * <p><strong>IME authors:</strong> Possible use cases for IME developers would be:</p>
-     * <ul>
-     *     <li>Automatically switching keyboard layout.</li>
-     *     <li>Changing language model for better typing experience.</li>
-     * </ul>
+     * <p>{@code null} means that no special language "hint" is needed.</p>
      *
-     * <p><strong>Editor authors:</strong> Providing this context information can help IMEs to
-     * improve text input experience.  For example, chat applications can remember what language is
-     * used in the last conversation for each chat session, and put the last used language at the
-     * top of {@link #locales}.</p>
+     * <p><strong>Editor authors:</strong> Specify this only when you are confident that the user
+     * will switch to certain languages in this context no matter what input method subtype is
+     * currently selected.  Otherwise, keep this {@code null}.  Explicit user actions and/or
+     * preferences would be good signals to specify this special "hint",  For example, a chat
+     * application may be able to put the last used language at the top of {@link #hintLocales}
+     * based on whom the user is going to talk, by remembering what language is used in the last
+     * conversation.  Do not specify {@link android.widget.TextView#getTextLocales()} only because
+     * it is used for text rendering.</p>
+     *
+     * @see android.widget.TextView#setImeHintLocales(LocaleList)
+     * @see android.widget.TextView#getImeHintLocales()
      */
-    public LocaleList locales = LocaleList.getEmptyLocaleList();
+    @Nullable
+    public LocaleList hintLocales = null;
 
     /**
      * Ensure that the data in this EditorInfo is compatible with an application
@@ -410,7 +417,7 @@
                 + " fieldId=" + fieldId
                 + " fieldName=" + fieldName);
         pw.println(prefix + "extras=" + extras);
-        pw.println(prefix + "locales=" + locales);
+        pw.println(prefix + "hintLocales=" + hintLocales);
     }
 
     /**
@@ -434,7 +441,11 @@
         dest.writeInt(fieldId);
         dest.writeString(fieldName);
         dest.writeBundle(extras);
-        locales.writeToParcel(dest, flags);
+        if (hintLocales != null) {
+            hintLocales.writeToParcel(dest, flags);
+        } else {
+            LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
+        }
     }
 
     /**
@@ -458,7 +469,8 @@
                     res.fieldId = source.readInt();
                     res.fieldName = source.readString();
                     res.extras = source.readBundle();
-                    res.locales = LocaleList.CREATOR.createFromParcel(source);
+                    LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
+                    res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
                     return res;
                 }
 
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 4fc6665..43306d0 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -191,6 +191,8 @@
                                     .InputMethod_Subtype_label, 0))
                             .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable
                                     .InputMethod_Subtype_icon, 0))
+                            .setLanguageTag(a.getString(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_languageTag))
                             .setSubtypeLocale(a.getString(com.android.internal.R.styleable
                                     .InputMethod_Subtype_imeSubtypeLocale))
                             .setSubtypeMode(a.getString(com.android.internal.R.styleable
diff --git a/core/java/android/webkit/ServiceWorkerClient.java b/core/java/android/webkit/ServiceWorkerClient.java
new file mode 100644
index 0000000..85de698
--- /dev/null
+++ b/core/java/android/webkit/ServiceWorkerClient.java
@@ -0,0 +1,41 @@
+/*
+ * 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.webkit;
+
+
+public class ServiceWorkerClient {
+
+    /**
+     * Notify the host application of a resource request and allow the
+     * application to return the data. If the return value is null, the
+     * Service Worker will continue to load the resource as usual.
+     * Otherwise, the return response and data will be used.
+     * NOTE: This method is called on a thread other than the UI thread
+     * so clients should exercise caution when accessing private data
+     * or the view system.
+     *
+     * @param request Object containing the details of the request.
+     * @return A {@link android.webkit.WebResourceResponse} containing the
+     *         response information or null if the WebView should load the
+     *         resource itself.
+     * @see {@link WebViewClient#shouldInterceptRequest()}
+     */
+    public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
+        return null;
+    }
+}
+
diff --git a/core/java/android/webkit/ServiceWorkerController.java b/core/java/android/webkit/ServiceWorkerController.java
new file mode 100644
index 0000000..9115558
--- /dev/null
+++ b/core/java/android/webkit/ServiceWorkerController.java
@@ -0,0 +1,52 @@
+/*
+ * 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.webkit;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Manages Service Workers used by WebView.
+ */
+public abstract class ServiceWorkerController {
+
+    /**
+     * Returns the default ServiceWorkerController instance. At present there is
+     * only one ServiceWorkerController instance for all WebView instances,
+     * however this restriction may be relaxed in the future.
+     *
+     * @return The default ServiceWorkerController instance.
+     */
+     @NonNull
+     public static ServiceWorkerController getInstance() {
+         return WebViewFactory.getProvider().getServiceWorkerController();
+     }
+
+    /**
+     * Gets the settings for all service workers.
+     *
+     * @return The current ServiceWorkerWebSettings
+     */
+    @NonNull
+    public abstract ServiceWorkerWebSettings getServiceWorkerWebSettings();
+
+    /**
+     * Sets the client to capture service worker related callbacks.
+     */
+    public abstract void setServiceWorkerClient(@Nullable ServiceWorkerClient client);
+}
+
diff --git a/core/java/android/webkit/ServiceWorkerWebSettings.java b/core/java/android/webkit/ServiceWorkerWebSettings.java
new file mode 100644
index 0000000..8b104d1c
--- /dev/null
+++ b/core/java/android/webkit/ServiceWorkerWebSettings.java
@@ -0,0 +1,88 @@
+/*
+ * 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.webkit;
+
+/**
+ * Manages settings state for all Service Workers. These settings are not tied to
+ * the lifetime of any WebView because service workers can outlive WebView instances.
+ * The settings are similar to {@link WebSettings} but only settings relevant to
+ * Service Workers are supported.
+ */
+// This is an abstract base class: concrete ServiceWorkerControllers must
+// create a class derived from this, and return an instance of it in the
+// ServiceWorkerController.getServiceWorkerWebSettings() method implementation.
+public abstract class ServiceWorkerWebSettings {
+
+    /**
+     * Overrides the way the cache is used, see {@link WebSettings#setCacheMode}.
+     *
+     * @param mode the mode to use
+     */
+    public abstract void setCacheMode(int mode);
+
+    /**
+     * Gets the current setting for overriding the cache mode.
+     *
+     * @return the current setting for overriding the cache mode
+     * @see #setCacheMode
+     */
+    public abstract int getCacheMode();
+
+    /**
+     * Enables or disables content URL access from Service Workers, see
+     * {@link WebSettings#setAllowContentAccess}.
+     */
+    public abstract void setAllowContentAccess(boolean allow);
+
+    /**
+     * Gets whether Service Workers support content URL access.
+     *
+     * @see #setAllowContentAccess
+     */
+    public abstract boolean getAllowContentAccess();
+
+    /**
+     * Enables or disables file access within Service Workers, see
+     * {@link WebSettings#setAllowFileAccess}.
+     */
+    public abstract void setAllowFileAccess(boolean allow);
+
+    /**
+     * Gets whether Service Workers support file access.
+     *
+     * @see #setAllowFileAccess
+     */
+    public abstract boolean getAllowFileAccess();
+
+    /**
+     * Sets whether the Service Workers should not load resources from the network,
+     * see {@link WebSettings#setBlockNetworkLoads}.
+     *
+     * @param flag whether the Service Workers should not load any resources from the
+     *             network
+     */
+    public abstract void setBlockNetworkLoads(boolean flag);
+
+    /**
+     * Gets whether Service Workers are prohibited from loading any resources from the network.
+     *
+     * @return true if the Service Workers are not allowed to load any resources from the network
+     * @see #setBlockNetworkLoads
+     */
+    public abstract boolean getBlockNetworkLoads();
+}
+
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 054eafc..b04b4c0 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -295,15 +295,27 @@
 
             Application initialApplication = AppGlobals.getInitialApplication();
             Context webViewContext = null;
-            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "initialApplication.createPackageContext()");
+            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "PackageManager.getApplicationInfo()");
             try {
                 // Construct a package context to load the Java code into the current app.
                 // This is done as early as possible since by constructing a package context we
                 // register the WebView package as a dependency for the current application so that
                 // when the WebView package is updated this application will be killed.
-                webViewContext = initialApplication.createPackageContext(
-                        sPackageInfo.packageName,
-                        Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+                ApplicationInfo applicationInfo =
+                    initialApplication.getPackageManager().getApplicationInfo(
+                        sPackageInfo.packageName, PackageManager.GET_SHARED_LIBRARY_FILES
+                        | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
+                        // make sure that we fetch the current provider even if its not installed
+                        // for the current user
+                        | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
+                        "initialApplication.createApplicationContext");
+                try {
+                    webViewContext = initialApplication.createApplicationContext(applicationInfo,
+                            Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+                } finally {
+                    Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
+                }
             } catch (PackageManager.NameNotFoundException e) {
                 throw new MissingWebViewPackageException(e);
             } finally {
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 2b66a83..8359a10 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -111,6 +111,14 @@
     TokenBindingService getTokenBindingService();
 
     /**
+     * Gets the ServiceWorkerController instance for this WebView implementation. The
+     * implementation must return the same instance on subsequent calls.
+     *
+     * @return the ServiceWorkerController instance
+     */
+    ServiceWorkerController getServiceWorkerController();
+
+    /**
      * Gets the singleton WebIconDatabase instance for this WebView implementation. The
      * implementation must return the same instance on subsequent calls.
      *
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6241a4c..6c2c956 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3203,42 +3203,74 @@
         return mContextMenuInfo;
     }
 
-    /** @hide */
+    @Override
+    public boolean showContextMenu() {
+        return showContextMenuInternal(0, 0, false);
+    }
+
     @Override
     public boolean showContextMenu(float x, float y) {
+        return showContextMenuInternal(x, y, true);
+    }
+
+    private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
         final int position = pointToPosition((int)x, (int)y);
         if (position != INVALID_POSITION) {
             final long id = mAdapter.getItemId(position);
             View child = getChildAt(position - mFirstPosition);
             if (child != null) {
                 mContextMenuInfo = createContextMenuInfo(child, position, id);
-                return super.showContextMenuForChild(AbsListView.this, x, y);
+                if (useOffsets) {
+                    return super.showContextMenuForChild(this, x, y);
+                } else {
+                    return super.showContextMenuForChild(this);
+                }
             }
         }
-        return super.showContextMenu(x, y);
+        if (useOffsets) {
+            return super.showContextMenu(x, y);
+        } else {
+            return super.showContextMenu();
+        }
     }
 
     @Override
     public boolean showContextMenuForChild(View originalView) {
-        final int longPressPosition = getPositionForView(originalView);
-        if (longPressPosition >= 0) {
-            final long longPressId = mAdapter.getItemId(longPressPosition);
-            boolean handled = false;
+        return showContextMenuForChildInternal(originalView, 0, 0, false);
+    }
 
-            if (mOnItemLongClickListener != null) {
-                handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
-                        longPressPosition, longPressId);
-            }
-            if (!handled) {
-                mContextMenuInfo = createContextMenuInfo(
-                        getChildAt(longPressPosition - mFirstPosition),
-                        longPressPosition, longPressId);
+    @Override
+    public boolean showContextMenuForChild(View originalView, float x, float y) {
+        return showContextMenuForChildInternal(originalView,x, y, true);
+    }
+
+    private boolean showContextMenuForChildInternal(View originalView, float x, float y,
+            boolean useOffsets) {
+        final int longPressPosition = getPositionForView(originalView);
+        if (longPressPosition < 0) {
+            return false;
+        }
+
+        final long longPressId = mAdapter.getItemId(longPressPosition);
+        boolean handled = false;
+
+        if (mOnItemLongClickListener != null) {
+            handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
+                    longPressPosition, longPressId);
+        }
+
+        if (!handled) {
+            final View child = getChildAt(longPressPosition - mFirstPosition);
+            mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
+
+            if (useOffsets) {
+                handled = super.showContextMenuForChild(originalView, x, y);
+            } else {
                 handled = super.showContextMenuForChild(originalView);
             }
-
-            return handled;
         }
-        return false;
+
+        return handled;
     }
 
     @Override
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index df5af25..4355eb3 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -69,6 +69,7 @@
 import android.text.style.TextAppearanceSpan;
 import android.text.style.URLSpan;
 import android.util.DisplayMetrics;
+import android.util.LocaleList;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.ActionMode;
@@ -5299,6 +5300,7 @@
         Bundle extras;
         OnEditorActionListener onEditorActionListener;
         boolean enterDown;
+        LocaleList imeHintLocales;
     }
 
     static class InputMethodState {
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index 9ebbe36..a6ef572 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.annotation.NonNull;
 import android.annotation.Widget;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -1097,15 +1098,15 @@
     }
     
     @Override
-    public void onLongPress(MotionEvent e) {
-        
+    public void onLongPress(@NonNull MotionEvent e) {
         if (mDownTouchPosition < 0) {
             return;
         }
         
         performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        long id = getItemIdAtPosition(mDownTouchPosition);
-        dispatchLongPress(mDownTouchView, mDownTouchPosition, id);
+
+        final long id = getItemIdAtPosition(mDownTouchPosition);
+        dispatchLongPress(mDownTouchView, mDownTouchPosition, id, e.getX(), e.getY(), true);
     }
 
     // Unused methods from GestureDetector.OnGestureListener below
@@ -1159,29 +1160,47 @@
 
     @Override
     public boolean showContextMenuForChild(View originalView) {
+        return showContextMenuForChildInternal(originalView, 0, 0, false);
+    }
 
+    @Override
+    public boolean showContextMenuForChild(View originalView, float x, float y) {
+        return showContextMenuForChildInternal(originalView, x, y, true);
+    }
+
+    private boolean showContextMenuForChildInternal(View originalView, float x, float y,
+            boolean useOffsets) {
         final int longPressPosition = getPositionForView(originalView);
         if (longPressPosition < 0) {
             return false;
         }
         
         final long longPressId = mAdapter.getItemId(longPressPosition);
-        return dispatchLongPress(originalView, longPressPosition, longPressId);
+        return dispatchLongPress(originalView, longPressPosition, longPressId, x, y, useOffsets);
     }
 
     @Override
     public boolean showContextMenu() {
-        
+        return showContextMenuInternal(0, 0, false);
+    }
+
+    @Override
+    public boolean showContextMenu(float x, float y) {
+        return showContextMenuInternal(x, y, true);
+    }
+
+    private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
         if (isPressed() && mSelectedPosition >= 0) {
-            int index = mSelectedPosition - mFirstPosition;
-            View v = getChildAt(index);
-            return dispatchLongPress(v, mSelectedPosition, mSelectedRowId);
+            final int index = mSelectedPosition - mFirstPosition;
+            final View v = getChildAt(index);
+            return dispatchLongPress(v, mSelectedPosition, mSelectedRowId, x, y, useOffsets);
         }        
         
         return false;
     }
 
-    private boolean dispatchLongPress(View view, int position, long id) {
+    private boolean dispatchLongPress(View view, int position, long id, float x, float y,
+            boolean useOffsets) {
         boolean handled = false;
         
         if (mOnItemLongClickListener != null) {
@@ -1191,7 +1210,12 @@
 
         if (!handled) {
             mContextMenuInfo = new AdapterContextMenuInfo(view, position, id);
-            handled = super.showContextMenuForChild(this);
+
+            if (useOffsets) {
+                handled = super.showContextMenuForChild(view, x, y);
+            } else {
+                handled = super.showContextMenuForChild(this);
+            }
         }
 
         if (handled) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 81ebdb3..c626af6 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -645,6 +645,16 @@
      */
     private Editor mEditor;
 
+    private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
+    private static final int DEVICE_PROVISIONED_NO = 1;
+    private static final int DEVICE_PROVISIONED_YES = 2;
+
+    /**
+     * Some special options such as sharing selected text should only be shown if the device
+     * is provisioned. Only check the provisioned state once for a given view instance.
+     */
+    private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
+
     /*
      * Kick-start the font cache for the zygote process (to pay the cost of
      * initializing freetype for our default font only once).
@@ -4992,6 +5002,35 @@
     }
 
     /**
+     * Change "hint" locales associated with the text view, which will be reported to an IME with
+     * {@link EditorInfo#hintLocales} when it has focus.
+     *
+     * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
+     * call {@link InputMethodManager#restartInput(View)}.</p>
+     * @param hintLocales List of the languages that the user is supposed to switch to no matter
+     * what input method subtype is currently used. Set {@code null} to clear the current "hint".
+     * @see #getImeHIntLocales()
+     * @see android.view.inputmethod.EditorInfo#hintLocales
+     */
+    public void setImeHintLocales(@Nullable LocaleList hintLocales) {
+        createEditorIfNeeded();
+        mEditor.createInputContentTypeIfNeeded();
+        mEditor.mInputContentType.imeHintLocales = hintLocales;
+    }
+
+    /**
+     * @return The current languages list "hint". {@code null} when no "hint" is available.
+     * @see #setImeHintLocales(LocaleList)
+     * @see android.view.inputmethod.EditorInfo#hintLocales
+     */
+    @Nullable
+    public LocaleList getImeHintLocales() {
+        if (mEditor == null) { return null; }
+        if (mEditor.mInputContentType == null) { return null; }
+        return mEditor.mInputContentType.imeHintLocales;
+    }
+
+    /**
      * Returns the error message that was set to be displayed with
      * {@link #setError}, or <code>null</code> if no error was set
      * or if it the error was cleared by the widget after user input.
@@ -6411,8 +6450,10 @@
                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
                 outAttrs.extras = mEditor.mInputContentType.extras;
+                outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
             } else {
                 outAttrs.imeOptions = EditorInfo.IME_NULL;
+                outAttrs.hintLocales = null;
             }
             if (focusSearch(FOCUS_DOWN) != null) {
                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
@@ -6440,9 +6481,6 @@
                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
             }
             outAttrs.hintText = mHint;
-            // LocaleList is designed to be immutable.  This is theoretically equivalent to copy
-            // the snapshot of the current text locales.
-            outAttrs.locales = getTextLocales();
             if (mText instanceof Editable) {
                 InputConnection ic = new EditableInputConnection(this);
                 outAttrs.initialSelStart = getSelectionStart();
@@ -9613,7 +9651,17 @@
     }
 
     boolean canShare() {
-        return canCopy();
+        return canCopy() && isDeviceProvisioned();
+    }
+
+    boolean isDeviceProvisioned() {
+        if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
+            mDeviceProvisionedState = Settings.Global.getInt(
+                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
+                    ? DEVICE_PROVISIONED_YES
+                    : DEVICE_PROVISIONED_NO;
+        }
+        return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
     }
 
     boolean canPaste() {
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index b1fc20d..444dbfa 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -239,6 +239,16 @@
     int sendBackupData(int numBytes);
     void cancelFullBackup();
 
+    /**
+     * Ask the transport whether this app is eligible for backup.
+     *
+     * @param targetPackage The identity of the application.
+     * @param isFullBackup If set, transport should check if app is eligible for full data backup,
+     *   otherwise to check if eligible for key-value backup.
+     * @return Whether this app is eligible for backup.
+     */
+    boolean isAppEligibleForBackup(in PackageInfo targetPackage, boolean isFullBackup);
+
     // full restore stuff
 
     /**
@@ -286,5 +296,4 @@
      *    operation will immediately be finished with no further attempts to restore app data.
      */
     int abortFullRestore();
-
 }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
new file mode 100644
index 0000000..975021e8
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
@@ -0,0 +1,71 @@
+/*
+ * 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.internal.inputmethod;
+
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.Objects;
+
+public class InputMethodSubtypeHandle {
+    private final String mInputMethodId;
+    private final int mSubtypeId;
+
+    public InputMethodSubtypeHandle(InputMethodInfo info, InputMethodSubtype subtype) {
+        mInputMethodId = info.getId();
+        if (subtype != null) {
+            mSubtypeId = subtype.hashCode();
+        } else {
+            mSubtypeId = 0;
+        }
+    }
+
+    public InputMethodSubtypeHandle(String inputMethodId, int subtypeId) {
+        mInputMethodId = inputMethodId;
+        mSubtypeId = subtypeId;
+    }
+
+    public String getInputMethodId() {
+        return mInputMethodId;
+    }
+
+    public int getSubtypeId() {
+        return mSubtypeId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof InputMethodSubtypeHandle)) {
+            return false;
+        }
+        InputMethodSubtypeHandle other = (InputMethodSubtypeHandle) o;
+        return TextUtils.equals(mInputMethodId, other.getInputMethodId())
+                && mSubtypeId == other.getSubtypeId();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mInputMethodId) * 31 + mSubtypeId;
+    }
+
+    @Override
+    public String toString() {
+        return "InputMethodSubtypeHandle{mInputMethodId=" + mInputMethodId
+            + ", mSubtypeId=" + mSubtypeId + "}";
+    }
+}
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index 59a1e4a..5b40bc0 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.util.Log;
 
 import java.util.ArrayList;
 
@@ -30,6 +31,9 @@
  */
 public class DividerSnapAlgorithm {
 
+    private static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400;
+    private static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600;
+
     /**
      * 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio
      */
@@ -46,6 +50,7 @@
     private static final int SNAP_ONLY_1_1 = 2;
 
     private final float mMinFlingVelocityPxPerSecond;
+    private final float mMinDismissVelocityPxPerSecond;
     private final int mDisplayWidth;
     private final int mDisplayHeight;
     private final int mDividerSize;
@@ -64,10 +69,12 @@
     private final SnapTarget mDismissEndTarget;
     private final SnapTarget mMiddleTarget;
 
-    public DividerSnapAlgorithm(Resources res, float minFlingVelocityPxPerSecond,
-            int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision,
-            Rect insets) {
-        mMinFlingVelocityPxPerSecond = minFlingVelocityPxPerSecond;
+    public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+            boolean isHorizontalDivision, Rect insets) {
+        mMinFlingVelocityPxPerSecond =
+                MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
+        mMinDismissVelocityPxPerSecond =
+                MIN_DISMISS_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
         mDividerSize = dividerSize;
         mDisplayWidth = displayWidth;
         mDisplayHeight = displayHeight;
@@ -85,15 +92,24 @@
     }
 
     public SnapTarget calculateSnapTarget(int position, float velocity) {
-        if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
-            return snap(position);
-        }
-        if (position < mFirstSplitTarget.position && velocity < 0) {
+        return calculateSnapTarget(position, velocity, true /* hardDismiss */);
+    }
+
+    /**
+     * @param position the top/left position of the divider
+     * @param velocity current dragging velocity
+     * @param hardDismiss if set, make it a bit harder to get reach the dismiss targets
+     */
+    public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) {
+        if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) {
             return mDismissStartTarget;
         }
-        if (position > mLastSplitTarget.position && velocity > 0) {
+        if (position > mLastSplitTarget.position && velocity > mMinDismissVelocityPxPerSecond) {
             return mDismissEndTarget;
         }
+        if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
+            return snap(position, hardDismiss);
+        }
         if (velocity < 0) {
             return mFirstSplitTarget;
         } else {
@@ -102,7 +118,7 @@
     }
 
     public SnapTarget calculateNonDismissingSnapTarget(int position) {
-        SnapTarget target = snap(position);
+        SnapTarget target = snap(position, false /* hardDismiss */);
         if (target == mDismissStartTarget) {
             return mFirstSplitTarget;
         } else if (target == mDismissEndTarget) {
@@ -146,12 +162,16 @@
         return mDismissEndTarget;
     }
 
-    private SnapTarget snap(int position) {
+    private SnapTarget snap(int position, boolean hardDismiss) {
         int minIndex = -1;
-        int minDistance = Integer.MAX_VALUE;
+        float minDistance = Float.MAX_VALUE;
         int size = mTargets.size();
         for (int i = 0; i < size; i++) {
-            int distance = Math.abs(position - mTargets.get(i).position);
+            SnapTarget target = mTargets.get(i);
+            float distance = Math.abs(position - target.position);
+            if (hardDismiss) {
+                distance /= target.distanceMultiplier;
+            }
             if (distance < minDistance) {
                 minIndex = i;
                 minDistance = distance;
@@ -165,7 +185,7 @@
         int dividerMax = isHorizontalDivision
                 ? mDisplayHeight
                 : mDisplayWidth;
-        mTargets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
+        mTargets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START, 0.35f));
         switch (mSnapMode) {
             case SNAP_MODE_16_9:
                 addRatio16_9Targets(isHorizontalDivision);
@@ -177,7 +197,7 @@
                 addMiddleTarget(isHorizontalDivision);
                 break;
         }
-        mTargets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
+        mTargets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
     }
 
     private void addFixedDivisionTargets(boolean isHorizontalDivision) {
@@ -232,9 +252,20 @@
         public final int position;
         public final int flag;
 
+        /**
+         * Multiplier used to calculate distance to snap position. The lower this value, the harder
+         * it's to snap on this target
+         */
+        private final float distanceMultiplier;
+
         public SnapTarget(int position, int flag) {
+            this(position, flag, 1f);
+        }
+
+        public SnapTarget(int position, int flag, float distanceMultiplier) {
             this.position = position;
             this.flag = flag;
+            this.distanceMultiplier = distanceMultiplier;
         }
     }
 }
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index 0597d3f..309d35b 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -62,6 +62,15 @@
     layout->doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint);
 }
 
+float MinikinUtils::measureText(const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
+        const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances) {
+    FontCollection *font;
+    MinikinPaint minikinPaint;
+    FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, &font, paint, typeface);
+    return Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint,
+            font, advances);
+}
+
 bool MinikinUtils::hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs) {
     const TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
     return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index 5bf1eec..9152539 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -40,6 +40,9 @@
             TypefaceImpl* typeface, const uint16_t* buf, size_t start, size_t count,
             size_t bufSize);
 
+    static float measureText(const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
+            const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances);
+
     static bool hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs);
 
     static float xOffsetForTextAlign(Paint* paint, const Layout& layout);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 98f8ce3..a3214eb 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -493,16 +493,16 @@
                 return 0;
             }
         }
-
-        Layout layout;
-        MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, start, count,
-                contextCount);
-        if (advances != NULL) {
-            std::unique_ptr<jfloat> advancesArray(new jfloat[count]);
-            layout.getAdvances(advancesArray.get());
+        std::unique_ptr<jfloat[]> advancesArray;
+        if (advances) {
+            advancesArray.reset(new jfloat[count]);
+        }
+        const float advance = MinikinUtils::measureText(paint, bidiFlags, typeface, text,
+                start, count, contextCount, advancesArray.get());
+        if (advances) {
             env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
         }
-        return layout.getAdvance();
+        return advance;
     }
 
     static jfloat getTextAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, jlong paintHandle,
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 24eb961..f9936ae 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -23,6 +23,7 @@
 #include <inttypes.h>
 
 #include <android_runtime/AndroidRuntime.h>
+#include <androidfw/DisplayEventDispatcher.h>
 #include <utils/Log.h>
 #include <utils/Looper.h>
 #include <utils/threads.h>
@@ -48,14 +49,12 @@
 } gDisplayEventReceiverClassInfo;
 
 
-class NativeDisplayEventReceiver : public LooperCallback {
+class NativeDisplayEventReceiver : public DisplayEventDispatcher {
 public:
     NativeDisplayEventReceiver(JNIEnv* env,
             jobject receiverWeak, const sp<MessageQueue>& messageQueue);
 
-    status_t initialize();
     void dispose();
-    status_t scheduleVsync();
 
 protected:
     virtual ~NativeDisplayEventReceiver();
@@ -66,15 +65,14 @@
     DisplayEventReceiver mReceiver;
     bool mWaitingForVsync;
 
-    virtual int handleEvent(int receiveFd, int events, void* data);
-    bool processPendingEvents(nsecs_t* outTimestamp, int32_t* id, uint32_t* outCount);
-    void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);
-    void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
+    virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);
+    virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
 };
 
 
 NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env,
         jobject receiverWeak, const sp<MessageQueue>& messageQueue) :
+        DisplayEventDispatcher(messageQueue->getLooper()),
         mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
         mMessageQueue(messageQueue), mWaitingForVsync(false) {
     ALOGV("receiver %p ~ Initializing display event receiver.", this);
@@ -85,21 +83,6 @@
     env->DeleteGlobalRef(mReceiverWeakGlobal);
 }
 
-status_t NativeDisplayEventReceiver::initialize() {
-    status_t result = mReceiver.initCheck();
-    if (result) {
-        ALOGW("Failed to initialize display event receiver, status=%d", result);
-        return result;
-    }
-
-    int rc = mMessageQueue->getLooper()->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT,
-            this, NULL);
-    if (rc < 0) {
-        return UNKNOWN_ERROR;
-    }
-    return OK;
-}
-
 void NativeDisplayEventReceiver::dispose() {
     ALOGV("receiver %p ~ Disposing display event receiver.", this);
 
@@ -108,87 +91,6 @@
     }
 }
 
-status_t NativeDisplayEventReceiver::scheduleVsync() {
-    if (!mWaitingForVsync) {
-        ALOGV("receiver %p ~ Scheduling vsync.", this);
-
-        // Drain all pending events.
-        nsecs_t vsyncTimestamp;
-        int32_t vsyncDisplayId;
-        uint32_t vsyncCount;
-        processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount);
-
-        status_t status = mReceiver.requestNextVsync();
-        if (status) {
-            ALOGW("Failed to request next vsync, status=%d", status);
-            return status;
-        }
-
-        mWaitingForVsync = true;
-    }
-    return OK;
-}
-
-int NativeDisplayEventReceiver::handleEvent(int receiveFd, int events, void* data) {
-    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
-        ALOGE("Display event receiver pipe was closed or an error occurred.  "
-                "events=0x%x", events);
-        return 0; // remove the callback
-    }
-
-    if (!(events & Looper::EVENT_INPUT)) {
-        ALOGW("Received spurious callback for unhandled poll event.  "
-                "events=0x%x", events);
-        return 1; // keep the callback
-    }
-
-    // Drain all pending events, keep the last vsync.
-    nsecs_t vsyncTimestamp;
-    int32_t vsyncDisplayId;
-    uint32_t vsyncCount;
-    if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
-        ALOGV("receiver %p ~ Vsync pulse: timestamp=%" PRId64 ", id=%d, count=%d",
-                this, vsyncTimestamp, vsyncDisplayId, vsyncCount);
-        mWaitingForVsync = false;
-        dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
-    }
-
-    return 1; // keep the callback
-}
-
-bool NativeDisplayEventReceiver::processPendingEvents(
-        nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) {
-    bool gotVsync = false;
-    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
-    ssize_t n;
-    while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
-        ALOGV("receiver %p ~ Read %d events.", this, int(n));
-        for (ssize_t i = 0; i < n; i++) {
-            const DisplayEventReceiver::Event& ev = buf[i];
-            switch (ev.header.type) {
-            case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
-                // Later vsync events will just overwrite the info from earlier
-                // ones. That's fine, we only care about the most recent.
-                gotVsync = true;
-                *outTimestamp = ev.header.timestamp;
-                *outId = ev.header.id;
-                *outCount = ev.vsync.count;
-                break;
-            case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
-                dispatchHotplug(ev.header.timestamp, ev.header.id, ev.hotplug.connected);
-                break;
-            default:
-                ALOGW("receiver %p ~ ignoring unknown event type %#x", this, ev.header.type);
-                break;
-            }
-        }
-    }
-    if (n < 0) {
-        ALOGW("Failed to get events from display event receiver, status=%d", status_t(n));
-    }
-    return gotVsync;
-}
-
 void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
diff --git a/core/res/res/drawable/ic_corp_badge_no_background.xml b/core/res/res/drawable/ic_corp_badge_no_background.xml
new file mode 100644
index 0000000..b1bddfc
--- /dev/null
+++ b/core/res/res/drawable/ic_corp_badge_no_background.xml
@@ -0,0 +1,30 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M20.801,5.981L17.13,5.98l0.001,-1.471l-2.053,-2.055L8.969,2.453L6.915,4.506L6.914,5.977L3.203,5.976c-1.216,0.0 -2.189,0.983 -2.189,2.199L1.0,12.406c0.0,1.216 0.983,2.2 2.199,2.2L10.0,14.608l0.0,-1.644l0.291,0.0l3.351,0.0l0.291,0.0l0.0,1.645l6.863,0.002c1.216,0.0 2.2,-0.983 2.2,-2.199L23.0,8.181C23.0,6.965 22.017,5.981 20.801,5.981zM15.076,5.979L8.968,5.978l0.001,-1.471l6.108,0.001L15.076,5.979z"
+        android:fillColor="#FF5722"/>
+    <path
+        android:pathData="M13.911,16.646L9.978,16.646L9.978,15.48L1.673,15.48l0.0,4.105c0.0,1.216 0.959,2.2 2.175,2.2l16.13,0.004c1.216,0.0 2.203,-0.983 2.203,-2.199l0.0,-4.11l-8.27,0.0L13.910999,16.646z"
+        android:fillColor="#FF5722"/>
+    <path
+        android:pathData="M23.657,6.55 h4.72 v1.137 h-4.72z"
+        android:fillColor="#00000000"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_notification_alert.xml b/core/res/res/drawable/ic_notification_alert.xml
index d17dfc1..c8514ac 100644
--- a/core/res/res/drawable/ic_notification_alert.xml
+++ b/core/res/res/drawable/ic_notification_alert.xml
@@ -20,14 +20,14 @@
         android:viewportHeight="24.0">
     <path
         android:pathData="M18.4,2.2L17.0,3.6c2.0,1.4 3.3,3.7 3.5,6.4l2.0,0.0C22.3,6.8 20.8,4.0 18.4,2.2z"
-        android:fillColor="#231F20"/>
+        android:fillColor="#FFFFFFFF"/>
     <path
         android:pathData="M7.1,3.6L5.7,2.2C3.3,4.0 1.7,6.8 1.5,10.0l2.0,0.0C3.7,7.3 5.0,5.0 7.1,3.6z"
-        android:fillColor="#231F20"/>
+        android:fillColor="#FFFFFFFF"/>
     <path
         android:pathData="M18.5,10.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.5,3.5C13.5,2.7 12.8,2.0 12.0,2.0s-1.5,0.7 -1.5,1.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.5,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0l-2.0,-2.0L18.5,10.5zM13.0,16.5l-2.0,0.0l0.0,-2.0l2.0,0.0L13.0,16.5zM13.0,12.5l-2.0,0.0l0.0,-6.0l2.0,0.0L13.0,12.5z"
-        android:fillColor="#231F20"/>
+        android:fillColor="#FFFFFFFF"/>
     <path
         android:pathData="M12.0,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0L10.0,20.0C10.0,21.1 10.9,22.0 12.0,22.0z"
-        android:fillColor="#231F20"/>
+        android:fillColor="#FFFFFFFF"/>
 </vector>
diff --git a/core/res/res/drawable/ic_notification_block.xml b/core/res/res/drawable/ic_notification_block.xml
index 27690740..572e97b 100644
--- a/core/res/res/drawable/ic_notification_block.xml
+++ b/core/res/res/drawable/ic_notification_block.xml
@@ -20,6 +20,6 @@
         android:viewportHeight="24.0">
 
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="#FFFFFFFF"
         android:pathData="M12.0,2.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0zM4.0,12.0c0.0,-4.42 3.58,-8.0 8.0,-8.0 1.85,0.0 3.5,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4.0,13.85 4.0,12.0zm8.0,8.0c-1.85,0.0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20.0,10.15 20.0,12.0c0.0,4.42 -3.58,8.0 -8.0,8.0z"/>
 </vector>
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 2a89faa..2a4aa967 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -26,6 +26,7 @@
             android:layout_height="56dp"
             android:paddingEnd="4dp"
             android:orientation="horizontal"
+            android:gravity="center_vertical"
             android:visibility="gone"
             android:background="#ffeeeeee"
             >
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index e45f52b..163db30 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -127,5 +127,15 @@
         android:paddingTop="1dp"
         android:visibility="gone"
         />
+    <ImageView android:id="@+id/profile_badge"
+        android:layout_width="@dimen/notification_badge_size"
+        android:layout_height="@dimen/notification_badge_size"
+        android:layout_gravity="center"
+        android:layout_marginStart="4dp"
+        android:paddingTop="1dp"
+        android:scaleType="fitCenter"
+        android:visibility="gone"
+        android:contentDescription="@string/notification_work_profile_content_description"
+        />
 </NotificationHeaderView>
 
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index fdbbbd6..a37bfa9 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -34,7 +34,7 @@
         android:orientation="vertical"
         >
         <include layout="@layout/notification_template_part_line1" />
-        <include layout="@layout/notification_template_part_line3" />
+        <include layout="@layout/notification_template_text" />
     </LinearLayout>
     <FrameLayout
         android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index dfd72c0..f302087 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -42,7 +42,7 @@
             android:orientation="vertical"
             >
             <include layout="@layout/notification_template_part_line1" />
-            <include layout="@layout/notification_template_part_line3" />
+            <include layout="@layout/notification_template_text" />
         </LinearLayout>
         <FrameLayout
             android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index c3db7c5..e8ff186 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -40,7 +40,7 @@
         android:orientation="vertical"
         >
         <include layout="@layout/notification_template_part_line1" />
-        <include layout="@layout/notification_template_part_line3" />
+        <include layout="@layout/notification_template_text" />
     </LinearLayout>
     <LinearLayout
         android:id="@+id/media_actions"
diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml
index 5c07b9b..50aec6b 100644
--- a/core/res/res/layout/notification_template_material_big_picture.xml
+++ b/core/res/res/layout/notification_template_material_big_picture.xml
@@ -40,7 +40,7 @@
             android:orientation="vertical">
             <include layout="@layout/notification_template_part_line1"/>
             <include layout="@layout/notification_template_progress"/>
-            <include layout="@layout/notification_template_part_line3"/>
+            <include layout="@layout/notification_template_text"/>
         </LinearLayout>
         <ImageView
                 android:id="@+id/big_picture"
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index ebaffc3..9a4b28c 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -36,33 +36,17 @@
         >
         <include layout="@layout/notification_template_part_line1" />
         <include layout="@layout/notification_template_progress" />
-        <LinearLayout
+        <com.android.internal.widget.ImageFloatingTextView android:id="@+id/big_text"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            android:paddingBottom="@dimen/notification_content_margin_bottom"
-            android:orientation="horizontal"
-            android:gravity="top"
-            android:layout_weight="1"
             android:layout_marginTop="1.5dp"
-            >
-            <com.android.internal.widget.ImageFloatingTextView android:id="@+id/big_text"
-                android:textAppearance="@style/TextAppearance.Material.Notification"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:singleLine="false"
-                android:visibility="gone"
-                />
-            <ImageView android:id="@+id/profile_badge_large_template"
-                android:layout_width="@dimen/notification_badge_size"
-                android:layout_height="@dimen/notification_badge_size"
-                android:layout_weight="0"
-                android:layout_marginStart="4dp"
-                android:scaleType="fitCenter"
-                android:visibility="gone"
-                android:contentDescription="@string/notification_work_profile_content_description"
-                />
-        </LinearLayout>
+            android:paddingBottom="@dimen/notification_content_margin_bottom"
+            android:textAppearance="@style/TextAppearance.Material.Notification"
+            android:singleLine="false"
+            android:layout_weight="1"
+            android:gravity="top"
+            android:visibility="gone"
+            />
         <ViewStub android:layout="@layout/notification_material_reply_text"
                 android:id="@+id/notification_material_reply_container"
                 android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index 14bf899..86902db 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -41,34 +41,15 @@
             android:layout_width="match_parent"
             android:layout_height="15dp"
             android:layout_marginTop="4dp"/>
-
-        <!-- We can't have another vertical linear layout here with weight != 0 so this forces us to
-             put the badge on the first line. -->
-        <LinearLayout
+        <TextView android:id="@+id/inbox_text0"
+            android:textAppearance="@style/TextAppearance.Material.Notification"
             android:layout_width="match_parent"
-            android:layout_weight="1"
             android:layout_height="0dp"
-            android:orientation="horizontal"
-            >
-            <TextView android:id="@+id/inbox_text0"
-                android:textAppearance="@style/TextAppearance.Material.Notification"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                android:ellipsize="end"
-                android:visibility="gone"
-                android:layout_weight="1"
-                />
-            <ImageView android:id="@+id/profile_badge_large_template"
-                android:layout_width="@dimen/notification_badge_size"
-                android:layout_height="@dimen/notification_badge_size"
-                android:layout_weight="0"
-                android:layout_marginStart="4dp"
-                android:scaleType="fitCenter"
-                android:visibility="gone"
-                android:contentDescription="@string/notification_work_profile_content_description"
-                />
-        </LinearLayout>
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            />
         <TextView android:id="@+id/inbox_text1"
             android:textAppearance="@style/TextAppearance.Material.Notification"
             android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index f0ced5f..9fcd356 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -48,7 +48,7 @@
             >
             <include layout="@layout/notification_template_part_line1"/>
             <include layout="@layout/notification_template_progress"/>
-            <include layout="@layout/notification_template_part_line3"/>
+            <include layout="@layout/notification_template_text"/>
         </LinearLayout>
         <LinearLayout
             android:id="@+id/media_actions"
diff --git a/core/res/res/layout/notification_template_part_line3.xml b/core/res/res/layout/notification_template_part_line3.xml
deleted file mode 100644
index dc47a48..0000000
--- a/core/res/res/layout/notification_template_part_line3.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2014 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/line3"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:gravity="top"
-    >
-    <TextView android:id="@+id/text"
-        android:textAppearance="@style/TextAppearance.Material.Notification"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:layout_gravity="top"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:fadingEdge="horizontal"
-        android:layout_marginTop="1.5dp"
-        />
-    <ImageView android:id="@+id/profile_badge_line3"
-        android:layout_width="@dimen/notification_badge_size"
-        android:layout_height="@dimen/notification_badge_size"
-        android:layout_gravity="center"
-        android:layout_weight="0"
-        android:layout_marginStart="4dp"
-        android:scaleType="fitCenter"
-        android:visibility="gone"
-        android:contentDescription="@string/notification_work_profile_content_description"
-        />
-</LinearLayout>
diff --git a/core/res/res/layout/notification_template_text.xml b/core/res/res/layout/notification_template_text.xml
new file mode 100644
index 0000000..38470cd
--- /dev/null
+++ b/core/res/res/layout/notification_template_text.xml
@@ -0,0 +1,28 @@
+<?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
+  -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/text"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="top"
+    android:layout_marginTop="1.5dp"
+    android:ellipsize="marquee"
+    android:fadingEdge="horizontal"
+    android:gravity="top"
+    android:singleLine="true"
+    android:textAppearance="@style/TextAppearance.Material.Notification"
+    />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index b2482cd..f92e7f0 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -275,7 +275,7 @@
     <dimen name="notification_large_icon_circle_padding">11dp</dimen>
 
     <!-- Size of the profile badge for notifications -->
-    <dimen name="notification_badge_size">16dp</dimen>
+    <dimen name="notification_badge_size">12dp</dimen>
 
     <!-- Keyguard dimensions -->
     <!-- TEMP -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9023dd4..a5d0020 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -86,7 +86,6 @@
   <java-symbol type="id" name="left_icon" />
   <java-symbol type="id" name="leftSpacer" />
   <java-symbol type="id" name="line1" />
-  <java-symbol type="id" name="line3" />
   <java-symbol type="id" name="list_footer" />
   <java-symbol type="id" name="list_item" />
   <java-symbol type="id" name="listContainer" />
@@ -209,8 +208,7 @@
   <java-symbol type="id" name="pin_confirm_text" />
   <java-symbol type="id" name="pin_error_message" />
   <java-symbol type="id" name="timePickerLayout" />
-  <java-symbol type="id" name="profile_badge_large_template" />
-  <java-symbol type="id" name="profile_badge_line3" />
+  <java-symbol type="id" name="profile_badge" />
   <java-symbol type="id" name="transitionPosition" />
   <java-symbol type="id" name="selection_start_handle" />
   <java-symbol type="id" name="selection_end_handle" />
@@ -1267,6 +1265,7 @@
   <java-symbol type="drawable" name="ic_corp_badge" />
   <java-symbol type="drawable" name="ic_corp_badge_off" />
   <java-symbol type="drawable" name="ic_corp_icon_badge" />
+  <java-symbol type="drawable" name="ic_corp_badge_no_background" />
   <java-symbol type="drawable" name="ic_corp_icon" />
   <java-symbol type="drawable" name="ic_corp_statusbar_icon" />
   <java-symbol type="drawable" name="emulator_circular_window_overlay" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 77d2ada..5a08887 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -78,10 +78,10 @@
     <shortcode country="cz" premium="9\\d{6,7}" free="116\\d{3}" />
 
     <!-- Germany: 4-5 digits plus 1232xxx (premium codes from http://www.vodafone.de/infofaxe/537.pdf and http://premiumdienste.eplus.de/pdf/kodex.pdf), plus EU. To keep the premium regex from being too large, it only includes payment processors that have been used by SMS malware, with the regular pattern matching the other premium short codes. -->
-    <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}|81214|81215|47529" />
+    <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}|81214|81215|47529|70296" />
 
     <!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
-    <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}" />
+    <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}|4665" />
 
     <!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU.
          http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht -->
@@ -102,7 +102,7 @@
     <!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU:
          http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf,
          visual voicemail code for EE: 887 -->
-    <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}|887" />
+    <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}|887|83669|34664|40406" />
 
     <!-- Georgia: 4 digits, known premium codes listed -->
     <shortcode country="ge" pattern="\\d{4}" premium="801[234]|888[239]" />
@@ -114,6 +114,9 @@
          http://clients.txtnation.com/entries/209633-hungary-premium-sms-short-code-regulations -->
     <shortcode country="hu" pattern="[01](?:\\d{3}|\\d{9})" premium="0691227910|1784" free="116\\d{3}" />
 
+    <!-- Indonesia -->
+    <shortcode country="id" free="99477|6006|46645" />
+
     <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
          http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
     <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" />
@@ -123,7 +126,7 @@
 
     <!-- Italy: 5 digits (premium=4xxxx), plus EU:
          http://clients.txtnation.com/attachments/token/di5kfblvubttvlw/?name=Italy_CASP_EN.pdf -->
-    <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}" />
+    <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}|4112503" />
 
     <!-- Japan: 8083 used by SOFTBANK_DCB_2 -->
     <shortcode country="jp" free="8083" />
@@ -137,6 +140,9 @@
     <!-- Kazakhstan: 4 digits, known premium codes listed: http://smscoin.net/info/pricing-kazakhstan/ -->
     <shortcode country="kz" pattern="\\d{4}" premium="335[02]|4161|444[469]|77[2359]0|8444|919[3-5]|968[2-5]" />
 
+    <!-- Kuwait -->
+    <shortcode country="kw" free="1378|50420|94006" />
+
     <!-- Lithuania: 3-5 digits, known premium codes listed, plus EU -->
     <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}" />
 
@@ -148,13 +154,13 @@
     <shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}" />
 
     <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
-    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" />
+    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645" />
 
     <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
-    <shortcode country="my" pattern="\\d{5}" premium="32298|33776" />
+    <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099" />
 
     <!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
-    <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}" />
+    <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223" />
 
     <!-- Norway: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" />
@@ -163,10 +169,10 @@
     <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" />
 
     <!-- Philippines -->
-    <shortcode country="ph" free="2147" />
+    <shortcode country="ph" free="2147|5495|5496" />
 
     <!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
-    <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012" />
+    <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
 
     <!-- Portugal: 5 digits, plus EU:
          http://clients.txtnation.com/entries/158326-portugal-premium-sms-short-code-regulations -->
@@ -210,4 +216,7 @@
          visual voicemail code for T-Mobile: 122 -->
     <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327|654)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" free="122|87902" />
 
+    <!-- Vietnam -->
+    <shortcode country="vn" free="5001" />
+
 </shortcodes>
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index e54723e..edbfef9 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -48,6 +48,9 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.KeyEvent;
 
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.is;
+
 /**
  * Tests the TextView widget from an Activity
  */
@@ -84,6 +87,43 @@
     }
 
     @SmallTest
+    public void testPositionCursorAtTextAtIndex_arabic() throws Exception {
+        // Arabic text. The expected cursorable boundary is
+        // | \u0623 \u064F | \u067A | \u0633 \u0652 |
+        final String text = "\u0623\u064F\u067A\u0633\u0652";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(3));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(3));
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(4));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(3), is(5))));
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(5));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(5));
+    }
+
+    @SmallTest
+    public void testPositionCursorAtTextAtIndex_devanagari() throws Exception {
+        // Devanagari text. The expected cursorable boundary is | \u0915 \u093E |
+        final String text = "\u0915\u093E";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
+    }
+
+    @SmallTest
     public void testLongPressToSelect() throws Exception {
         final String helloWorld = "Hello Kirk!";
         onView(withId(R.id.textview)).perform(click());
diff --git a/include/androidfw/DisplayEventDispatcher.h b/include/androidfw/DisplayEventDispatcher.h
new file mode 100644
index 0000000..3ade215
--- /dev/null
+++ b/include/androidfw/DisplayEventDispatcher.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+
+namespace android {
+
+class DisplayEventDispatcher : public LooperCallback {
+public:
+    DisplayEventDispatcher(const sp<Looper>& looper);
+
+    status_t initialize();
+    void dispose();
+    status_t scheduleVsync();
+
+protected:
+    virtual ~DisplayEventDispatcher() = default;
+
+private:
+    sp<Looper> mLooper;
+    DisplayEventReceiver mReceiver;
+    bool mWaitingForVsync;
+
+    virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) = 0;
+    virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected) = 0;
+
+    virtual int handleEvent(int receiveFd, int events, void* data);
+    bool processPendingEvents(nsecs_t* outTimestamp, int32_t* id, uint32_t* outCount);
+};
+}
diff --git a/include/androidfw/LocaleData.h b/include/androidfw/LocaleData.h
new file mode 100644
index 0000000..add0ab5
--- /dev/null
+++ b/include/androidfw/LocaleData.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef _LIBS_UTILS_LOCALE_DATA_H
+#define _LIBS_UTILS_LOCALE_DATA_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace android {
+
+int localeDataCompareRegions(
+        const char* left_region, const char* right_region,
+        const char* requested_language, const char* requested_script,
+        const char* requested_region);
+
+void localeDataComputeScript(char out[4], const char* language, const char* region);
+
+} // namespace android
+
+#endif // _LIBS_UTILS_LOCALE_DATA_H
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 428a2b8..88fecce 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -21,6 +21,7 @@
 #define _LIBS_UTILS_RESOURCE_TYPES_H
 
 #include <androidfw/Asset.h>
+#include <androidfw/LocaleData.h>
 #include <utils/ByteOrder.h>
 #include <utils/Errors.h>
 #include <utils/String16.h>
@@ -1127,8 +1128,9 @@
     // configuration. (eg. Hant, Latn, etc.). Interpreted in conjunction with
     // the locale field.
     char localeScript[4];
+    bool localeScriptWasProvided;
 
-    // A single BCP-47 variant subtag. Will vary in length between 5 and 8
+    // A single BCP-47 variant subtag. Will vary in length between 4 and 8
     // chars. Interpreted in conjunction with the locale field.
     char localeVariant[8];
 
@@ -1228,10 +1230,15 @@
 
     inline void clearLocale() {
         locale = 0;
+        localeScriptWasProvided = false;
         memset(localeScript, 0, sizeof(localeScript));
         memset(localeVariant, 0, sizeof(localeVariant));
     }
 
+    inline void computeScript() {
+        localeDataComputeScript(localeScript, language, country);
+    }
+
     // Get the 2 or 3 letter language code of this configuration. Trailing
     // bytes are set to '\0'.
     size_t unpackLanguage(char language[4]) const;
@@ -1255,6 +1262,12 @@
     // and 0 if they're equally specific.
     int isLocaleMoreSpecificThan(const ResTable_config &o) const;
 
+    // Return true if 'this' is a better locale match than 'o' for the
+    // 'requested' configuration. Similar to isBetterThan(), this assumes that
+    // match() has already been used to remove any configurations that don't
+    // match the requested configuration at all.
+    bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
+
     String8 toString() const;
 };
 
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
index f682fb8..6bbfcd2 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -21,6 +21,7 @@
     Asset.cpp \
     AssetDir.cpp \
     AssetManager.cpp \
+    LocaleData.cpp \
     misc.cpp \
     ObbFile.cpp \
     ResourceTypes.cpp \
@@ -33,7 +34,8 @@
     $(commonSources) \
     BackupData.cpp \
     BackupHelpers.cpp \
-    CursorWindow.cpp
+    CursorWindow.cpp \
+    DisplayEventDispatcher.cpp
 
 hostSources := $(commonSources)
 
@@ -65,6 +67,7 @@
     libbinder \
     liblog \
     libcutils \
+    libgui \
     libutils \
     libz
 
diff --git a/libs/androidfw/DisplayEventDispatcher.cpp b/libs/androidfw/DisplayEventDispatcher.cpp
new file mode 100644
index 0000000..b8ef9ea4
--- /dev/null
+++ b/libs/androidfw/DisplayEventDispatcher.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "DisplayEventDispatcher"
+
+#include <cinttypes>
+#include <cstdint>
+
+#include <androidfw/DisplayEventDispatcher.h>
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+
+#include <utils/Timers.h>
+
+namespace android {
+
+// Number of events to read at a time from the DisplayEventDispatcher pipe.
+// The value should be large enough that we can quickly drain the pipe
+// using just a few large reads.
+static const size_t EVENT_BUFFER_SIZE = 100;
+
+DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper) :
+        mLooper(looper), mWaitingForVsync(false) {
+    ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);
+}
+
+status_t DisplayEventDispatcher::initialize() {
+    status_t result = mReceiver.initCheck();
+    if (result) {
+        ALOGW("Failed to initialize display event receiver, status=%d", result);
+        return result;
+    }
+
+    int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT,
+            this, NULL);
+    if (rc < 0) {
+        return UNKNOWN_ERROR;
+    }
+    return OK;
+}
+
+void DisplayEventDispatcher::dispose() {
+    ALOGV("dispatcher %p ~ Disposing display event dispatcher.", this);
+
+    if (!mReceiver.initCheck()) {
+        mLooper->removeFd(mReceiver.getFd());
+    }
+}
+
+status_t DisplayEventDispatcher::scheduleVsync() {
+    if (!mWaitingForVsync) {
+        ALOGV("dispatcher %p ~ Scheduling vsync.", this);
+
+        // Drain all pending events.
+        nsecs_t vsyncTimestamp;
+        int32_t vsyncDisplayId;
+        uint32_t vsyncCount;
+        if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
+            ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "",
+                    this, ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));
+        }
+
+        status_t status = mReceiver.requestNextVsync();
+        if (status) {
+            ALOGW("Failed to request next vsync, status=%d", status);
+            return status;
+        }
+
+        mWaitingForVsync = true;
+    }
+    return OK;
+}
+
+int DisplayEventDispatcher::handleEvent(int, int events, void*) {
+    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+        ALOGE("Display event receiver pipe was closed or an error occurred.  "
+                "events=0x%x", events);
+        return 0; // remove the callback
+    }
+
+    if (!(events & Looper::EVENT_INPUT)) {
+        ALOGW("Received spurious callback for unhandled poll event.  "
+                "events=0x%x", events);
+        return 1; // keep the callback
+    }
+
+    // Drain all pending events, keep the last vsync.
+    nsecs_t vsyncTimestamp;
+    int32_t vsyncDisplayId;
+    uint32_t vsyncCount;
+    if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
+        ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", id=%d, count=%d",
+                this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount);
+        mWaitingForVsync = false;
+        dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
+    }
+
+    return 1; // keep the callback
+}
+
+bool DisplayEventDispatcher::processPendingEvents(
+        nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) {
+    bool gotVsync = false;
+    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+    ssize_t n;
+    while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+        ALOGV("dispatcher %p ~ Read %d events.", this, int(n));
+        for (ssize_t i = 0; i < n; i++) {
+            const DisplayEventReceiver::Event& ev = buf[i];
+            switch (ev.header.type) {
+            case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+                // Later vsync events will just overwrite the info from earlier
+                // ones. That's fine, we only care about the most recent.
+                gotVsync = true;
+                *outTimestamp = ev.header.timestamp;
+                *outId = ev.header.id;
+                *outCount = ev.vsync.count;
+                break;
+            case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
+                dispatchHotplug(ev.header.timestamp, ev.header.id, ev.hotplug.connected);
+                break;
+            default:
+                ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
+                break;
+            }
+        }
+    }
+    if (n < 0) {
+        ALOGW("Failed to get events from display event dispatcher, status=%d", status_t(n));
+    }
+    return gotVsync;
+}
+}
diff --git a/libs/androidfw/LocaleData.cpp b/libs/androidfw/LocaleData.cpp
new file mode 100644
index 0000000..c0c3ab8
--- /dev/null
+++ b/libs/androidfw/LocaleData.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ */
+
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <androidfw/LocaleData.h>
+
+namespace android {
+
+#include "LocaleDataTables.cpp"
+
+inline uint32_t packLocale(const char* language, const char* region) {
+    return (((uint8_t) language[0]) << 24u) | (((uint8_t) language[1]) << 16u) |
+           (((uint8_t) region[0]) << 8u) | ((uint8_t) region[1]);
+}
+
+inline uint32_t dropRegion(uint32_t packed_locale) {
+    return packed_locale & 0xFFFF0000lu;
+}
+
+inline bool hasRegion(uint32_t packed_locale) {
+    return (packed_locale & 0x0000FFFFlu) != 0;
+}
+
+const size_t SCRIPT_LENGTH = 4;
+const size_t SCRIPT_PARENTS_COUNT = sizeof(SCRIPT_PARENTS)/sizeof(SCRIPT_PARENTS[0]);
+const uint32_t PACKED_ROOT = 0; // to represent the root locale
+
+uint32_t findParent(uint32_t packed_locale, const char* script) {
+    if (hasRegion(packed_locale)) {
+        for (size_t i = 0; i < SCRIPT_PARENTS_COUNT; i++) {
+            if (memcmp(script, SCRIPT_PARENTS[i].script, SCRIPT_LENGTH) == 0) {
+                auto map = SCRIPT_PARENTS[i].map;
+                auto lookup_result = map->find(packed_locale);
+                if (lookup_result != map->end()) {
+                    return lookup_result->second;
+                }
+                break;
+            }
+        }
+        return dropRegion(packed_locale);
+    }
+    return PACKED_ROOT;
+}
+
+// Find the ancestors of a locale, and fill 'out' with it (assumes out has enough
+// space). If any of the members of stop_list was seen, write it in the
+// output but stop afterwards.
+//
+// This also outputs the index of the last written ancestor in the stop_list
+// to stop_list_index, which will be -1 if it is not found in the stop_list.
+//
+// Returns the number of ancestors written in the output, which is always
+// at least one.
+size_t findAncestors(uint32_t* out, ssize_t* stop_list_index,
+                     uint32_t packed_locale, const char* script,
+                     const uint32_t* stop_list, size_t stop_set_length) {
+    uint32_t ancestor = packed_locale;
+    size_t count = 0;
+    do {
+        out[count++] = ancestor;
+        for (size_t i = 0; i < stop_set_length; i++) {
+            if (stop_list[i] == ancestor) {
+                *stop_list_index = (ssize_t) i;
+                return count;
+            }
+        }
+        ancestor = findParent(ancestor, script);
+    } while (ancestor != PACKED_ROOT);
+    *stop_list_index = (ssize_t) -1;
+    return count;
+}
+
+size_t findDistance(uint32_t supported,
+                    const char* script,
+                    const uint32_t* request_ancestors,
+                    size_t request_ancestors_count) {
+    uint32_t supported_ancestors[MAX_PARENT_DEPTH+1];
+    ssize_t request_ancestors_index;
+    const size_t supported_ancestor_count = findAncestors(
+            supported_ancestors, &request_ancestors_index,
+            supported, script,
+            request_ancestors, request_ancestors_count);
+    // Since both locales share the same root, there will always be a shared
+    // ancestor, so the distance in the parent tree is the sum of the distance
+    // of 'supported' to the lowest common ancestor (number of ancestors
+    // written for 'supported' minus 1) plus the distance of 'request' to the
+    // lowest common ancestor (the index of the ancestor in request_ancestors).
+    return supported_ancestor_count + request_ancestors_index - 1;
+}
+
+inline bool isRepresentative(uint32_t language_and_region, const char* script) {
+    const uint64_t packed_locale = (
+            (((uint64_t) language_and_region) << 32u) |
+            (((uint64_t) script[0]) << 24u) |
+            (((uint64_t) script[1]) << 16u) |
+            (((uint64_t) script[2]) <<  8u) |
+            ((uint64_t) script[3]));
+
+    return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0);
+}
+
+int localeDataCompareRegions(
+        const char* left_region, const char* right_region,
+        const char* requested_language, const char* requested_script,
+        const char* requested_region) {
+
+    if (left_region[0] == right_region[0] && left_region[1] == right_region[1]) {
+        return 0;
+    }
+    const uint32_t left = packLocale(requested_language, left_region);
+    const uint32_t right = packLocale(requested_language, right_region);
+    const uint32_t request = packLocale(requested_language, requested_region);
+
+    uint32_t request_ancestors[MAX_PARENT_DEPTH+1];
+    ssize_t left_right_index;
+    // Find the parents of the request, but stop as soon as we saw left or right
+    const uint32_t left_and_right[] = {left, right};
+    const size_t ancestor_count = findAncestors(
+            request_ancestors, &left_right_index,
+            request, requested_script,
+            left_and_right, sizeof(left_and_right)/sizeof(left_and_right[0]));
+    if (left_right_index == 0) { // We saw left earlier
+        return 1;
+    }
+    if (left_right_index == 1) { // We saw right earlier
+        return -1;
+    }
+
+    // If we are here, neither left nor right are an ancestor of the
+    // request. This means that all the ancestors have been computed and
+    // the last ancestor is just the language by itself. We will use the
+    // distance in the parent tree for determining the better match.
+    const size_t left_distance = findDistance(
+            left, requested_script, request_ancestors, ancestor_count);
+    const size_t right_distance = findDistance(
+            right, requested_script, request_ancestors, ancestor_count);
+    if (left_distance != right_distance) {
+        return (int) right_distance - (int) left_distance; // smaller distance is better
+    }
+
+    // If we are here, left and right are equidistant from the request. We will
+    // try and see if any of them is a representative locale.
+    const bool left_is_representative = isRepresentative(left, requested_script);
+    const bool right_is_representative = isRepresentative(right, requested_script);
+    if (left_is_representative != right_is_representative) {
+        return (int) left_is_representative - (int) right_is_representative;
+    }
+
+    // We have no way of figuring out which locale is a better match. For
+    // the sake of stability, we consider the locale with the lower region
+    // code (in dictionary order) better, with two-letter codes before
+    // three-digit codes (since two-letter codes are more specific).
+    return (int64_t) right - (int64_t) left;
+}
+
+void localeDataComputeScript(char out[4], const char* language, const char* region) {
+    if (language[0] == '\0') {
+        memset(out, '\0', SCRIPT_LENGTH);
+        return;
+    }
+    uint32_t lookup_key = packLocale(language, region);
+    auto lookup_result = LIKELY_SCRIPTS.find(lookup_key);
+    if (lookup_result == LIKELY_SCRIPTS.end()) {
+        // We couldn't find the locale. Let's try without the region
+        if (region[0] != '\0') {
+            lookup_key = dropRegion(lookup_key);
+            lookup_result = LIKELY_SCRIPTS.find(lookup_key);
+            if (lookup_result != LIKELY_SCRIPTS.end()) {
+                memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH);
+                return;
+            }
+        }
+        // We don't know anything about the locale
+        memset(out, '\0', SCRIPT_LENGTH);
+        return;
+    } else {
+        // We found the locale.
+        memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH);
+    }
+}
+
+} // namespace android
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
new file mode 100644
index 0000000..1ac5085
--- /dev/null
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -0,0 +1,1701 @@
+// Auto-generated by frameworks/base/tools/localedata/extract_icu_data.py
+
+const char SCRIPT_CODES[][4] = {
+    /* 0  */ {'A', 'h', 'o', 'm'},
+    /* 1  */ {'A', 'r', 'a', 'b'},
+    /* 2  */ {'A', 'r', 'm', 'i'},
+    /* 3  */ {'A', 'r', 'm', 'n'},
+    /* 4  */ {'A', 'v', 's', 't'},
+    /* 5  */ {'B', 'a', 'm', 'u'},
+    /* 6  */ {'B', 'a', 's', 's'},
+    /* 7  */ {'B', 'e', 'n', 'g'},
+    /* 8  */ {'B', 'r', 'a', 'h'},
+    /* 9  */ {'C', 'a', 'n', 's'},
+    /* 10 */ {'C', 'a', 'r', 'i'},
+    /* 11 */ {'C', 'h', 'a', 'm'},
+    /* 12 */ {'C', 'h', 'e', 'r'},
+    /* 13 */ {'C', 'o', 'p', 't'},
+    /* 14 */ {'C', 'p', 'r', 't'},
+    /* 15 */ {'C', 'y', 'r', 'l'},
+    /* 16 */ {'D', 'e', 'v', 'a'},
+    /* 17 */ {'E', 'g', 'y', 'p'},
+    /* 18 */ {'E', 't', 'h', 'i'},
+    /* 19 */ {'G', 'e', 'o', 'r'},
+    /* 20 */ {'G', 'o', 't', 'h'},
+    /* 21 */ {'G', 'r', 'e', 'k'},
+    /* 22 */ {'G', 'u', 'j', 'r'},
+    /* 23 */ {'G', 'u', 'r', 'u'},
+    /* 24 */ {'H', 'a', 'n', 's'},
+    /* 25 */ {'H', 'a', 'n', 't'},
+    /* 26 */ {'H', 'a', 't', 'r'},
+    /* 27 */ {'H', 'e', 'b', 'r'},
+    /* 28 */ {'H', 'l', 'u', 'w'},
+    /* 29 */ {'H', 'm', 'n', 'g'},
+    /* 30 */ {'I', 't', 'a', 'l'},
+    /* 31 */ {'J', 'p', 'a', 'n'},
+    /* 32 */ {'K', 'a', 'l', 'i'},
+    /* 33 */ {'K', 'a', 'n', 'a'},
+    /* 34 */ {'K', 'h', 'a', 'r'},
+    /* 35 */ {'K', 'h', 'm', 'r'},
+    /* 36 */ {'K', 'n', 'd', 'a'},
+    /* 37 */ {'K', 'o', 'r', 'e'},
+    /* 38 */ {'K', 't', 'h', 'i'},
+    /* 39 */ {'L', 'a', 'n', 'a'},
+    /* 40 */ {'L', 'a', 'o', 'o'},
+    /* 41 */ {'L', 'a', 't', 'n'},
+    /* 42 */ {'L', 'e', 'p', 'c'},
+    /* 43 */ {'L', 'i', 'n', 'a'},
+    /* 44 */ {'L', 'i', 's', 'u'},
+    /* 45 */ {'L', 'y', 'c', 'i'},
+    /* 46 */ {'L', 'y', 'd', 'i'},
+    /* 47 */ {'M', 'a', 'n', 'd'},
+    /* 48 */ {'M', 'a', 'n', 'i'},
+    /* 49 */ {'M', 'e', 'r', 'c'},
+    /* 50 */ {'M', 'l', 'y', 'm'},
+    /* 51 */ {'M', 'o', 'n', 'g'},
+    /* 52 */ {'M', 'r', 'o', 'o'},
+    /* 53 */ {'M', 'y', 'm', 'r'},
+    /* 54 */ {'N', 'a', 'r', 'b'},
+    /* 55 */ {'N', 'k', 'o', 'o'},
+    /* 56 */ {'O', 'g', 'a', 'm'},
+    /* 57 */ {'O', 'r', 'k', 'h'},
+    /* 58 */ {'O', 'r', 'y', 'a'},
+    /* 59 */ {'P', 'a', 'u', 'c'},
+    /* 60 */ {'P', 'h', 'l', 'i'},
+    /* 61 */ {'P', 'h', 'n', 'x'},
+    /* 62 */ {'P', 'l', 'r', 'd'},
+    /* 63 */ {'P', 'r', 't', 'i'},
+    /* 64 */ {'R', 'u', 'n', 'r'},
+    /* 65 */ {'S', 'a', 'm', 'r'},
+    /* 66 */ {'S', 'a', 'r', 'b'},
+    /* 67 */ {'S', 'a', 'u', 'r'},
+    /* 68 */ {'S', 'g', 'n', 'w'},
+    /* 69 */ {'S', 'i', 'n', 'h'},
+    /* 70 */ {'S', 'o', 'r', 'a'},
+    /* 71 */ {'S', 'y', 'r', 'c'},
+    /* 72 */ {'T', 'a', 'l', 'e'},
+    /* 73 */ {'T', 'a', 'l', 'u'},
+    /* 74 */ {'T', 'a', 'm', 'l'},
+    /* 75 */ {'T', 'a', 'v', 't'},
+    /* 76 */ {'T', 'e', 'l', 'u'},
+    /* 77 */ {'T', 'f', 'n', 'g'},
+    /* 78 */ {'T', 'h', 'a', 'a'},
+    /* 79 */ {'T', 'h', 'a', 'i'},
+    /* 80 */ {'T', 'i', 'b', 't'},
+    /* 81 */ {'U', 'g', 'a', 'r'},
+    /* 82 */ {'V', 'a', 'i', 'i'},
+    /* 83 */ {'X', 'p', 'e', 'o'},
+    /* 84 */ {'X', 's', 'u', 'x'},
+    /* 85 */ {'Y', 'i', 'i', 'i'},
+    /* 86 */ {'~', '~', '~', 'A'},
+    /* 87 */ {'~', '~', '~', 'B'},
+};
+
+
+const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
+    {0x61610000u, 41u}, // aa -> Latn
+    {0x61620000u, 15u}, // ab -> Cyrl
+    {0xC4200000u, 41u}, // abr -> Latn
+    {0x90400000u, 41u}, // ace -> Latn
+    {0x9C400000u, 41u}, // ach -> Latn
+    {0x80600000u, 41u}, // ada -> Latn
+    {0xE0600000u, 15u}, // ady -> Cyrl
+    {0x61650000u,  4u}, // ae -> Avst
+    {0x84800000u,  1u}, // aeb -> Arab
+    {0x61660000u, 41u}, // af -> Latn
+    {0xC0C00000u, 41u}, // agq -> Latn
+    {0xB8E00000u,  0u}, // aho -> Ahom
+    {0x616B0000u, 41u}, // ak -> Latn
+    {0xA9400000u, 84u}, // akk -> Xsux
+    {0xB5600000u, 41u}, // aln -> Latn
+    {0xCD600000u, 15u}, // alt -> Cyrl
+    {0x616D0000u, 18u}, // am -> Ethi
+    {0xB9800000u, 41u}, // amo -> Latn
+    {0xE5C00000u, 41u}, // aoz -> Latn
+    {0x61720000u,  1u}, // ar -> Arab
+    {0x61725842u, 87u}, // ar-XB -> ~~~B
+    {0x8A200000u,  2u}, // arc -> Armi
+    {0xB6200000u, 41u}, // arn -> Latn
+    {0xBA200000u, 41u}, // aro -> Latn
+    {0xC2200000u,  1u}, // arq -> Arab
+    {0xE2200000u,  1u}, // ary -> Arab
+    {0xE6200000u,  1u}, // arz -> Arab
+    {0x61730000u,  7u}, // as -> Beng
+    {0x82400000u, 41u}, // asa -> Latn
+    {0x92400000u, 68u}, // ase -> Sgnw
+    {0xCE400000u, 41u}, // ast -> Latn
+    {0xA6600000u, 41u}, // atj -> Latn
+    {0x61760000u, 15u}, // av -> Cyrl
+    {0x82C00000u, 16u}, // awa -> Deva
+    {0x61790000u, 41u}, // ay -> Latn
+    {0x617A0000u, 41u}, // az -> Latn
+    {0x617A4951u,  1u}, // az-IQ -> Arab
+    {0x617A4952u,  1u}, // az-IR -> Arab
+    {0x617A5255u, 15u}, // az-RU -> Cyrl
+    {0x62610000u, 15u}, // ba -> Cyrl
+    {0xAC010000u,  1u}, // bal -> Arab
+    {0xB4010000u, 41u}, // ban -> Latn
+    {0xBC010000u, 16u}, // bap -> Deva
+    {0xC4010000u, 41u}, // bar -> Latn
+    {0xC8010000u, 41u}, // bas -> Latn
+    {0xDC010000u,  5u}, // bax -> Bamu
+    {0x88210000u, 41u}, // bbc -> Latn
+    {0xA4210000u, 41u}, // bbj -> Latn
+    {0xA0410000u, 41u}, // bci -> Latn
+    {0x62650000u, 15u}, // be -> Cyrl
+    {0xA4810000u,  1u}, // bej -> Arab
+    {0xB0810000u, 41u}, // bem -> Latn
+    {0xD8810000u, 41u}, // bew -> Latn
+    {0xE4810000u, 41u}, // bez -> Latn
+    {0x8CA10000u, 41u}, // bfd -> Latn
+    {0xC0A10000u, 74u}, // bfq -> Taml
+    {0xCCA10000u,  1u}, // bft -> Arab
+    {0xE0A10000u, 16u}, // bfy -> Deva
+    {0x62670000u, 15u}, // bg -> Cyrl
+    {0x88C10000u, 16u}, // bgc -> Deva
+    {0xB4C10000u,  1u}, // bgn -> Arab
+    {0xDCC10000u, 21u}, // bgx -> Grek
+    {0x62680000u, 38u}, // bh -> Kthi
+    {0x84E10000u, 16u}, // bhb -> Deva
+    {0xA0E10000u, 16u}, // bhi -> Deva
+    {0xA8E10000u, 41u}, // bhk -> Latn
+    {0xB8E10000u, 16u}, // bho -> Deva
+    {0x62690000u, 41u}, // bi -> Latn
+    {0xA9010000u, 41u}, // bik -> Latn
+    {0xB5010000u, 41u}, // bin -> Latn
+    {0xA5210000u, 16u}, // bjj -> Deva
+    {0xB5210000u, 41u}, // bjn -> Latn
+    {0xB1410000u, 41u}, // bkm -> Latn
+    {0xD1410000u, 41u}, // bku -> Latn
+    {0xCD610000u, 75u}, // blt -> Tavt
+    {0x626D0000u, 41u}, // bm -> Latn
+    {0xC1810000u, 41u}, // bmq -> Latn
+    {0x626E0000u,  7u}, // bn -> Beng
+    {0x626F0000u, 80u}, // bo -> Tibt
+    {0xE1E10000u,  7u}, // bpy -> Beng
+    {0xA2010000u,  1u}, // bqi -> Arab
+    {0xD6010000u, 41u}, // bqv -> Latn
+    {0x62720000u, 41u}, // br -> Latn
+    {0x82210000u, 16u}, // bra -> Deva
+    {0x9E210000u,  1u}, // brh -> Arab
+    {0xDE210000u, 16u}, // brx -> Deva
+    {0x62730000u, 41u}, // bs -> Latn
+    {0xC2410000u,  6u}, // bsq -> Bass
+    {0xCA410000u, 41u}, // bss -> Latn
+    {0xBA610000u, 41u}, // bto -> Latn
+    {0xD6610000u, 16u}, // btv -> Deva
+    {0x82810000u, 15u}, // bua -> Cyrl
+    {0x8A810000u, 41u}, // buc -> Latn
+    {0x9A810000u, 41u}, // bug -> Latn
+    {0xB2810000u, 41u}, // bum -> Latn
+    {0x86A10000u, 41u}, // bvb -> Latn
+    {0xB7010000u, 18u}, // byn -> Ethi
+    {0xD7010000u, 41u}, // byv -> Latn
+    {0x93210000u, 41u}, // bze -> Latn
+    {0x63610000u, 41u}, // ca -> Latn
+    {0x9C420000u, 41u}, // cch -> Latn
+    {0xBC420000u,  7u}, // ccp -> Beng
+    {0x63650000u, 15u}, // ce -> Cyrl
+    {0x84820000u, 41u}, // ceb -> Latn
+    {0x98C20000u, 41u}, // cgg -> Latn
+    {0x63680000u, 41u}, // ch -> Latn
+    {0xA8E20000u, 41u}, // chk -> Latn
+    {0xB0E20000u, 15u}, // chm -> Cyrl
+    {0xB8E20000u, 41u}, // cho -> Latn
+    {0xBCE20000u, 41u}, // chp -> Latn
+    {0xC4E20000u, 12u}, // chr -> Cher
+    {0x81220000u,  1u}, // cja -> Arab
+    {0xB1220000u, 11u}, // cjm -> Cham
+    {0x85420000u,  1u}, // ckb -> Arab
+    {0x636F0000u, 41u}, // co -> Latn
+    {0xBDC20000u, 13u}, // cop -> Copt
+    {0xC9E20000u, 41u}, // cps -> Latn
+    {0x63720000u,  9u}, // cr -> Cans
+    {0xA6220000u,  9u}, // crj -> Cans
+    {0xAA220000u,  9u}, // crk -> Cans
+    {0xAE220000u,  9u}, // crl -> Cans
+    {0xB2220000u,  9u}, // crm -> Cans
+    {0xCA220000u, 41u}, // crs -> Latn
+    {0x63730000u, 41u}, // cs -> Latn
+    {0x86420000u, 41u}, // csb -> Latn
+    {0xDA420000u,  9u}, // csw -> Cans
+    {0x8E620000u, 59u}, // ctd -> Pauc
+    {0x63750000u, 15u}, // cu -> Cyrl
+    {0x63760000u, 15u}, // cv -> Cyrl
+    {0x63790000u, 41u}, // cy -> Latn
+    {0x64610000u, 41u}, // da -> Latn
+    {0xA8030000u, 41u}, // dak -> Latn
+    {0xC4030000u, 15u}, // dar -> Cyrl
+    {0xD4030000u, 41u}, // dav -> Latn
+    {0x88430000u,  1u}, // dcc -> Arab
+    {0x64650000u, 41u}, // de -> Latn
+    {0xB4830000u, 41u}, // den -> Latn
+    {0xC4C30000u, 41u}, // dgr -> Latn
+    {0x91230000u, 41u}, // dje -> Latn
+    {0xA5A30000u, 41u}, // dnj -> Latn
+    {0xA1C30000u,  1u}, // doi -> Arab
+    {0x86430000u, 41u}, // dsb -> Latn
+    {0xB2630000u, 41u}, // dtm -> Latn
+    {0xBE630000u, 41u}, // dtp -> Latn
+    {0x82830000u, 41u}, // dua -> Latn
+    {0x64760000u, 78u}, // dv -> Thaa
+    {0xBB030000u, 41u}, // dyo -> Latn
+    {0xD3030000u, 41u}, // dyu -> Latn
+    {0x647A0000u, 80u}, // dz -> Tibt
+    {0xD0240000u, 41u}, // ebu -> Latn
+    {0x65650000u, 41u}, // ee -> Latn
+    {0xA0A40000u, 41u}, // efi -> Latn
+    {0xACC40000u, 41u}, // egl -> Latn
+    {0xE0C40000u, 17u}, // egy -> Egyp
+    {0xE1440000u, 32u}, // eky -> Kali
+    {0x656C0000u, 21u}, // el -> Grek
+    {0x656E0000u, 41u}, // en -> Latn
+    {0x656E5841u, 86u}, // en-XA -> ~~~A
+    {0x656F0000u, 41u}, // eo -> Latn
+    {0x65730000u, 41u}, // es -> Latn
+    {0xD2440000u, 41u}, // esu -> Latn
+    {0x65740000u, 41u}, // et -> Latn
+    {0xCE640000u, 30u}, // ett -> Ital
+    {0x65750000u, 41u}, // eu -> Latn
+    {0xBAC40000u, 41u}, // ewo -> Latn
+    {0xCEE40000u, 41u}, // ext -> Latn
+    {0x66610000u,  1u}, // fa -> Arab
+    {0xB4050000u, 41u}, // fan -> Latn
+    {0x66660000u, 41u}, // ff -> Latn
+    {0xB0A50000u, 41u}, // ffm -> Latn
+    {0x66690000u, 41u}, // fi -> Latn
+    {0x81050000u,  1u}, // fia -> Arab
+    {0xAD050000u, 41u}, // fil -> Latn
+    {0xCD050000u, 41u}, // fit -> Latn
+    {0x666A0000u, 41u}, // fj -> Latn
+    {0x666F0000u, 41u}, // fo -> Latn
+    {0xB5C50000u, 41u}, // fon -> Latn
+    {0x66720000u, 41u}, // fr -> Latn
+    {0x8A250000u, 41u}, // frc -> Latn
+    {0xBE250000u, 41u}, // frp -> Latn
+    {0xC6250000u, 41u}, // frr -> Latn
+    {0xCA250000u, 41u}, // frs -> Latn
+    {0x8E850000u, 41u}, // fud -> Latn
+    {0xC2850000u, 41u}, // fuq -> Latn
+    {0xC6850000u, 41u}, // fur -> Latn
+    {0xD6850000u, 41u}, // fuv -> Latn
+    {0xC6A50000u, 41u}, // fvr -> Latn
+    {0x66790000u, 41u}, // fy -> Latn
+    {0x67610000u, 41u}, // ga -> Latn
+    {0x80060000u, 41u}, // gaa -> Latn
+    {0x98060000u, 41u}, // gag -> Latn
+    {0xB4060000u, 24u}, // gan -> Hans
+    {0xE0060000u, 41u}, // gay -> Latn
+    {0xB0260000u, 16u}, // gbm -> Deva
+    {0xE4260000u,  1u}, // gbz -> Arab
+    {0xC4460000u, 41u}, // gcr -> Latn
+    {0x67640000u, 41u}, // gd -> Latn
+    {0xE4860000u, 18u}, // gez -> Ethi
+    {0xB4C60000u, 16u}, // ggn -> Deva
+    {0xAD060000u, 41u}, // gil -> Latn
+    {0xA9260000u,  1u}, // gjk -> Arab
+    {0xD1260000u,  1u}, // gju -> Arab
+    {0x676C0000u, 41u}, // gl -> Latn
+    {0xA9660000u,  1u}, // glk -> Arab
+    {0x676E0000u, 41u}, // gn -> Latn
+    {0xB1C60000u, 16u}, // gom -> Deva
+    {0xB5C60000u, 76u}, // gon -> Telu
+    {0xC5C60000u, 41u}, // gor -> Latn
+    {0xC9C60000u, 41u}, // gos -> Latn
+    {0xCDC60000u, 20u}, // got -> Goth
+    {0x8A260000u, 14u}, // grc -> Cprt
+    {0xCE260000u,  7u}, // grt -> Beng
+    {0xDA460000u, 41u}, // gsw -> Latn
+    {0x67750000u, 22u}, // gu -> Gujr
+    {0x86860000u, 41u}, // gub -> Latn
+    {0x8A860000u, 41u}, // guc -> Latn
+    {0xC6860000u, 41u}, // gur -> Latn
+    {0xE6860000u, 41u}, // guz -> Latn
+    {0x67760000u, 41u}, // gv -> Latn
+    {0xC6A60000u, 16u}, // gvr -> Deva
+    {0xA2C60000u, 41u}, // gwi -> Latn
+    {0x68610000u, 41u}, // ha -> Latn
+    {0x6861434Du,  1u}, // ha-CM -> Arab
+    {0x68615344u,  1u}, // ha-SD -> Arab
+    {0xA8070000u, 24u}, // hak -> Hans
+    {0xD8070000u, 41u}, // haw -> Latn
+    {0xE4070000u,  1u}, // haz -> Arab
+    {0x68650000u, 27u}, // he -> Hebr
+    {0x68690000u, 16u}, // hi -> Deva
+    {0x95070000u, 41u}, // hif -> Latn
+    {0xAD070000u, 41u}, // hil -> Latn
+    {0xD1670000u, 28u}, // hlu -> Hluw
+    {0x8D870000u, 62u}, // hmd -> Plrd
+    {0x8DA70000u,  1u}, // hnd -> Arab
+    {0x91A70000u, 16u}, // hne -> Deva
+    {0xA5A70000u, 29u}, // hnj -> Hmng
+    {0xB5A70000u, 41u}, // hnn -> Latn
+    {0xB9A70000u,  1u}, // hno -> Arab
+    {0x686F0000u, 41u}, // ho -> Latn
+    {0x89C70000u, 16u}, // hoc -> Deva
+    {0xA5C70000u, 16u}, // hoj -> Deva
+    {0x68720000u, 41u}, // hr -> Latn
+    {0x86470000u, 41u}, // hsb -> Latn
+    {0xB6470000u, 24u}, // hsn -> Hans
+    {0x68740000u, 41u}, // ht -> Latn
+    {0x68750000u, 41u}, // hu -> Latn
+    {0x68790000u,  3u}, // hy -> Armn
+    {0x687A0000u, 41u}, // hz -> Latn
+    {0x69610000u, 41u}, // ia -> Latn
+    {0x80280000u, 41u}, // iba -> Latn
+    {0x84280000u, 41u}, // ibb -> Latn
+    {0x69640000u, 41u}, // id -> Latn
+    {0x69670000u, 41u}, // ig -> Latn
+    {0x69690000u, 85u}, // ii -> Yiii
+    {0x696B0000u, 41u}, // ik -> Latn
+    {0xCD480000u, 41u}, // ikt -> Latn
+    {0xB9680000u, 41u}, // ilo -> Latn
+    {0x696E0000u, 41u}, // in -> Latn
+    {0x9DA80000u, 15u}, // inh -> Cyrl
+    {0x69730000u, 41u}, // is -> Latn
+    {0x69740000u, 41u}, // it -> Latn
+    {0x69750000u,  9u}, // iu -> Cans
+    {0x69770000u, 27u}, // iw -> Hebr
+    {0x9F280000u, 41u}, // izh -> Latn
+    {0x6A610000u, 31u}, // ja -> Jpan
+    {0xB0090000u, 41u}, // jam -> Latn
+    {0xB8C90000u, 41u}, // jgo -> Latn
+    {0x6A690000u, 27u}, // ji -> Hebr
+    {0x89890000u, 41u}, // jmc -> Latn
+    {0xAD890000u, 16u}, // jml -> Deva
+    {0xCE890000u, 41u}, // jut -> Latn
+    {0x6A760000u, 41u}, // jv -> Latn
+    {0x6A770000u, 41u}, // jw -> Latn
+    {0x6B610000u, 19u}, // ka -> Geor
+    {0x800A0000u, 15u}, // kaa -> Cyrl
+    {0x840A0000u, 41u}, // kab -> Latn
+    {0x880A0000u, 41u}, // kac -> Latn
+    {0xA40A0000u, 41u}, // kaj -> Latn
+    {0xB00A0000u, 41u}, // kam -> Latn
+    {0xB80A0000u, 41u}, // kao -> Latn
+    {0x8C2A0000u, 15u}, // kbd -> Cyrl
+    {0x984A0000u, 41u}, // kcg -> Latn
+    {0xA84A0000u, 41u}, // kck -> Latn
+    {0x906A0000u, 41u}, // kde -> Latn
+    {0xCC6A0000u, 79u}, // kdt -> Thai
+    {0x808A0000u, 41u}, // kea -> Latn
+    {0xB48A0000u, 41u}, // ken -> Latn
+    {0xB8AA0000u, 41u}, // kfo -> Latn
+    {0xC4AA0000u, 16u}, // kfr -> Deva
+    {0xE0AA0000u, 16u}, // kfy -> Deva
+    {0x6B670000u, 41u}, // kg -> Latn
+    {0x90CA0000u, 41u}, // kge -> Latn
+    {0xBCCA0000u, 41u}, // kgp -> Latn
+    {0x80EA0000u, 41u}, // kha -> Latn
+    {0x84EA0000u, 73u}, // khb -> Talu
+    {0xB4EA0000u, 16u}, // khn -> Deva
+    {0xC0EA0000u, 41u}, // khq -> Latn
+    {0xCCEA0000u, 53u}, // kht -> Mymr
+    {0xD8EA0000u,  1u}, // khw -> Arab
+    {0x6B690000u, 41u}, // ki -> Latn
+    {0xD10A0000u, 41u}, // kiu -> Latn
+    {0x6B6A0000u, 41u}, // kj -> Latn
+    {0x992A0000u, 40u}, // kjg -> Laoo
+    {0x6B6B0000u, 15u}, // kk -> Cyrl
+    {0x6B6B4146u,  1u}, // kk-AF -> Arab
+    {0x6B6B434Eu,  1u}, // kk-CN -> Arab
+    {0x6B6B4952u,  1u}, // kk-IR -> Arab
+    {0x6B6B4D4Eu,  1u}, // kk-MN -> Arab
+    {0xA54A0000u, 41u}, // kkj -> Latn
+    {0x6B6C0000u, 41u}, // kl -> Latn
+    {0xB56A0000u, 41u}, // kln -> Latn
+    {0x6B6D0000u, 35u}, // km -> Khmr
+    {0x858A0000u, 41u}, // kmb -> Latn
+    {0x6B6E0000u, 36u}, // kn -> Knda
+    {0x6B6F0000u, 37u}, // ko -> Kore
+    {0xA1CA0000u, 15u}, // koi -> Cyrl
+    {0xA9CA0000u, 16u}, // kok -> Deva
+    {0xC9CA0000u, 41u}, // kos -> Latn
+    {0x91EA0000u, 41u}, // kpe -> Latn
+    {0x8A2A0000u, 15u}, // krc -> Cyrl
+    {0xA22A0000u, 41u}, // kri -> Latn
+    {0xA62A0000u, 41u}, // krj -> Latn
+    {0xAE2A0000u, 41u}, // krl -> Latn
+    {0xD22A0000u, 16u}, // kru -> Deva
+    {0x6B730000u,  1u}, // ks -> Arab
+    {0x864A0000u, 41u}, // ksb -> Latn
+    {0x964A0000u, 41u}, // ksf -> Latn
+    {0x9E4A0000u, 41u}, // ksh -> Latn
+    {0x6B750000u, 41u}, // ku -> Latn
+    {0x6B754952u,  1u}, // ku-IR -> Arab
+    {0x6B754C42u,  1u}, // ku-LB -> Arab
+    {0xB28A0000u, 15u}, // kum -> Cyrl
+    {0x6B760000u, 15u}, // kv -> Cyrl
+    {0xC6AA0000u, 41u}, // kvr -> Latn
+    {0xDEAA0000u,  1u}, // kvx -> Arab
+    {0x6B770000u, 41u}, // kw -> Latn
+    {0xB2EA0000u, 79u}, // kxm -> Thai
+    {0xBEEA0000u,  1u}, // kxp -> Arab
+    {0x6B790000u, 15u}, // ky -> Cyrl
+    {0x6B79434Eu,  1u}, // ky-CN -> Arab
+    {0x6B795452u, 41u}, // ky-TR -> Latn
+    {0x6C610000u, 41u}, // la -> Latn
+    {0x840B0000u, 43u}, // lab -> Lina
+    {0x8C0B0000u, 27u}, // lad -> Hebr
+    {0x980B0000u, 41u}, // lag -> Latn
+    {0x9C0B0000u,  1u}, // lah -> Arab
+    {0xA40B0000u, 41u}, // laj -> Latn
+    {0x6C620000u, 41u}, // lb -> Latn
+    {0x902B0000u, 15u}, // lbe -> Cyrl
+    {0xD82B0000u, 41u}, // lbw -> Latn
+    {0xBC4B0000u, 79u}, // lcp -> Thai
+    {0xBC8B0000u, 42u}, // lep -> Lepc
+    {0xE48B0000u, 15u}, // lez -> Cyrl
+    {0x6C670000u, 41u}, // lg -> Latn
+    {0x6C690000u, 41u}, // li -> Latn
+    {0x950B0000u, 16u}, // lif -> Deva
+    {0xA50B0000u, 41u}, // lij -> Latn
+    {0xC90B0000u, 44u}, // lis -> Lisu
+    {0xBD2B0000u, 41u}, // ljp -> Latn
+    {0xA14B0000u,  1u}, // lki -> Arab
+    {0xCD4B0000u, 41u}, // lkt -> Latn
+    {0xB58B0000u, 76u}, // lmn -> Telu
+    {0xB98B0000u, 41u}, // lmo -> Latn
+    {0x6C6E0000u, 41u}, // ln -> Latn
+    {0x6C6F0000u, 40u}, // lo -> Laoo
+    {0xADCB0000u, 41u}, // lol -> Latn
+    {0xE5CB0000u, 41u}, // loz -> Latn
+    {0x8A2B0000u,  1u}, // lrc -> Arab
+    {0x6C740000u, 41u}, // lt -> Latn
+    {0x9A6B0000u, 41u}, // ltg -> Latn
+    {0x6C750000u, 41u}, // lu -> Latn
+    {0x828B0000u, 41u}, // lua -> Latn
+    {0xBA8B0000u, 41u}, // luo -> Latn
+    {0xE28B0000u, 41u}, // luy -> Latn
+    {0xE68B0000u,  1u}, // luz -> Arab
+    {0x6C760000u, 41u}, // lv -> Latn
+    {0xAECB0000u, 79u}, // lwl -> Thai
+    {0x9F2B0000u, 24u}, // lzh -> Hans
+    {0xE72B0000u, 41u}, // lzz -> Latn
+    {0x8C0C0000u, 41u}, // mad -> Latn
+    {0x940C0000u, 41u}, // maf -> Latn
+    {0x980C0000u, 16u}, // mag -> Deva
+    {0xA00C0000u, 16u}, // mai -> Deva
+    {0xA80C0000u, 41u}, // mak -> Latn
+    {0xB40C0000u, 41u}, // man -> Latn
+    {0xB40C474Eu, 55u}, // man-GN -> Nkoo
+    {0xC80C0000u, 41u}, // mas -> Latn
+    {0xE40C0000u, 41u}, // maz -> Latn
+    {0x946C0000u, 15u}, // mdf -> Cyrl
+    {0x9C6C0000u, 41u}, // mdh -> Latn
+    {0xC46C0000u, 41u}, // mdr -> Latn
+    {0xB48C0000u, 41u}, // men -> Latn
+    {0xC48C0000u, 41u}, // mer -> Latn
+    {0x80AC0000u,  1u}, // mfa -> Arab
+    {0x90AC0000u, 41u}, // mfe -> Latn
+    {0x6D670000u, 41u}, // mg -> Latn
+    {0x9CCC0000u, 41u}, // mgh -> Latn
+    {0xB8CC0000u, 41u}, // mgo -> Latn
+    {0xBCCC0000u, 16u}, // mgp -> Deva
+    {0xE0CC0000u, 41u}, // mgy -> Latn
+    {0x6D680000u, 41u}, // mh -> Latn
+    {0x6D690000u, 41u}, // mi -> Latn
+    {0xB50C0000u, 41u}, // min -> Latn
+    {0xC90C0000u, 26u}, // mis -> Hatr
+    {0x6D6B0000u, 15u}, // mk -> Cyrl
+    {0x6D6C0000u, 50u}, // ml -> Mlym
+    {0xC96C0000u, 41u}, // mls -> Latn
+    {0x6D6E0000u, 15u}, // mn -> Cyrl
+    {0x6D6E434Eu, 51u}, // mn-CN -> Mong
+    {0xA1AC0000u,  7u}, // mni -> Beng
+    {0xD9AC0000u, 53u}, // mnw -> Mymr
+    {0x91CC0000u, 41u}, // moe -> Latn
+    {0x9DCC0000u, 41u}, // moh -> Latn
+    {0xC9CC0000u, 41u}, // mos -> Latn
+    {0x6D720000u, 16u}, // mr -> Deva
+    {0x8E2C0000u, 16u}, // mrd -> Deva
+    {0xA62C0000u, 15u}, // mrj -> Cyrl
+    {0xD22C0000u, 52u}, // mru -> Mroo
+    {0x6D730000u, 41u}, // ms -> Latn
+    {0x6D734343u,  1u}, // ms-CC -> Arab
+    {0x6D734944u,  1u}, // ms-ID -> Arab
+    {0x6D740000u, 41u}, // mt -> Latn
+    {0xC66C0000u, 16u}, // mtr -> Deva
+    {0x828C0000u, 41u}, // mua -> Latn
+    {0xCA8C0000u, 41u}, // mus -> Latn
+    {0xE2AC0000u,  1u}, // mvy -> Arab
+    {0xAACC0000u, 41u}, // mwk -> Latn
+    {0xC6CC0000u, 16u}, // mwr -> Deva
+    {0xD6CC0000u, 41u}, // mwv -> Latn
+    {0x8AEC0000u, 41u}, // mxc -> Latn
+    {0x6D790000u, 53u}, // my -> Mymr
+    {0xD70C0000u, 15u}, // myv -> Cyrl
+    {0xDF0C0000u, 41u}, // myx -> Latn
+    {0xE70C0000u, 47u}, // myz -> Mand
+    {0xB72C0000u,  1u}, // mzn -> Arab
+    {0x6E610000u, 41u}, // na -> Latn
+    {0xB40D0000u, 24u}, // nan -> Hans
+    {0xBC0D0000u, 41u}, // nap -> Latn
+    {0xC00D0000u, 41u}, // naq -> Latn
+    {0x6E620000u, 41u}, // nb -> Latn
+    {0x9C4D0000u, 41u}, // nch -> Latn
+    {0x6E640000u, 41u}, // nd -> Latn
+    {0x886D0000u, 41u}, // ndc -> Latn
+    {0xC86D0000u, 41u}, // nds -> Latn
+    {0x6E650000u, 16u}, // ne -> Deva
+    {0xD88D0000u, 16u}, // new -> Deva
+    {0x6E670000u, 41u}, // ng -> Latn
+    {0xACCD0000u, 41u}, // ngl -> Latn
+    {0x90ED0000u, 41u}, // nhe -> Latn
+    {0xD8ED0000u, 41u}, // nhw -> Latn
+    {0xA50D0000u, 41u}, // nij -> Latn
+    {0xD10D0000u, 41u}, // niu -> Latn
+    {0xB92D0000u, 41u}, // njo -> Latn
+    {0x6E6C0000u, 41u}, // nl -> Latn
+    {0x998D0000u, 41u}, // nmg -> Latn
+    {0x6E6E0000u, 41u}, // nn -> Latn
+    {0x9DAD0000u, 41u}, // nnh -> Latn
+    {0x6E6F0000u, 41u}, // no -> Latn
+    {0x8DCD0000u, 39u}, // nod -> Lana
+    {0x91CD0000u, 16u}, // noe -> Deva
+    {0xB5CD0000u, 64u}, // non -> Runr
+    {0xBA0D0000u, 55u}, // nqo -> Nkoo
+    {0x6E720000u, 41u}, // nr -> Latn
+    {0xAA4D0000u,  9u}, // nsk -> Cans
+    {0xBA4D0000u, 41u}, // nso -> Latn
+    {0xCA8D0000u, 41u}, // nus -> Latn
+    {0x6E760000u, 41u}, // nv -> Latn
+    {0xC2ED0000u, 41u}, // nxq -> Latn
+    {0x6E790000u, 41u}, // ny -> Latn
+    {0xB30D0000u, 41u}, // nym -> Latn
+    {0xB70D0000u, 41u}, // nyn -> Latn
+    {0xA32D0000u, 41u}, // nzi -> Latn
+    {0x6F630000u, 41u}, // oc -> Latn
+    {0x6F6D0000u, 41u}, // om -> Latn
+    {0x6F720000u, 58u}, // or -> Orya
+    {0x6F730000u, 15u}, // os -> Cyrl
+    {0xAA6E0000u, 57u}, // otk -> Orkh
+    {0x70610000u, 23u}, // pa -> Guru
+    {0x7061504Bu,  1u}, // pa-PK -> Arab
+    {0x980F0000u, 41u}, // pag -> Latn
+    {0xAC0F0000u, 60u}, // pal -> Phli
+    {0xB00F0000u, 41u}, // pam -> Latn
+    {0xBC0F0000u, 41u}, // pap -> Latn
+    {0xD00F0000u, 41u}, // pau -> Latn
+    {0x8C4F0000u, 41u}, // pcd -> Latn
+    {0xB04F0000u, 41u}, // pcm -> Latn
+    {0x886F0000u, 41u}, // pdc -> Latn
+    {0xCC6F0000u, 41u}, // pdt -> Latn
+    {0xB88F0000u, 83u}, // peo -> Xpeo
+    {0xACAF0000u, 41u}, // pfl -> Latn
+    {0xB4EF0000u, 61u}, // phn -> Phnx
+    {0x814F0000u,  8u}, // pka -> Brah
+    {0xB94F0000u, 41u}, // pko -> Latn
+    {0x706C0000u, 41u}, // pl -> Latn
+    {0xC98F0000u, 41u}, // pms -> Latn
+    {0xCDAF0000u, 21u}, // pnt -> Grek
+    {0xB5CF0000u, 41u}, // pon -> Latn
+    {0x822F0000u, 34u}, // pra -> Khar
+    {0x8E2F0000u,  1u}, // prd -> Arab
+    {0x9A2F0000u, 41u}, // prg -> Latn
+    {0x70730000u,  1u}, // ps -> Arab
+    {0x70740000u, 41u}, // pt -> Latn
+    {0xD28F0000u, 41u}, // puu -> Latn
+    {0x71750000u, 41u}, // qu -> Latn
+    {0x8A900000u, 41u}, // quc -> Latn
+    {0x9A900000u, 41u}, // qug -> Latn
+    {0xA4110000u, 16u}, // raj -> Deva
+    {0x94510000u, 41u}, // rcf -> Latn
+    {0xA4910000u, 41u}, // rej -> Latn
+    {0xB4D10000u, 41u}, // rgn -> Latn
+    {0x81110000u, 41u}, // ria -> Latn
+    {0x95110000u, 77u}, // rif -> Tfng
+    {0x95114E4Cu, 41u}, // rif-NL -> Latn
+    {0xC9310000u, 16u}, // rjs -> Deva
+    {0xCD510000u,  7u}, // rkt -> Beng
+    {0x726D0000u, 41u}, // rm -> Latn
+    {0x95910000u, 41u}, // rmf -> Latn
+    {0xB9910000u, 41u}, // rmo -> Latn
+    {0xCD910000u,  1u}, // rmt -> Arab
+    {0xD1910000u, 41u}, // rmu -> Latn
+    {0x726E0000u, 41u}, // rn -> Latn
+    {0x99B10000u, 41u}, // rng -> Latn
+    {0x726F0000u, 41u}, // ro -> Latn
+    {0x85D10000u, 41u}, // rob -> Latn
+    {0x95D10000u, 41u}, // rof -> Latn
+    {0xB2710000u, 41u}, // rtm -> Latn
+    {0x72750000u, 15u}, // ru -> Cyrl
+    {0x92910000u, 15u}, // rue -> Cyrl
+    {0x9A910000u, 41u}, // rug -> Latn
+    {0x72770000u, 41u}, // rw -> Latn
+    {0xAAD10000u, 41u}, // rwk -> Latn
+    {0xD3110000u, 33u}, // ryu -> Kana
+    {0x73610000u, 16u}, // sa -> Deva
+    {0x94120000u, 41u}, // saf -> Latn
+    {0x9C120000u, 15u}, // sah -> Cyrl
+    {0xC0120000u, 41u}, // saq -> Latn
+    {0xC8120000u, 41u}, // sas -> Latn
+    {0xCC120000u, 41u}, // sat -> Latn
+    {0xE4120000u, 67u}, // saz -> Saur
+    {0xBC320000u, 41u}, // sbp -> Latn
+    {0x73630000u, 41u}, // sc -> Latn
+    {0xA8520000u, 16u}, // sck -> Deva
+    {0xB4520000u, 41u}, // scn -> Latn
+    {0xB8520000u, 41u}, // sco -> Latn
+    {0xC8520000u, 41u}, // scs -> Latn
+    {0x73640000u,  1u}, // sd -> Arab
+    {0x88720000u, 41u}, // sdc -> Latn
+    {0x9C720000u,  1u}, // sdh -> Arab
+    {0x73650000u, 41u}, // se -> Latn
+    {0x94920000u, 41u}, // sef -> Latn
+    {0x9C920000u, 41u}, // seh -> Latn
+    {0xA0920000u, 41u}, // sei -> Latn
+    {0xC8920000u, 41u}, // ses -> Latn
+    {0x73670000u, 41u}, // sg -> Latn
+    {0x80D20000u, 56u}, // sga -> Ogam
+    {0xC8D20000u, 41u}, // sgs -> Latn
+    {0x73680000u, 41u}, // sh -> Latn
+    {0xA0F20000u, 77u}, // shi -> Tfng
+    {0xB4F20000u, 53u}, // shn -> Mymr
+    {0x73690000u, 69u}, // si -> Sinh
+    {0x8D120000u, 41u}, // sid -> Latn
+    {0x736B0000u, 41u}, // sk -> Latn
+    {0xC5520000u,  1u}, // skr -> Arab
+    {0x736C0000u, 41u}, // sl -> Latn
+    {0xA1720000u, 41u}, // sli -> Latn
+    {0xE1720000u, 41u}, // sly -> Latn
+    {0x736D0000u, 41u}, // sm -> Latn
+    {0x81920000u, 41u}, // sma -> Latn
+    {0xA5920000u, 41u}, // smj -> Latn
+    {0xB5920000u, 41u}, // smn -> Latn
+    {0xBD920000u, 65u}, // smp -> Samr
+    {0xC9920000u, 41u}, // sms -> Latn
+    {0x736E0000u, 41u}, // sn -> Latn
+    {0xA9B20000u, 41u}, // snk -> Latn
+    {0x736F0000u, 41u}, // so -> Latn
+    {0xD1D20000u, 79u}, // sou -> Thai
+    {0x73710000u, 41u}, // sq -> Latn
+    {0x73720000u, 15u}, // sr -> Cyrl
+    {0x73724D45u, 41u}, // sr-ME -> Latn
+    {0x7372524Fu, 41u}, // sr-RO -> Latn
+    {0x73725255u, 41u}, // sr-RU -> Latn
+    {0x73725452u, 41u}, // sr-TR -> Latn
+    {0x86320000u, 70u}, // srb -> Sora
+    {0xB6320000u, 41u}, // srn -> Latn
+    {0xC6320000u, 41u}, // srr -> Latn
+    {0xDE320000u, 16u}, // srx -> Deva
+    {0x73730000u, 41u}, // ss -> Latn
+    {0xE2520000u, 41u}, // ssy -> Latn
+    {0x73740000u, 41u}, // st -> Latn
+    {0xC2720000u, 41u}, // stq -> Latn
+    {0x73750000u, 41u}, // su -> Latn
+    {0xAA920000u, 41u}, // suk -> Latn
+    {0xCA920000u, 41u}, // sus -> Latn
+    {0x73760000u, 41u}, // sv -> Latn
+    {0x73770000u, 41u}, // sw -> Latn
+    {0x86D20000u,  1u}, // swb -> Arab
+    {0x8AD20000u, 41u}, // swc -> Latn
+    {0x9AD20000u, 41u}, // swg -> Latn
+    {0xD6D20000u, 16u}, // swv -> Deva
+    {0xB6F20000u, 41u}, // sxn -> Latn
+    {0xAF120000u,  7u}, // syl -> Beng
+    {0xC7120000u, 71u}, // syr -> Syrc
+    {0xAF320000u, 41u}, // szl -> Latn
+    {0x74610000u, 74u}, // ta -> Taml
+    {0xA4130000u, 16u}, // taj -> Deva
+    {0xD8330000u, 41u}, // tbw -> Latn
+    {0xE0530000u, 36u}, // tcy -> Knda
+    {0x8C730000u, 72u}, // tdd -> Tale
+    {0x98730000u, 16u}, // tdg -> Deva
+    {0x9C730000u, 16u}, // tdh -> Deva
+    {0x74650000u, 76u}, // te -> Telu
+    {0xB0930000u, 41u}, // tem -> Latn
+    {0xB8930000u, 41u}, // teo -> Latn
+    {0xCC930000u, 41u}, // tet -> Latn
+    {0x74670000u, 15u}, // tg -> Cyrl
+    {0x7467504Bu,  1u}, // tg-PK -> Arab
+    {0x74680000u, 79u}, // th -> Thai
+    {0xACF30000u, 16u}, // thl -> Deva
+    {0xC0F30000u, 16u}, // thq -> Deva
+    {0xC4F30000u, 16u}, // thr -> Deva
+    {0x74690000u, 18u}, // ti -> Ethi
+    {0x99130000u, 18u}, // tig -> Ethi
+    {0xD5130000u, 41u}, // tiv -> Latn
+    {0x746B0000u, 41u}, // tk -> Latn
+    {0xAD530000u, 41u}, // tkl -> Latn
+    {0xC5530000u, 41u}, // tkr -> Latn
+    {0xCD530000u, 16u}, // tkt -> Deva
+    {0x746C0000u, 41u}, // tl -> Latn
+    {0xE1730000u, 41u}, // tly -> Latn
+    {0x9D930000u, 41u}, // tmh -> Latn
+    {0x746E0000u, 41u}, // tn -> Latn
+    {0x746F0000u, 41u}, // to -> Latn
+    {0x99D30000u, 41u}, // tog -> Latn
+    {0xA1F30000u, 41u}, // tpi -> Latn
+    {0x74720000u, 41u}, // tr -> Latn
+    {0xD2330000u, 41u}, // tru -> Latn
+    {0xD6330000u, 41u}, // trv -> Latn
+    {0x74730000u, 41u}, // ts -> Latn
+    {0x8E530000u, 21u}, // tsd -> Grek
+    {0x96530000u, 16u}, // tsf -> Deva
+    {0x9A530000u, 41u}, // tsg -> Latn
+    {0xA6530000u, 80u}, // tsj -> Tibt
+    {0x74740000u, 15u}, // tt -> Cyrl
+    {0xA6730000u, 41u}, // ttj -> Latn
+    {0xCA730000u, 79u}, // tts -> Thai
+    {0xCE730000u, 41u}, // ttt -> Latn
+    {0xB2930000u, 41u}, // tum -> Latn
+    {0xAEB30000u, 41u}, // tvl -> Latn
+    {0xC2D30000u, 41u}, // twq -> Latn
+    {0x74790000u, 41u}, // ty -> Latn
+    {0xD7130000u, 15u}, // tyv -> Cyrl
+    {0xB3330000u, 41u}, // tzm -> Latn
+    {0xB0740000u, 15u}, // udm -> Cyrl
+    {0x75670000u,  1u}, // ug -> Arab
+    {0x75674B5Au, 15u}, // ug-KZ -> Cyrl
+    {0x75674D4Eu, 15u}, // ug-MN -> Cyrl
+    {0x80D40000u, 81u}, // uga -> Ugar
+    {0x756B0000u, 15u}, // uk -> Cyrl
+    {0xA1740000u, 41u}, // uli -> Latn
+    {0x85940000u, 41u}, // umb -> Latn
+    {0xC5B40000u,  7u}, // unr -> Beng
+    {0xC5B44E50u, 16u}, // unr-NP -> Deva
+    {0xDDB40000u,  7u}, // unx -> Beng
+    {0x75720000u,  1u}, // ur -> Arab
+    {0x757A0000u, 41u}, // uz -> Latn
+    {0x757A4146u,  1u}, // uz-AF -> Arab
+    {0x757A434Eu, 15u}, // uz-CN -> Cyrl
+    {0xA0150000u, 82u}, // vai -> Vaii
+    {0x76650000u, 41u}, // ve -> Latn
+    {0x88950000u, 41u}, // vec -> Latn
+    {0xBC950000u, 41u}, // vep -> Latn
+    {0x76690000u, 41u}, // vi -> Latn
+    {0x89150000u, 41u}, // vic -> Latn
+    {0xC9750000u, 41u}, // vls -> Latn
+    {0x95950000u, 41u}, // vmf -> Latn
+    {0xD9950000u, 41u}, // vmw -> Latn
+    {0x766F0000u, 41u}, // vo -> Latn
+    {0xCDD50000u, 41u}, // vot -> Latn
+    {0xBA350000u, 41u}, // vro -> Latn
+    {0xB6950000u, 41u}, // vun -> Latn
+    {0x77610000u, 41u}, // wa -> Latn
+    {0x90160000u, 41u}, // wae -> Latn
+    {0xAC160000u, 18u}, // wal -> Ethi
+    {0xC4160000u, 41u}, // war -> Latn
+    {0xBC360000u, 41u}, // wbp -> Latn
+    {0xC0360000u, 76u}, // wbq -> Telu
+    {0xC4360000u, 16u}, // wbr -> Deva
+    {0xC9760000u, 41u}, // wls -> Latn
+    {0xA1B60000u,  1u}, // wni -> Arab
+    {0x776F0000u, 41u}, // wo -> Latn
+    {0xB2760000u, 16u}, // wtm -> Deva
+    {0xD2960000u, 24u}, // wuu -> Hans
+    {0xD4170000u, 41u}, // xav -> Latn
+    {0xC4570000u, 10u}, // xcr -> Cari
+    {0x78680000u, 41u}, // xh -> Latn
+    {0x89770000u, 45u}, // xlc -> Lyci
+    {0x8D770000u, 46u}, // xld -> Lydi
+    {0x95970000u, 19u}, // xmf -> Geor
+    {0xB5970000u, 48u}, // xmn -> Mani
+    {0xC5970000u, 49u}, // xmr -> Merc
+    {0x81B70000u, 54u}, // xna -> Narb
+    {0xC5B70000u, 16u}, // xnr -> Deva
+    {0x99D70000u, 41u}, // xog -> Latn
+    {0xC5F70000u, 63u}, // xpr -> Prti
+    {0x82570000u, 66u}, // xsa -> Sarb
+    {0xC6570000u, 16u}, // xsr -> Deva
+    {0xB8180000u, 41u}, // yao -> Latn
+    {0xBC180000u, 41u}, // yap -> Latn
+    {0xD4180000u, 41u}, // yav -> Latn
+    {0x84380000u, 41u}, // ybb -> Latn
+    {0x79690000u, 27u}, // yi -> Hebr
+    {0x796F0000u, 41u}, // yo -> Latn
+    {0xAE380000u, 41u}, // yrl -> Latn
+    {0x82980000u, 41u}, // yua -> Latn
+    {0x7A610000u, 41u}, // za -> Latn
+    {0x98190000u, 41u}, // zag -> Latn
+    {0xA4790000u,  1u}, // zdj -> Arab
+    {0x80990000u, 41u}, // zea -> Latn
+    {0x9CD90000u, 77u}, // zgh -> Tfng
+    {0x7A680000u, 24u}, // zh -> Hans
+    {0x7A684155u, 25u}, // zh-AU -> Hant
+    {0x7A68424Eu, 25u}, // zh-BN -> Hant
+    {0x7A684742u, 25u}, // zh-GB -> Hant
+    {0x7A684746u, 25u}, // zh-GF -> Hant
+    {0x7A68484Bu, 25u}, // zh-HK -> Hant
+    {0x7A684944u, 25u}, // zh-ID -> Hant
+    {0x7A684D4Fu, 25u}, // zh-MO -> Hant
+    {0x7A684D59u, 25u}, // zh-MY -> Hant
+    {0x7A685041u, 25u}, // zh-PA -> Hant
+    {0x7A685046u, 25u}, // zh-PF -> Hant
+    {0x7A685048u, 25u}, // zh-PH -> Hant
+    {0x7A685352u, 25u}, // zh-SR -> Hant
+    {0x7A685448u, 25u}, // zh-TH -> Hant
+    {0x7A685457u, 25u}, // zh-TW -> Hant
+    {0x7A685553u, 25u}, // zh-US -> Hant
+    {0x7A68564Eu, 25u}, // zh-VN -> Hant
+    {0xA1990000u, 41u}, // zmi -> Latn
+    {0x7A750000u, 41u}, // zu -> Latn
+    {0x83390000u, 41u}, // zza -> Latn
+});
+
+std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
+    0x616145544C61746Ellu, // aa_Latn_ET
+    0x616247454379726Cllu, // ab_Cyrl_GE
+    0xC42047484C61746Ellu, // abr_Latn_GH
+    0x904049444C61746Ellu, // ace_Latn_ID
+    0x9C4055474C61746Ellu, // ach_Latn_UG
+    0x806047484C61746Ellu, // ada_Latn_GH
+    0xE06052554379726Cllu, // ady_Cyrl_RU
+    0x6165495241767374llu, // ae_Avst_IR
+    0x8480544E41726162llu, // aeb_Arab_TN
+    0x61665A414C61746Ellu, // af_Latn_ZA
+    0xC0C0434D4C61746Ellu, // agq_Latn_CM
+    0xB8E0494E41686F6Dllu, // aho_Ahom_IN
+    0x616B47484C61746Ellu, // ak_Latn_GH
+    0xA940495158737578llu, // akk_Xsux_IQ
+    0xB560584B4C61746Ellu, // aln_Latn_XK
+    0xCD6052554379726Cllu, // alt_Cyrl_RU
+    0x616D455445746869llu, // am_Ethi_ET
+    0xB9804E474C61746Ellu, // amo_Latn_NG
+    0xE5C049444C61746Ellu, // aoz_Latn_ID
+    0x6172454741726162llu, // ar_Arab_EG
+    0x8A20495241726D69llu, // arc_Armi_IR
+    0x8A204A4F4E626174llu, // arc_Nbat_JO
+    0x8A20535950616C6Dllu, // arc_Palm_SY
+    0xB620434C4C61746Ellu, // arn_Latn_CL
+    0xBA20424F4C61746Ellu, // aro_Latn_BO
+    0xC220445A41726162llu, // arq_Arab_DZ
+    0xE2204D4141726162llu, // ary_Arab_MA
+    0xE620454741726162llu, // arz_Arab_EG
+    0x6173494E42656E67llu, // as_Beng_IN
+    0x8240545A4C61746Ellu, // asa_Latn_TZ
+    0x9240555353676E77llu, // ase_Sgnw_US
+    0xCE4045534C61746Ellu, // ast_Latn_ES
+    0xA66043414C61746Ellu, // atj_Latn_CA
+    0x617652554379726Cllu, // av_Cyrl_RU
+    0x82C0494E44657661llu, // awa_Deva_IN
+    0x6179424F4C61746Ellu, // ay_Latn_BO
+    0x617A495241726162llu, // az_Arab_IR
+    0x617A415A4C61746Ellu, // az_Latn_AZ
+    0x626152554379726Cllu, // ba_Cyrl_RU
+    0xAC01504B41726162llu, // bal_Arab_PK
+    0xB40149444C61746Ellu, // ban_Latn_ID
+    0xBC014E5044657661llu, // bap_Deva_NP
+    0xC40141544C61746Ellu, // bar_Latn_AT
+    0xC801434D4C61746Ellu, // bas_Latn_CM
+    0xDC01434D42616D75llu, // bax_Bamu_CM
+    0x882149444C61746Ellu, // bbc_Latn_ID
+    0xA421434D4C61746Ellu, // bbj_Latn_CM
+    0xA04143494C61746Ellu, // bci_Latn_CI
+    0x626542594379726Cllu, // be_Cyrl_BY
+    0xA481534441726162llu, // bej_Arab_SD
+    0xB0815A4D4C61746Ellu, // bem_Latn_ZM
+    0xD88149444C61746Ellu, // bew_Latn_ID
+    0xE481545A4C61746Ellu, // bez_Latn_TZ
+    0x8CA1434D4C61746Ellu, // bfd_Latn_CM
+    0xC0A1494E54616D6Cllu, // bfq_Taml_IN
+    0xCCA1504B41726162llu, // bft_Arab_PK
+    0xE0A1494E44657661llu, // bfy_Deva_IN
+    0x626742474379726Cllu, // bg_Cyrl_BG
+    0x88C1494E44657661llu, // bgc_Deva_IN
+    0xB4C1504B41726162llu, // bgn_Arab_PK
+    0xDCC154524772656Bllu, // bgx_Grek_TR
+    0x6268494E4B746869llu, // bh_Kthi_IN
+    0x84E1494E44657661llu, // bhb_Deva_IN
+    0xA0E1494E44657661llu, // bhi_Deva_IN
+    0xA8E150484C61746Ellu, // bhk_Latn_PH
+    0xB8E1494E44657661llu, // bho_Deva_IN
+    0x626956554C61746Ellu, // bi_Latn_VU
+    0xA90150484C61746Ellu, // bik_Latn_PH
+    0xB5014E474C61746Ellu, // bin_Latn_NG
+    0xA521494E44657661llu, // bjj_Deva_IN
+    0xB52149444C61746Ellu, // bjn_Latn_ID
+    0xB141434D4C61746Ellu, // bkm_Latn_CM
+    0xD14150484C61746Ellu, // bku_Latn_PH
+    0xCD61564E54617674llu, // blt_Tavt_VN
+    0x626D4D4C4C61746Ellu, // bm_Latn_ML
+    0xC1814D4C4C61746Ellu, // bmq_Latn_ML
+    0x626E424442656E67llu, // bn_Beng_BD
+    0x626F434E54696274llu, // bo_Tibt_CN
+    0xE1E1494E42656E67llu, // bpy_Beng_IN
+    0xA201495241726162llu, // bqi_Arab_IR
+    0xD60143494C61746Ellu, // bqv_Latn_CI
+    0x627246524C61746Ellu, // br_Latn_FR
+    0x8221494E44657661llu, // bra_Deva_IN
+    0x9E21504B41726162llu, // brh_Arab_PK
+    0xDE21494E44657661llu, // brx_Deva_IN
+    0x627342414C61746Ellu, // bs_Latn_BA
+    0xC2414C5242617373llu, // bsq_Bass_LR
+    0xCA41434D4C61746Ellu, // bss_Latn_CM
+    0xBA6150484C61746Ellu, // bto_Latn_PH
+    0xD661504B44657661llu, // btv_Deva_PK
+    0x828152554379726Cllu, // bua_Cyrl_RU
+    0x8A8159544C61746Ellu, // buc_Latn_YT
+    0x9A8149444C61746Ellu, // bug_Latn_ID
+    0xB281434D4C61746Ellu, // bum_Latn_CM
+    0x86A147514C61746Ellu, // bvb_Latn_GQ
+    0xB701455245746869llu, // byn_Ethi_ER
+    0xD701434D4C61746Ellu, // byv_Latn_CM
+    0x93214D4C4C61746Ellu, // bze_Latn_ML
+    0x636145534C61746Ellu, // ca_Latn_ES
+    0x9C424E474C61746Ellu, // cch_Latn_NG
+    0xBC42494E42656E67llu, // ccp_Beng_IN
+    0xBC42424443616B6Dllu, // ccp_Cakm_BD
+    0x636552554379726Cllu, // ce_Cyrl_RU
+    0x848250484C61746Ellu, // ceb_Latn_PH
+    0x98C255474C61746Ellu, // cgg_Latn_UG
+    0x636847554C61746Ellu, // ch_Latn_GU
+    0xA8E2464D4C61746Ellu, // chk_Latn_FM
+    0xB0E252554379726Cllu, // chm_Cyrl_RU
+    0xB8E255534C61746Ellu, // cho_Latn_US
+    0xBCE243414C61746Ellu, // chp_Latn_CA
+    0xC4E2555343686572llu, // chr_Cher_US
+    0x81224B4841726162llu, // cja_Arab_KH
+    0xB122564E4368616Dllu, // cjm_Cham_VN
+    0x8542495141726162llu, // ckb_Arab_IQ
+    0x636F46524C61746Ellu, // co_Latn_FR
+    0xBDC24547436F7074llu, // cop_Copt_EG
+    0xC9E250484C61746Ellu, // cps_Latn_PH
+    0x6372434143616E73llu, // cr_Cans_CA
+    0xA622434143616E73llu, // crj_Cans_CA
+    0xAA22434143616E73llu, // crk_Cans_CA
+    0xAE22434143616E73llu, // crl_Cans_CA
+    0xB222434143616E73llu, // crm_Cans_CA
+    0xCA2253434C61746Ellu, // crs_Latn_SC
+    0x6373435A4C61746Ellu, // cs_Latn_CZ
+    0x8642504C4C61746Ellu, // csb_Latn_PL
+    0xDA42434143616E73llu, // csw_Cans_CA
+    0x8E624D4D50617563llu, // ctd_Pauc_MM
+    0x637552554379726Cllu, // cu_Cyrl_RU
+    0x63754247476C6167llu, // cu_Glag_BG
+    0x637652554379726Cllu, // cv_Cyrl_RU
+    0x637947424C61746Ellu, // cy_Latn_GB
+    0x6461444B4C61746Ellu, // da_Latn_DK
+    0xA80355534C61746Ellu, // dak_Latn_US
+    0xC40352554379726Cllu, // dar_Cyrl_RU
+    0xD4034B454C61746Ellu, // dav_Latn_KE
+    0x8843494E41726162llu, // dcc_Arab_IN
+    0x646544454C61746Ellu, // de_Latn_DE
+    0xB48343414C61746Ellu, // den_Latn_CA
+    0xC4C343414C61746Ellu, // dgr_Latn_CA
+    0x91234E454C61746Ellu, // dje_Latn_NE
+    0xA5A343494C61746Ellu, // dnj_Latn_CI
+    0xA1C3494E41726162llu, // doi_Arab_IN
+    0x864344454C61746Ellu, // dsb_Latn_DE
+    0xB2634D4C4C61746Ellu, // dtm_Latn_ML
+    0xBE634D594C61746Ellu, // dtp_Latn_MY
+    0x8283434D4C61746Ellu, // dua_Latn_CM
+    0x64764D5654686161llu, // dv_Thaa_MV
+    0xBB03534E4C61746Ellu, // dyo_Latn_SN
+    0xD30342464C61746Ellu, // dyu_Latn_BF
+    0x647A425454696274llu, // dz_Tibt_BT
+    0xD0244B454C61746Ellu, // ebu_Latn_KE
+    0x656547484C61746Ellu, // ee_Latn_GH
+    0xA0A44E474C61746Ellu, // efi_Latn_NG
+    0xACC449544C61746Ellu, // egl_Latn_IT
+    0xE0C4454745677970llu, // egy_Egyp_EG
+    0xE1444D4D4B616C69llu, // eky_Kali_MM
+    0x656C47524772656Bllu, // el_Grek_GR
+    0x656E47424C61746Ellu, // en_Latn_GB
+    0x656E55534C61746Ellu, // en_Latn_US
+    0x656E474253686177llu, // en_Shaw_GB
+    0x657345534C61746Ellu, // es_Latn_ES
+    0x65734D584C61746Ellu, // es_Latn_MX
+    0x657355534C61746Ellu, // es_Latn_US
+    0xD24455534C61746Ellu, // esu_Latn_US
+    0x657445454C61746Ellu, // et_Latn_EE
+    0xCE6449544974616Cllu, // ett_Ital_IT
+    0x657545534C61746Ellu, // eu_Latn_ES
+    0xBAC4434D4C61746Ellu, // ewo_Latn_CM
+    0xCEE445534C61746Ellu, // ext_Latn_ES
+    0x6661495241726162llu, // fa_Arab_IR
+    0xB40547514C61746Ellu, // fan_Latn_GQ
+    0x6666534E4C61746Ellu, // ff_Latn_SN
+    0xB0A54D4C4C61746Ellu, // ffm_Latn_ML
+    0x666946494C61746Ellu, // fi_Latn_FI
+    0x8105534441726162llu, // fia_Arab_SD
+    0xAD0550484C61746Ellu, // fil_Latn_PH
+    0xCD0553454C61746Ellu, // fit_Latn_SE
+    0x666A464A4C61746Ellu, // fj_Latn_FJ
+    0x666F464F4C61746Ellu, // fo_Latn_FO
+    0xB5C5424A4C61746Ellu, // fon_Latn_BJ
+    0x667246524C61746Ellu, // fr_Latn_FR
+    0x8A2555534C61746Ellu, // frc_Latn_US
+    0xBE2546524C61746Ellu, // frp_Latn_FR
+    0xC62544454C61746Ellu, // frr_Latn_DE
+    0xCA2544454C61746Ellu, // frs_Latn_DE
+    0x8E8557464C61746Ellu, // fud_Latn_WF
+    0xC2854E454C61746Ellu, // fuq_Latn_NE
+    0xC68549544C61746Ellu, // fur_Latn_IT
+    0xD6854E474C61746Ellu, // fuv_Latn_NG
+    0xC6A553444C61746Ellu, // fvr_Latn_SD
+    0x66794E4C4C61746Ellu, // fy_Latn_NL
+    0x676149454C61746Ellu, // ga_Latn_IE
+    0x800647484C61746Ellu, // gaa_Latn_GH
+    0x98064D444C61746Ellu, // gag_Latn_MD
+    0xB406434E48616E73llu, // gan_Hans_CN
+    0xE00649444C61746Ellu, // gay_Latn_ID
+    0xB026494E44657661llu, // gbm_Deva_IN
+    0xE426495241726162llu, // gbz_Arab_IR
+    0xC44647464C61746Ellu, // gcr_Latn_GF
+    0x676447424C61746Ellu, // gd_Latn_GB
+    0xE486455445746869llu, // gez_Ethi_ET
+    0xB4C64E5044657661llu, // ggn_Deva_NP
+    0xAD064B494C61746Ellu, // gil_Latn_KI
+    0xA926504B41726162llu, // gjk_Arab_PK
+    0xD126504B41726162llu, // gju_Arab_PK
+    0x676C45534C61746Ellu, // gl_Latn_ES
+    0xA966495241726162llu, // glk_Arab_IR
+    0x676E50594C61746Ellu, // gn_Latn_PY
+    0xB1C6494E44657661llu, // gom_Deva_IN
+    0xB5C6494E54656C75llu, // gon_Telu_IN
+    0xC5C649444C61746Ellu, // gor_Latn_ID
+    0xC9C64E4C4C61746Ellu, // gos_Latn_NL
+    0xCDC65541476F7468llu, // got_Goth_UA
+    0x8A26435943707274llu, // grc_Cprt_CY
+    0x8A2647524C696E62llu, // grc_Linb_GR
+    0xCE26494E42656E67llu, // grt_Beng_IN
+    0xDA4643484C61746Ellu, // gsw_Latn_CH
+    0x6775494E47756A72llu, // gu_Gujr_IN
+    0x868642524C61746Ellu, // gub_Latn_BR
+    0x8A86434F4C61746Ellu, // guc_Latn_CO
+    0xC68647484C61746Ellu, // gur_Latn_GH
+    0xE6864B454C61746Ellu, // guz_Latn_KE
+    0x6776494D4C61746Ellu, // gv_Latn_IM
+    0xC6A64E5044657661llu, // gvr_Deva_NP
+    0xA2C643414C61746Ellu, // gwi_Latn_CA
+    0x68614E474C61746Ellu, // ha_Latn_NG
+    0xA807434E48616E73llu, // hak_Hans_CN
+    0xD80755534C61746Ellu, // haw_Latn_US
+    0xE407414641726162llu, // haz_Arab_AF
+    0x6865494C48656272llu, // he_Hebr_IL
+    0x6869494E44657661llu, // hi_Deva_IN
+    0x9507464A4C61746Ellu, // hif_Latn_FJ
+    0xAD0750484C61746Ellu, // hil_Latn_PH
+    0xD1675452486C7577llu, // hlu_Hluw_TR
+    0x8D87434E506C7264llu, // hmd_Plrd_CN
+    0x8DA7504B41726162llu, // hnd_Arab_PK
+    0x91A7494E44657661llu, // hne_Deva_IN
+    0xA5A74C41486D6E67llu, // hnj_Hmng_LA
+    0xB5A750484C61746Ellu, // hnn_Latn_PH
+    0xB9A7504B41726162llu, // hno_Arab_PK
+    0x686F50474C61746Ellu, // ho_Latn_PG
+    0x89C7494E44657661llu, // hoc_Deva_IN
+    0xA5C7494E44657661llu, // hoj_Deva_IN
+    0x687248524C61746Ellu, // hr_Latn_HR
+    0x864744454C61746Ellu, // hsb_Latn_DE
+    0xB647434E48616E73llu, // hsn_Hans_CN
+    0x687448544C61746Ellu, // ht_Latn_HT
+    0x687548554C61746Ellu, // hu_Latn_HU
+    0x6879414D41726D6Ellu, // hy_Armn_AM
+    0x687A4E414C61746Ellu, // hz_Latn_NA
+    0x696146524C61746Ellu, // ia_Latn_FR
+    0x80284D594C61746Ellu, // iba_Latn_MY
+    0x84284E474C61746Ellu, // ibb_Latn_NG
+    0x696449444C61746Ellu, // id_Latn_ID
+    0x69674E474C61746Ellu, // ig_Latn_NG
+    0x6969434E59696969llu, // ii_Yiii_CN
+    0x696B55534C61746Ellu, // ik_Latn_US
+    0xCD4843414C61746Ellu, // ikt_Latn_CA
+    0xB96850484C61746Ellu, // ilo_Latn_PH
+    0x696E49444C61746Ellu, // in_Latn_ID
+    0x9DA852554379726Cllu, // inh_Cyrl_RU
+    0x697349534C61746Ellu, // is_Latn_IS
+    0x697449544C61746Ellu, // it_Latn_IT
+    0x6975434143616E73llu, // iu_Cans_CA
+    0x6977494C48656272llu, // iw_Hebr_IL
+    0x9F2852554C61746Ellu, // izh_Latn_RU
+    0x6A614A504A70616Ellu, // ja_Jpan_JP
+    0xB0094A4D4C61746Ellu, // jam_Latn_JM
+    0xB8C9434D4C61746Ellu, // jgo_Latn_CM
+    0x6A69554148656272llu, // ji_Hebr_UA
+    0x8989545A4C61746Ellu, // jmc_Latn_TZ
+    0xAD894E5044657661llu, // jml_Deva_NP
+    0xCE89444B4C61746Ellu, // jut_Latn_DK
+    0x6A7649444C61746Ellu, // jv_Latn_ID
+    0x6A7749444C61746Ellu, // jw_Latn_ID
+    0x6B61474547656F72llu, // ka_Geor_GE
+    0x800A555A4379726Cllu, // kaa_Cyrl_UZ
+    0x840A445A4C61746Ellu, // kab_Latn_DZ
+    0x880A4D4D4C61746Ellu, // kac_Latn_MM
+    0xA40A4E474C61746Ellu, // kaj_Latn_NG
+    0xB00A4B454C61746Ellu, // kam_Latn_KE
+    0xB80A4D4C4C61746Ellu, // kao_Latn_ML
+    0x8C2A52554379726Cllu, // kbd_Cyrl_RU
+    0x984A4E474C61746Ellu, // kcg_Latn_NG
+    0xA84A5A574C61746Ellu, // kck_Latn_ZW
+    0x906A545A4C61746Ellu, // kde_Latn_TZ
+    0xCC6A544854686169llu, // kdt_Thai_TH
+    0x808A43564C61746Ellu, // kea_Latn_CV
+    0xB48A434D4C61746Ellu, // ken_Latn_CM
+    0xB8AA43494C61746Ellu, // kfo_Latn_CI
+    0xC4AA494E44657661llu, // kfr_Deva_IN
+    0xE0AA494E44657661llu, // kfy_Deva_IN
+    0x6B6743444C61746Ellu, // kg_Latn_CD
+    0x90CA49444C61746Ellu, // kge_Latn_ID
+    0xBCCA42524C61746Ellu, // kgp_Latn_BR
+    0x80EA494E4C61746Ellu, // kha_Latn_IN
+    0x84EA434E54616C75llu, // khb_Talu_CN
+    0xB4EA494E44657661llu, // khn_Deva_IN
+    0xC0EA4D4C4C61746Ellu, // khq_Latn_ML
+    0xCCEA494E4D796D72llu, // kht_Mymr_IN
+    0xD8EA504B41726162llu, // khw_Arab_PK
+    0x6B694B454C61746Ellu, // ki_Latn_KE
+    0xD10A54524C61746Ellu, // kiu_Latn_TR
+    0x6B6A4E414C61746Ellu, // kj_Latn_NA
+    0x992A4C414C616F6Fllu, // kjg_Laoo_LA
+    0x6B6B434E41726162llu, // kk_Arab_CN
+    0x6B6B4B5A4379726Cllu, // kk_Cyrl_KZ
+    0xA54A434D4C61746Ellu, // kkj_Latn_CM
+    0x6B6C474C4C61746Ellu, // kl_Latn_GL
+    0xB56A4B454C61746Ellu, // kln_Latn_KE
+    0x6B6D4B484B686D72llu, // km_Khmr_KH
+    0x858A414F4C61746Ellu, // kmb_Latn_AO
+    0x6B6E494E4B6E6461llu, // kn_Knda_IN
+    0x6B6F4B524B6F7265llu, // ko_Kore_KR
+    0xA1CA52554379726Cllu, // koi_Cyrl_RU
+    0xA9CA494E44657661llu, // kok_Deva_IN
+    0xC9CA464D4C61746Ellu, // kos_Latn_FM
+    0x91EA4C524C61746Ellu, // kpe_Latn_LR
+    0x8A2A52554379726Cllu, // krc_Cyrl_RU
+    0xA22A534C4C61746Ellu, // kri_Latn_SL
+    0xA62A50484C61746Ellu, // krj_Latn_PH
+    0xAE2A52554C61746Ellu, // krl_Latn_RU
+    0xD22A494E44657661llu, // kru_Deva_IN
+    0x6B73494E41726162llu, // ks_Arab_IN
+    0x864A545A4C61746Ellu, // ksb_Latn_TZ
+    0x964A434D4C61746Ellu, // ksf_Latn_CM
+    0x9E4A44454C61746Ellu, // ksh_Latn_DE
+    0x6B75495141726162llu, // ku_Arab_IQ
+    0x6B7554524C61746Ellu, // ku_Latn_TR
+    0xB28A52554379726Cllu, // kum_Cyrl_RU
+    0x6B7652554379726Cllu, // kv_Cyrl_RU
+    0xC6AA49444C61746Ellu, // kvr_Latn_ID
+    0xDEAA504B41726162llu, // kvx_Arab_PK
+    0x6B7747424C61746Ellu, // kw_Latn_GB
+    0xB2EA544854686169llu, // kxm_Thai_TH
+    0xBEEA504B41726162llu, // kxp_Arab_PK
+    0x6B79434E41726162llu, // ky_Arab_CN
+    0x6B794B474379726Cllu, // ky_Cyrl_KG
+    0x6B7954524C61746Ellu, // ky_Latn_TR
+    0x6C6156414C61746Ellu, // la_Latn_VA
+    0x840B47524C696E61llu, // lab_Lina_GR
+    0x8C0B494C48656272llu, // lad_Hebr_IL
+    0x980B545A4C61746Ellu, // lag_Latn_TZ
+    0x9C0B504B41726162llu, // lah_Arab_PK
+    0xA40B55474C61746Ellu, // laj_Latn_UG
+    0x6C624C554C61746Ellu, // lb_Latn_LU
+    0x902B52554379726Cllu, // lbe_Cyrl_RU
+    0xD82B49444C61746Ellu, // lbw_Latn_ID
+    0xBC4B434E54686169llu, // lcp_Thai_CN
+    0xBC8B494E4C657063llu, // lep_Lepc_IN
+    0xE48B52554379726Cllu, // lez_Cyrl_RU
+    0x6C6755474C61746Ellu, // lg_Latn_UG
+    0x6C694E4C4C61746Ellu, // li_Latn_NL
+    0x950B4E5044657661llu, // lif_Deva_NP
+    0x950B494E4C696D62llu, // lif_Limb_IN
+    0xA50B49544C61746Ellu, // lij_Latn_IT
+    0xC90B434E4C697375llu, // lis_Lisu_CN
+    0xBD2B49444C61746Ellu, // ljp_Latn_ID
+    0xA14B495241726162llu, // lki_Arab_IR
+    0xCD4B55534C61746Ellu, // lkt_Latn_US
+    0xB58B494E54656C75llu, // lmn_Telu_IN
+    0xB98B49544C61746Ellu, // lmo_Latn_IT
+    0x6C6E43444C61746Ellu, // ln_Latn_CD
+    0x6C6F4C414C616F6Fllu, // lo_Laoo_LA
+    0xADCB43444C61746Ellu, // lol_Latn_CD
+    0xE5CB5A4D4C61746Ellu, // loz_Latn_ZM
+    0x8A2B495241726162llu, // lrc_Arab_IR
+    0x6C744C544C61746Ellu, // lt_Latn_LT
+    0x9A6B4C564C61746Ellu, // ltg_Latn_LV
+    0x6C7543444C61746Ellu, // lu_Latn_CD
+    0x828B43444C61746Ellu, // lua_Latn_CD
+    0xBA8B4B454C61746Ellu, // luo_Latn_KE
+    0xE28B4B454C61746Ellu, // luy_Latn_KE
+    0xE68B495241726162llu, // luz_Arab_IR
+    0x6C764C564C61746Ellu, // lv_Latn_LV
+    0xAECB544854686169llu, // lwl_Thai_TH
+    0x9F2B434E48616E73llu, // lzh_Hans_CN
+    0xE72B54524C61746Ellu, // lzz_Latn_TR
+    0x8C0C49444C61746Ellu, // mad_Latn_ID
+    0x940C434D4C61746Ellu, // maf_Latn_CM
+    0x980C494E44657661llu, // mag_Deva_IN
+    0xA00C494E44657661llu, // mai_Deva_IN
+    0xA80C49444C61746Ellu, // mak_Latn_ID
+    0xB40C474D4C61746Ellu, // man_Latn_GM
+    0xB40C474E4E6B6F6Fllu, // man_Nkoo_GN
+    0xC80C4B454C61746Ellu, // mas_Latn_KE
+    0xE40C4D584C61746Ellu, // maz_Latn_MX
+    0x946C52554379726Cllu, // mdf_Cyrl_RU
+    0x9C6C50484C61746Ellu, // mdh_Latn_PH
+    0xC46C49444C61746Ellu, // mdr_Latn_ID
+    0xB48C534C4C61746Ellu, // men_Latn_SL
+    0xC48C4B454C61746Ellu, // mer_Latn_KE
+    0x80AC544841726162llu, // mfa_Arab_TH
+    0x90AC4D554C61746Ellu, // mfe_Latn_MU
+    0x6D674D474C61746Ellu, // mg_Latn_MG
+    0x9CCC4D5A4C61746Ellu, // mgh_Latn_MZ
+    0xB8CC434D4C61746Ellu, // mgo_Latn_CM
+    0xBCCC4E5044657661llu, // mgp_Deva_NP
+    0xE0CC545A4C61746Ellu, // mgy_Latn_TZ
+    0x6D684D484C61746Ellu, // mh_Latn_MH
+    0x6D694E5A4C61746Ellu, // mi_Latn_NZ
+    0xB50C49444C61746Ellu, // min_Latn_ID
+    0xC90C495148617472llu, // mis_Hatr_IQ
+    0x6D6B4D4B4379726Cllu, // mk_Cyrl_MK
+    0x6D6C494E4D6C796Dllu, // ml_Mlym_IN
+    0xC96C53444C61746Ellu, // mls_Latn_SD
+    0x6D6E4D4E4379726Cllu, // mn_Cyrl_MN
+    0x6D6E434E4D6F6E67llu, // mn_Mong_CN
+    0xA1AC494E42656E67llu, // mni_Beng_IN
+    0xD9AC4D4D4D796D72llu, // mnw_Mymr_MM
+    0x91CC43414C61746Ellu, // moe_Latn_CA
+    0x9DCC43414C61746Ellu, // moh_Latn_CA
+    0xC9CC42464C61746Ellu, // mos_Latn_BF
+    0x6D72494E44657661llu, // mr_Deva_IN
+    0x8E2C4E5044657661llu, // mrd_Deva_NP
+    0xA62C52554379726Cllu, // mrj_Cyrl_RU
+    0xD22C42444D726F6Fllu, // mru_Mroo_BD
+    0x6D734D594C61746Ellu, // ms_Latn_MY
+    0x6D744D544C61746Ellu, // mt_Latn_MT
+    0xC66C494E44657661llu, // mtr_Deva_IN
+    0x828C434D4C61746Ellu, // mua_Latn_CM
+    0xCA8C55534C61746Ellu, // mus_Latn_US
+    0xE2AC504B41726162llu, // mvy_Arab_PK
+    0xAACC4D4C4C61746Ellu, // mwk_Latn_ML
+    0xC6CC494E44657661llu, // mwr_Deva_IN
+    0xD6CC49444C61746Ellu, // mwv_Latn_ID
+    0x8AEC5A574C61746Ellu, // mxc_Latn_ZW
+    0x6D794D4D4D796D72llu, // my_Mymr_MM
+    0xD70C52554379726Cllu, // myv_Cyrl_RU
+    0xDF0C55474C61746Ellu, // myx_Latn_UG
+    0xE70C49524D616E64llu, // myz_Mand_IR
+    0xB72C495241726162llu, // mzn_Arab_IR
+    0x6E614E524C61746Ellu, // na_Latn_NR
+    0xB40D434E48616E73llu, // nan_Hans_CN
+    0xBC0D49544C61746Ellu, // nap_Latn_IT
+    0xC00D4E414C61746Ellu, // naq_Latn_NA
+    0x6E624E4F4C61746Ellu, // nb_Latn_NO
+    0x9C4D4D584C61746Ellu, // nch_Latn_MX
+    0x6E645A574C61746Ellu, // nd_Latn_ZW
+    0x886D4D5A4C61746Ellu, // ndc_Latn_MZ
+    0xC86D44454C61746Ellu, // nds_Latn_DE
+    0x6E654E5044657661llu, // ne_Deva_NP
+    0xD88D4E5044657661llu, // new_Deva_NP
+    0x6E674E414C61746Ellu, // ng_Latn_NA
+    0xACCD4D5A4C61746Ellu, // ngl_Latn_MZ
+    0x90ED4D584C61746Ellu, // nhe_Latn_MX
+    0xD8ED4D584C61746Ellu, // nhw_Latn_MX
+    0xA50D49444C61746Ellu, // nij_Latn_ID
+    0xD10D4E554C61746Ellu, // niu_Latn_NU
+    0xB92D494E4C61746Ellu, // njo_Latn_IN
+    0x6E6C4E4C4C61746Ellu, // nl_Latn_NL
+    0x998D434D4C61746Ellu, // nmg_Latn_CM
+    0x6E6E4E4F4C61746Ellu, // nn_Latn_NO
+    0x9DAD434D4C61746Ellu, // nnh_Latn_CM
+    0x6E6F4E4F4C61746Ellu, // no_Latn_NO
+    0x8DCD54484C616E61llu, // nod_Lana_TH
+    0x91CD494E44657661llu, // noe_Deva_IN
+    0xB5CD534552756E72llu, // non_Runr_SE
+    0xBA0D474E4E6B6F6Fllu, // nqo_Nkoo_GN
+    0x6E725A414C61746Ellu, // nr_Latn_ZA
+    0xAA4D434143616E73llu, // nsk_Cans_CA
+    0xBA4D5A414C61746Ellu, // nso_Latn_ZA
+    0xCA8D53534C61746Ellu, // nus_Latn_SS
+    0x6E7655534C61746Ellu, // nv_Latn_US
+    0xC2ED434E4C61746Ellu, // nxq_Latn_CN
+    0x6E794D574C61746Ellu, // ny_Latn_MW
+    0xB30D545A4C61746Ellu, // nym_Latn_TZ
+    0xB70D55474C61746Ellu, // nyn_Latn_UG
+    0xA32D47484C61746Ellu, // nzi_Latn_GH
+    0x6F6346524C61746Ellu, // oc_Latn_FR
+    0x6F6D45544C61746Ellu, // om_Latn_ET
+    0x6F72494E4F727961llu, // or_Orya_IN
+    0x6F7347454379726Cllu, // os_Cyrl_GE
+    0xAA6E4D4E4F726B68llu, // otk_Orkh_MN
+    0x7061504B41726162llu, // pa_Arab_PK
+    0x7061494E47757275llu, // pa_Guru_IN
+    0x980F50484C61746Ellu, // pag_Latn_PH
+    0xAC0F495250686C69llu, // pal_Phli_IR
+    0xAC0F434E50686C70llu, // pal_Phlp_CN
+    0xB00F50484C61746Ellu, // pam_Latn_PH
+    0xBC0F41574C61746Ellu, // pap_Latn_AW
+    0xD00F50574C61746Ellu, // pau_Latn_PW
+    0x8C4F46524C61746Ellu, // pcd_Latn_FR
+    0xB04F4E474C61746Ellu, // pcm_Latn_NG
+    0x886F55534C61746Ellu, // pdc_Latn_US
+    0xCC6F43414C61746Ellu, // pdt_Latn_CA
+    0xB88F49525870656Fllu, // peo_Xpeo_IR
+    0xACAF44454C61746Ellu, // pfl_Latn_DE
+    0xB4EF4C4250686E78llu, // phn_Phnx_LB
+    0x814F494E42726168llu, // pka_Brah_IN
+    0xB94F4B454C61746Ellu, // pko_Latn_KE
+    0x706C504C4C61746Ellu, // pl_Latn_PL
+    0xC98F49544C61746Ellu, // pms_Latn_IT
+    0xCDAF47524772656Bllu, // pnt_Grek_GR
+    0xB5CF464D4C61746Ellu, // pon_Latn_FM
+    0x822F504B4B686172llu, // pra_Khar_PK
+    0x8E2F495241726162llu, // prd_Arab_IR
+    0x7073414641726162llu, // ps_Arab_AF
+    0x707442524C61746Ellu, // pt_Latn_BR
+    0xD28F47414C61746Ellu, // puu_Latn_GA
+    0x717550454C61746Ellu, // qu_Latn_PE
+    0x8A9047544C61746Ellu, // quc_Latn_GT
+    0x9A9045434C61746Ellu, // qug_Latn_EC
+    0xA411494E44657661llu, // raj_Deva_IN
+    0x945152454C61746Ellu, // rcf_Latn_RE
+    0xA49149444C61746Ellu, // rej_Latn_ID
+    0xB4D149544C61746Ellu, // rgn_Latn_IT
+    0x8111494E4C61746Ellu, // ria_Latn_IN
+    0x95114D4154666E67llu, // rif_Tfng_MA
+    0xC9314E5044657661llu, // rjs_Deva_NP
+    0xCD51424442656E67llu, // rkt_Beng_BD
+    0x726D43484C61746Ellu, // rm_Latn_CH
+    0x959146494C61746Ellu, // rmf_Latn_FI
+    0xB99143484C61746Ellu, // rmo_Latn_CH
+    0xCD91495241726162llu, // rmt_Arab_IR
+    0xD19153454C61746Ellu, // rmu_Latn_SE
+    0x726E42494C61746Ellu, // rn_Latn_BI
+    0x99B14D5A4C61746Ellu, // rng_Latn_MZ
+    0x726F524F4C61746Ellu, // ro_Latn_RO
+    0x85D149444C61746Ellu, // rob_Latn_ID
+    0x95D1545A4C61746Ellu, // rof_Latn_TZ
+    0xB271464A4C61746Ellu, // rtm_Latn_FJ
+    0x727552554379726Cllu, // ru_Cyrl_RU
+    0x929155414379726Cllu, // rue_Cyrl_UA
+    0x9A9153424C61746Ellu, // rug_Latn_SB
+    0x727752574C61746Ellu, // rw_Latn_RW
+    0xAAD1545A4C61746Ellu, // rwk_Latn_TZ
+    0xD3114A504B616E61llu, // ryu_Kana_JP
+    0x7361494E44657661llu, // sa_Deva_IN
+    0x941247484C61746Ellu, // saf_Latn_GH
+    0x9C1252554379726Cllu, // sah_Cyrl_RU
+    0xC0124B454C61746Ellu, // saq_Latn_KE
+    0xC81249444C61746Ellu, // sas_Latn_ID
+    0xCC12494E4C61746Ellu, // sat_Latn_IN
+    0xE412494E53617572llu, // saz_Saur_IN
+    0xBC32545A4C61746Ellu, // sbp_Latn_TZ
+    0x736349544C61746Ellu, // sc_Latn_IT
+    0xA852494E44657661llu, // sck_Deva_IN
+    0xB45249544C61746Ellu, // scn_Latn_IT
+    0xB85247424C61746Ellu, // sco_Latn_GB
+    0xC85243414C61746Ellu, // scs_Latn_CA
+    0x7364504B41726162llu, // sd_Arab_PK
+    0x7364494E44657661llu, // sd_Deva_IN
+    0x7364494E4B686F6Allu, // sd_Khoj_IN
+    0x7364494E53696E64llu, // sd_Sind_IN
+    0x887249544C61746Ellu, // sdc_Latn_IT
+    0x9C72495241726162llu, // sdh_Arab_IR
+    0x73654E4F4C61746Ellu, // se_Latn_NO
+    0x949243494C61746Ellu, // sef_Latn_CI
+    0x9C924D5A4C61746Ellu, // seh_Latn_MZ
+    0xA0924D584C61746Ellu, // sei_Latn_MX
+    0xC8924D4C4C61746Ellu, // ses_Latn_ML
+    0x736743464C61746Ellu, // sg_Latn_CF
+    0x80D249454F67616Dllu, // sga_Ogam_IE
+    0xC8D24C544C61746Ellu, // sgs_Latn_LT
+    0xA0F24D4154666E67llu, // shi_Tfng_MA
+    0xB4F24D4D4D796D72llu, // shn_Mymr_MM
+    0x73694C4B53696E68llu, // si_Sinh_LK
+    0x8D1245544C61746Ellu, // sid_Latn_ET
+    0x736B534B4C61746Ellu, // sk_Latn_SK
+    0xC552504B41726162llu, // skr_Arab_PK
+    0x736C53494C61746Ellu, // sl_Latn_SI
+    0xA172504C4C61746Ellu, // sli_Latn_PL
+    0xE17249444C61746Ellu, // sly_Latn_ID
+    0x736D57534C61746Ellu, // sm_Latn_WS
+    0x819253454C61746Ellu, // sma_Latn_SE
+    0xA59253454C61746Ellu, // smj_Latn_SE
+    0xB59246494C61746Ellu, // smn_Latn_FI
+    0xBD92494C53616D72llu, // smp_Samr_IL
+    0xC99246494C61746Ellu, // sms_Latn_FI
+    0x736E5A574C61746Ellu, // sn_Latn_ZW
+    0xA9B24D4C4C61746Ellu, // snk_Latn_ML
+    0x736F534F4C61746Ellu, // so_Latn_SO
+    0xD1D2544854686169llu, // sou_Thai_TH
+    0x7371414C4C61746Ellu, // sq_Latn_AL
+    0x737252534379726Cllu, // sr_Cyrl_RS
+    0x737252534C61746Ellu, // sr_Latn_RS
+    0x8632494E536F7261llu, // srb_Sora_IN
+    0xB63253524C61746Ellu, // srn_Latn_SR
+    0xC632534E4C61746Ellu, // srr_Latn_SN
+    0xDE32494E44657661llu, // srx_Deva_IN
+    0x73735A414C61746Ellu, // ss_Latn_ZA
+    0xE25245524C61746Ellu, // ssy_Latn_ER
+    0x73745A414C61746Ellu, // st_Latn_ZA
+    0xC27244454C61746Ellu, // stq_Latn_DE
+    0x737549444C61746Ellu, // su_Latn_ID
+    0xAA92545A4C61746Ellu, // suk_Latn_TZ
+    0xCA92474E4C61746Ellu, // sus_Latn_GN
+    0x737653454C61746Ellu, // sv_Latn_SE
+    0x7377545A4C61746Ellu, // sw_Latn_TZ
+    0x86D2595441726162llu, // swb_Arab_YT
+    0x8AD243444C61746Ellu, // swc_Latn_CD
+    0x9AD244454C61746Ellu, // swg_Latn_DE
+    0xD6D2494E44657661llu, // swv_Deva_IN
+    0xB6F249444C61746Ellu, // sxn_Latn_ID
+    0xAF12424442656E67llu, // syl_Beng_BD
+    0xC712495153797263llu, // syr_Syrc_IQ
+    0xAF32504C4C61746Ellu, // szl_Latn_PL
+    0x7461494E54616D6Cllu, // ta_Taml_IN
+    0xA4134E5044657661llu, // taj_Deva_NP
+    0xD83350484C61746Ellu, // tbw_Latn_PH
+    0xE053494E4B6E6461llu, // tcy_Knda_IN
+    0x8C73434E54616C65llu, // tdd_Tale_CN
+    0x98734E5044657661llu, // tdg_Deva_NP
+    0x9C734E5044657661llu, // tdh_Deva_NP
+    0x7465494E54656C75llu, // te_Telu_IN
+    0xB093534C4C61746Ellu, // tem_Latn_SL
+    0xB89355474C61746Ellu, // teo_Latn_UG
+    0xCC93544C4C61746Ellu, // tet_Latn_TL
+    0x7467504B41726162llu, // tg_Arab_PK
+    0x7467544A4379726Cllu, // tg_Cyrl_TJ
+    0x7468544854686169llu, // th_Thai_TH
+    0xACF34E5044657661llu, // thl_Deva_NP
+    0xC0F34E5044657661llu, // thq_Deva_NP
+    0xC4F34E5044657661llu, // thr_Deva_NP
+    0x7469455445746869llu, // ti_Ethi_ET
+    0x9913455245746869llu, // tig_Ethi_ER
+    0xD5134E474C61746Ellu, // tiv_Latn_NG
+    0x746B544D4C61746Ellu, // tk_Latn_TM
+    0xAD53544B4C61746Ellu, // tkl_Latn_TK
+    0xC553415A4C61746Ellu, // tkr_Latn_AZ
+    0xCD534E5044657661llu, // tkt_Deva_NP
+    0x746C50484C61746Ellu, // tl_Latn_PH
+    0xE173415A4C61746Ellu, // tly_Latn_AZ
+    0x9D934E454C61746Ellu, // tmh_Latn_NE
+    0x746E5A414C61746Ellu, // tn_Latn_ZA
+    0x746F544F4C61746Ellu, // to_Latn_TO
+    0x99D34D574C61746Ellu, // tog_Latn_MW
+    0xA1F350474C61746Ellu, // tpi_Latn_PG
+    0x747254524C61746Ellu, // tr_Latn_TR
+    0xD23354524C61746Ellu, // tru_Latn_TR
+    0xD63354574C61746Ellu, // trv_Latn_TW
+    0x74735A414C61746Ellu, // ts_Latn_ZA
+    0x8E5347524772656Bllu, // tsd_Grek_GR
+    0x96534E5044657661llu, // tsf_Deva_NP
+    0x9A5350484C61746Ellu, // tsg_Latn_PH
+    0xA653425454696274llu, // tsj_Tibt_BT
+    0x747452554379726Cllu, // tt_Cyrl_RU
+    0xA67355474C61746Ellu, // ttj_Latn_UG
+    0xCA73544854686169llu, // tts_Thai_TH
+    0xCE73415A4C61746Ellu, // ttt_Latn_AZ
+    0xB2934D574C61746Ellu, // tum_Latn_MW
+    0xAEB354564C61746Ellu, // tvl_Latn_TV
+    0xC2D34E454C61746Ellu, // twq_Latn_NE
+    0x747950464C61746Ellu, // ty_Latn_PF
+    0xD71352554379726Cllu, // tyv_Cyrl_RU
+    0xB3334D414C61746Ellu, // tzm_Latn_MA
+    0xB07452554379726Cllu, // udm_Cyrl_RU
+    0x7567434E41726162llu, // ug_Arab_CN
+    0x75674B5A4379726Cllu, // ug_Cyrl_KZ
+    0x80D4535955676172llu, // uga_Ugar_SY
+    0x756B55414379726Cllu, // uk_Cyrl_UA
+    0xA174464D4C61746Ellu, // uli_Latn_FM
+    0x8594414F4C61746Ellu, // umb_Latn_AO
+    0xC5B4494E42656E67llu, // unr_Beng_IN
+    0xC5B44E5044657661llu, // unr_Deva_NP
+    0xDDB4494E42656E67llu, // unx_Beng_IN
+    0x7572504B41726162llu, // ur_Arab_PK
+    0x757A414641726162llu, // uz_Arab_AF
+    0x757A555A4C61746Ellu, // uz_Latn_UZ
+    0xA0154C5256616969llu, // vai_Vaii_LR
+    0x76655A414C61746Ellu, // ve_Latn_ZA
+    0x889549544C61746Ellu, // vec_Latn_IT
+    0xBC9552554C61746Ellu, // vep_Latn_RU
+    0x7669564E4C61746Ellu, // vi_Latn_VN
+    0x891553584C61746Ellu, // vic_Latn_SX
+    0xC97542454C61746Ellu, // vls_Latn_BE
+    0x959544454C61746Ellu, // vmf_Latn_DE
+    0xD9954D5A4C61746Ellu, // vmw_Latn_MZ
+    0xCDD552554C61746Ellu, // vot_Latn_RU
+    0xBA3545454C61746Ellu, // vro_Latn_EE
+    0xB695545A4C61746Ellu, // vun_Latn_TZ
+    0x776142454C61746Ellu, // wa_Latn_BE
+    0x901643484C61746Ellu, // wae_Latn_CH
+    0xAC16455445746869llu, // wal_Ethi_ET
+    0xC41650484C61746Ellu, // war_Latn_PH
+    0xBC3641554C61746Ellu, // wbp_Latn_AU
+    0xC036494E54656C75llu, // wbq_Telu_IN
+    0xC436494E44657661llu, // wbr_Deva_IN
+    0xC97657464C61746Ellu, // wls_Latn_WF
+    0xA1B64B4D41726162llu, // wni_Arab_KM
+    0x776F534E4C61746Ellu, // wo_Latn_SN
+    0xB276494E44657661llu, // wtm_Deva_IN
+    0xD296434E48616E73llu, // wuu_Hans_CN
+    0xD41742524C61746Ellu, // xav_Latn_BR
+    0xC457545243617269llu, // xcr_Cari_TR
+    0x78685A414C61746Ellu, // xh_Latn_ZA
+    0x897754524C796369llu, // xlc_Lyci_TR
+    0x8D7754524C796469llu, // xld_Lydi_TR
+    0x9597474547656F72llu, // xmf_Geor_GE
+    0xB597434E4D616E69llu, // xmn_Mani_CN
+    0xC59753444D657263llu, // xmr_Merc_SD
+    0x81B753414E617262llu, // xna_Narb_SA
+    0xC5B7494E44657661llu, // xnr_Deva_IN
+    0x99D755474C61746Ellu, // xog_Latn_UG
+    0xC5F7495250727469llu, // xpr_Prti_IR
+    0x8257594553617262llu, // xsa_Sarb_YE
+    0xC6574E5044657661llu, // xsr_Deva_NP
+    0xB8184D5A4C61746Ellu, // yao_Latn_MZ
+    0xBC18464D4C61746Ellu, // yap_Latn_FM
+    0xD418434D4C61746Ellu, // yav_Latn_CM
+    0x8438434D4C61746Ellu, // ybb_Latn_CM
+    0x796F4E474C61746Ellu, // yo_Latn_NG
+    0xAE3842524C61746Ellu, // yrl_Latn_BR
+    0x82984D584C61746Ellu, // yua_Latn_MX
+    0x7A61434E4C61746Ellu, // za_Latn_CN
+    0x981953444C61746Ellu, // zag_Latn_SD
+    0xA4794B4D41726162llu, // zdj_Arab_KM
+    0x80994E4C4C61746Ellu, // zea_Latn_NL
+    0x9CD94D4154666E67llu, // zgh_Tfng_MA
+    0x7A685457426F706Fllu, // zh_Bopo_TW
+    0x7A68434E48616E73llu, // zh_Hans_CN
+    0x7A68545748616E74llu, // zh_Hant_TW
+    0xA1994D594C61746Ellu, // zmi_Latn_MY
+    0x7A755A414C61746Ellu, // zu_Latn_ZA
+    0x833954524C61746Ellu, // zza_Latn_TR
+});
+
+const std::unordered_map<uint32_t, uint32_t> ARAB_PARENTS({
+    {0x6172445Au, 0x61729420u}, // ar-DZ -> ar-015
+    {0x61724548u, 0x61729420u}, // ar-EH -> ar-015
+    {0x61724C59u, 0x61729420u}, // ar-LY -> ar-015
+    {0x61724D41u, 0x61729420u}, // ar-MA -> ar-015
+    {0x6172544Eu, 0x61729420u}, // ar-TN -> ar-015
+});
+
+const std::unordered_map<uint32_t, uint32_t> HANT_PARENTS({
+    {0x7A684D4Fu, 0x7A68484Bu}, // zh-Hant-MO -> zh-Hant-HK
+});
+
+const std::unordered_map<uint32_t, uint32_t> LATN_PARENTS({
+    {0x656E80A1u, 0x656E8400u}, // en-150 -> en-001
+    {0x656E4147u, 0x656E8400u}, // en-AG -> en-001
+    {0x656E4149u, 0x656E8400u}, // en-AI -> en-001
+    {0x656E4154u, 0x656E80A1u}, // en-AT -> en-150
+    {0x656E4155u, 0x656E8400u}, // en-AU -> en-001
+    {0x656E4242u, 0x656E8400u}, // en-BB -> en-001
+    {0x656E4245u, 0x656E8400u}, // en-BE -> en-001
+    {0x656E424Du, 0x656E8400u}, // en-BM -> en-001
+    {0x656E4253u, 0x656E8400u}, // en-BS -> en-001
+    {0x656E4257u, 0x656E8400u}, // en-BW -> en-001
+    {0x656E425Au, 0x656E8400u}, // en-BZ -> en-001
+    {0x656E4341u, 0x656E8400u}, // en-CA -> en-001
+    {0x656E4343u, 0x656E8400u}, // en-CC -> en-001
+    {0x656E4348u, 0x656E80A1u}, // en-CH -> en-150
+    {0x656E434Bu, 0x656E8400u}, // en-CK -> en-001
+    {0x656E434Du, 0x656E8400u}, // en-CM -> en-001
+    {0x656E4358u, 0x656E8400u}, // en-CX -> en-001
+    {0x656E4359u, 0x656E8400u}, // en-CY -> en-001
+    {0x656E4445u, 0x656E80A1u}, // en-DE -> en-150
+    {0x656E4447u, 0x656E8400u}, // en-DG -> en-001
+    {0x656E444Bu, 0x656E80A1u}, // en-DK -> en-150
+    {0x656E444Du, 0x656E8400u}, // en-DM -> en-001
+    {0x656E4552u, 0x656E8400u}, // en-ER -> en-001
+    {0x656E4649u, 0x656E80A1u}, // en-FI -> en-150
+    {0x656E464Au, 0x656E8400u}, // en-FJ -> en-001
+    {0x656E464Bu, 0x656E8400u}, // en-FK -> en-001
+    {0x656E464Du, 0x656E8400u}, // en-FM -> en-001
+    {0x656E4742u, 0x656E8400u}, // en-GB -> en-001
+    {0x656E4744u, 0x656E8400u}, // en-GD -> en-001
+    {0x656E4747u, 0x656E8400u}, // en-GG -> en-001
+    {0x656E4748u, 0x656E8400u}, // en-GH -> en-001
+    {0x656E4749u, 0x656E8400u}, // en-GI -> en-001
+    {0x656E474Du, 0x656E8400u}, // en-GM -> en-001
+    {0x656E4759u, 0x656E8400u}, // en-GY -> en-001
+    {0x656E484Bu, 0x656E8400u}, // en-HK -> en-001
+    {0x656E4945u, 0x656E8400u}, // en-IE -> en-001
+    {0x656E494Cu, 0x656E8400u}, // en-IL -> en-001
+    {0x656E494Du, 0x656E8400u}, // en-IM -> en-001
+    {0x656E494Eu, 0x656E8400u}, // en-IN -> en-001
+    {0x656E494Fu, 0x656E8400u}, // en-IO -> en-001
+    {0x656E4A45u, 0x656E8400u}, // en-JE -> en-001
+    {0x656E4A4Du, 0x656E8400u}, // en-JM -> en-001
+    {0x656E4B45u, 0x656E8400u}, // en-KE -> en-001
+    {0x656E4B49u, 0x656E8400u}, // en-KI -> en-001
+    {0x656E4B4Eu, 0x656E8400u}, // en-KN -> en-001
+    {0x656E4B59u, 0x656E8400u}, // en-KY -> en-001
+    {0x656E4C43u, 0x656E8400u}, // en-LC -> en-001
+    {0x656E4C52u, 0x656E8400u}, // en-LR -> en-001
+    {0x656E4C53u, 0x656E8400u}, // en-LS -> en-001
+    {0x656E4D47u, 0x656E8400u}, // en-MG -> en-001
+    {0x656E4D4Fu, 0x656E8400u}, // en-MO -> en-001
+    {0x656E4D53u, 0x656E8400u}, // en-MS -> en-001
+    {0x656E4D54u, 0x656E8400u}, // en-MT -> en-001
+    {0x656E4D55u, 0x656E8400u}, // en-MU -> en-001
+    {0x656E4D57u, 0x656E8400u}, // en-MW -> en-001
+    {0x656E4D59u, 0x656E8400u}, // en-MY -> en-001
+    {0x656E4E41u, 0x656E8400u}, // en-NA -> en-001
+    {0x656E4E46u, 0x656E8400u}, // en-NF -> en-001
+    {0x656E4E47u, 0x656E8400u}, // en-NG -> en-001
+    {0x656E4E4Cu, 0x656E80A1u}, // en-NL -> en-150
+    {0x656E4E52u, 0x656E8400u}, // en-NR -> en-001
+    {0x656E4E55u, 0x656E8400u}, // en-NU -> en-001
+    {0x656E4E5Au, 0x656E8400u}, // en-NZ -> en-001
+    {0x656E5047u, 0x656E8400u}, // en-PG -> en-001
+    {0x656E5048u, 0x656E8400u}, // en-PH -> en-001
+    {0x656E504Bu, 0x656E8400u}, // en-PK -> en-001
+    {0x656E504Eu, 0x656E8400u}, // en-PN -> en-001
+    {0x656E5057u, 0x656E8400u}, // en-PW -> en-001
+    {0x656E5257u, 0x656E8400u}, // en-RW -> en-001
+    {0x656E5342u, 0x656E8400u}, // en-SB -> en-001
+    {0x656E5343u, 0x656E8400u}, // en-SC -> en-001
+    {0x656E5344u, 0x656E8400u}, // en-SD -> en-001
+    {0x656E5345u, 0x656E80A1u}, // en-SE -> en-150
+    {0x656E5347u, 0x656E8400u}, // en-SG -> en-001
+    {0x656E5348u, 0x656E8400u}, // en-SH -> en-001
+    {0x656E5349u, 0x656E80A1u}, // en-SI -> en-150
+    {0x656E534Cu, 0x656E8400u}, // en-SL -> en-001
+    {0x656E5353u, 0x656E8400u}, // en-SS -> en-001
+    {0x656E5358u, 0x656E8400u}, // en-SX -> en-001
+    {0x656E535Au, 0x656E8400u}, // en-SZ -> en-001
+    {0x656E5443u, 0x656E8400u}, // en-TC -> en-001
+    {0x656E544Bu, 0x656E8400u}, // en-TK -> en-001
+    {0x656E544Fu, 0x656E8400u}, // en-TO -> en-001
+    {0x656E5454u, 0x656E8400u}, // en-TT -> en-001
+    {0x656E5456u, 0x656E8400u}, // en-TV -> en-001
+    {0x656E545Au, 0x656E8400u}, // en-TZ -> en-001
+    {0x656E5547u, 0x656E8400u}, // en-UG -> en-001
+    {0x656E5643u, 0x656E8400u}, // en-VC -> en-001
+    {0x656E5647u, 0x656E8400u}, // en-VG -> en-001
+    {0x656E5655u, 0x656E8400u}, // en-VU -> en-001
+    {0x656E5753u, 0x656E8400u}, // en-WS -> en-001
+    {0x656E5A41u, 0x656E8400u}, // en-ZA -> en-001
+    {0x656E5A4Du, 0x656E8400u}, // en-ZM -> en-001
+    {0x656E5A57u, 0x656E8400u}, // en-ZW -> en-001
+    {0x65734152u, 0x6573A424u}, // es-AR -> es-419
+    {0x6573424Fu, 0x6573A424u}, // es-BO -> es-419
+    {0x6573434Cu, 0x6573A424u}, // es-CL -> es-419
+    {0x6573434Fu, 0x6573A424u}, // es-CO -> es-419
+    {0x65734352u, 0x6573A424u}, // es-CR -> es-419
+    {0x65734355u, 0x6573A424u}, // es-CU -> es-419
+    {0x6573444Fu, 0x6573A424u}, // es-DO -> es-419
+    {0x65734543u, 0x6573A424u}, // es-EC -> es-419
+    {0x65734754u, 0x6573A424u}, // es-GT -> es-419
+    {0x6573484Eu, 0x6573A424u}, // es-HN -> es-419
+    {0x65734D58u, 0x6573A424u}, // es-MX -> es-419
+    {0x65734E49u, 0x6573A424u}, // es-NI -> es-419
+    {0x65735041u, 0x6573A424u}, // es-PA -> es-419
+    {0x65735045u, 0x6573A424u}, // es-PE -> es-419
+    {0x65735052u, 0x6573A424u}, // es-PR -> es-419
+    {0x65735059u, 0x6573A424u}, // es-PY -> es-419
+    {0x65735356u, 0x6573A424u}, // es-SV -> es-419
+    {0x65735553u, 0x6573A424u}, // es-US -> es-419
+    {0x65735559u, 0x6573A424u}, // es-UY -> es-419
+    {0x65735645u, 0x6573A424u}, // es-VE -> es-419
+    {0x7074414Fu, 0x70745054u}, // pt-AO -> pt-PT
+    {0x70744356u, 0x70745054u}, // pt-CV -> pt-PT
+    {0x70744757u, 0x70745054u}, // pt-GW -> pt-PT
+    {0x70744D4Fu, 0x70745054u}, // pt-MO -> pt-PT
+    {0x70744D5Au, 0x70745054u}, // pt-MZ -> pt-PT
+    {0x70745354u, 0x70745054u}, // pt-ST -> pt-PT
+    {0x7074544Cu, 0x70745054u}, // pt-TL -> pt-PT
+});
+
+const struct {
+    const char script[4];
+    const std::unordered_map<uint32_t, uint32_t>* map;
+} SCRIPT_PARENTS[] = {
+    {{'A', 'r', 'a', 'b'}, &ARAB_PARENTS},
+    {{'H', 'a', 'n', 't'}, &HANT_PARENTS},
+    {{'L', 'a', 't', 'n'}, &LATN_PARENTS},
+};
+
+const size_t MAX_PARENT_DEPTH = 3;
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 44f92c7..71e9c92 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1868,7 +1868,10 @@
     }
 
     // The language & region are equal, so compare the scripts and variants.
-    int script = memcmp(l.localeScript, r.localeScript, sizeof(l.localeScript));
+    const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
+    const char *lScript = l.localeScriptWasProvided ? l.localeScript : emptyScript;
+    const char *rScript = r.localeScriptWasProvided ? r.localeScript : emptyScript;
+    int script = memcmp(lScript, rScript, sizeof(l.localeScript));
     if (script) {
         return script;
     }
@@ -2012,10 +2015,10 @@
     // scripts since it seems more useful to do so. We will consider
     // "en-US-POSIX" to be more specific than "en-Latn-US".
 
-    const int score = ((localeScript[0] != 0) ? 1 : 0) +
+    const int score = (localeScriptWasProvided ? 1 : 0) +
         ((localeVariant[0] != 0) ? 2 : 0);
 
-    const int oScore = ((o.localeScript[0] != 0) ? 1 : 0) +
+    const int oScore = (o.localeScriptWasProvided ? 1 : 0) +
         ((o.localeVariant[0] != 0) ? 2 : 0);
 
     return score - oScore;
@@ -2165,6 +2168,63 @@
     return false;
 }
 
+bool ResTable_config::isLocaleBetterThan(const ResTable_config& o,
+        const ResTable_config* requested) const {
+    if (requested->locale == 0) {
+        // The request doesn't have a locale, so no resource is better
+        // than the other.
+        return false;
+    }
+
+    if (locale == 0 && o.locale == 0) {
+        // The locales parts of both resources are empty, so no one is better
+        // than the other.
+        return false;
+    }
+
+    // Non-matching locales have been filtered out, so both resources
+    // match the requested locale.
+    //
+    // Because of the locale-related checks in match() and the checks, we know
+    // that:
+    // 1) The resource languages are either empty or match the request;
+    // and
+    // 2) If the request's script is known, the resource scripts are either
+    //    unknown or match the request.
+
+    if (language[0] != o.language[0]) {
+        // The languages of the two resources are not the same. We can only
+        // assume that one of the two resources matched the request because one
+        // doesn't have a language and the other has a matching language.
+        return (language[0] != 0);
+    }
+
+    // If we are here, both the resources have the same non-empty language as
+    // the request.
+    //
+    // Because the languages are the same, computeScript() always
+    // returns a non-empty script for languages it knows about, and we have passed
+    // the script checks in match(), the scripts are either all unknown or are
+    // all the same. So we can't gain anything by checking the scripts. We need
+    // to check the region and variant.
+
+    // See if any of the regions is better than the other
+    const int region_comparison = localeDataCompareRegions(
+            country, o.country,
+            language, localeScript, requested->country);
+    if (region_comparison != 0) {
+        return (region_comparison > 0);
+    }
+
+    // The regions are the same. Try the variant.
+    if (requested->localeVariant[0] != '\0'
+            && strncmp(localeVariant, requested->localeVariant, sizeof(localeVariant)) == 0) {
+        return (strncmp(o.localeVariant, requested->localeVariant, sizeof(localeVariant)) != 0);
+    }
+
+    return false;
+}
+
 bool ResTable_config::isBetterThan(const ResTable_config& o,
         const ResTable_config* requested) const {
     if (requested) {
@@ -2178,26 +2238,8 @@
             }
         }
 
-        if (locale || o.locale) {
-            if ((language[0] != o.language[0]) && requested->language[0]) {
-                return (language[0]);
-            }
-
-            if ((country[0] != o.country[0]) && requested->country[0]) {
-                return (country[0]);
-            }
-        }
-
-        if (localeScript[0] || o.localeScript[0]) {
-            if (localeScript[0] != o.localeScript[0] && requested->localeScript[0]) {
-                return localeScript[0];
-            }
-        }
-
-        if (localeVariant[0] || o.localeVariant[0]) {
-            if (localeVariant[0] != o.localeVariant[0] && requested->localeVariant[0]) {
-                return localeVariant[0];
-            }
+        if (isLocaleBetterThan(o, requested)) {
+            return true;
         }
 
         if (screenLayout || o.screenLayout) {
@@ -2445,20 +2487,33 @@
         }
     }
     if (locale != 0) {
-        // Don't consider the script & variants when deciding matches.
+        // Don't consider country and variants when deciding matches.
+        // (Theoretically, the variant can also affect the script. For
+        // example, "ar-alalc97" probably implies the Latin script, but since
+        // CLDR doesn't support getting likely scripts for that, we'll assume
+        // the variant doesn't change the script.)
         //
-        // If we two configs differ only in their script or language, they
-        // can be weeded out in the isMoreSpecificThan test.
-        if (language[0] != 0
-            && (language[0] != settings.language[0]
-                || language[1] != settings.language[1])) {
+        // If two configs differ only in their country and variant,
+        // they can be weeded out in the isMoreSpecificThan test.
+        if (language[0] != settings.language[0] || language[1] != settings.language[1]) {
             return false;
         }
 
-        if (country[0] != 0
-            && (country[0] != settings.country[0]
-                || country[1] != settings.country[1])) {
-            return false;
+        // For backward compatibility and supporting private-use locales, we
+        // fall back to old behavior if we couldn't determine the script for
+        // either of the desired locale or the provided locale.
+        if (localeScript[0] == '\0' || localeScript[1] == '\0') {
+            if (country[0] != '\0'
+                && (country[0] != settings.country[0]
+                    || country[1] != settings.country[1])) {
+                return false;
+            }
+        } else {
+            // But if we could determine the scripts, they should be the same
+            // for the locales to match.
+            if (memcmp(localeScript, settings.localeScript, sizeof(localeScript)) != 0) {
+                return false;
+            }
         }
     }
 
@@ -2587,7 +2642,7 @@
         return;
     }
 
-    if (!localeScript[0] && !localeVariant[0]) {
+    if (!localeScriptWasProvided && !localeVariant[0]) {
         // Legacy format.
         if (out.size() > 0) {
             out.append("-");
@@ -2605,7 +2660,7 @@
         return;
     }
 
-    // We are writing the modified bcp47 tag.
+    // We are writing the modified BCP 47 tag.
     // It starts with 'b+' and uses '+' as a separator.
 
     if (out.size() > 0) {
@@ -2617,7 +2672,7 @@
     size_t len = unpackLanguage(buf);
     out.append(buf, len);
 
-    if (localeScript[0]) {
+    if (localeScriptWasProvided) {
         out.append("+");
         out.append(localeScript, sizeof(localeScript));
     }
@@ -2630,7 +2685,7 @@
 
     if (localeVariant[0]) {
         out.append("+");
-        out.append(localeVariant, sizeof(localeVariant));
+        out.append(localeVariant, strnlen(localeVariant, sizeof(localeVariant)));
     }
 }
 
@@ -2648,7 +2703,7 @@
         charsWritten += unpackLanguage(str);
     }
 
-    if (localeScript[0]) {
+    if (localeScriptWasProvided) {
         if (charsWritten) {
             str[charsWritten++] = '-';
         }
@@ -2682,11 +2737,16 @@
            config->language[0] ? config->packRegion(start) : config->packLanguage(start);
            break;
        case 4:
-           config->localeScript[0] = toupper(start[0]);
-           for (size_t i = 1; i < 4; ++i) {
-               config->localeScript[i] = tolower(start[i]);
+           if ('0' <= start[0] && start[0] <= '9') {
+               // this is a variant, so fall through
+           } else {
+               config->localeScript[0] = toupper(start[0]);
+               for (size_t i = 1; i < 4; ++i) {
+                   config->localeScript[i] = tolower(start[i]);
+               }
+               config->localeScriptWasProvided = true;
+               break;
            }
-           break;
        case 5:
        case 6:
        case 7:
@@ -2704,6 +2764,7 @@
 
 void ResTable_config::setBcp47Locale(const char* in) {
     locale = 0;
+    localeScriptWasProvided = false;
     memset(localeScript, 0, sizeof(localeScript));
     memset(localeVariant, 0, sizeof(localeVariant));
 
@@ -2720,6 +2781,9 @@
 
     const size_t size = in + strlen(in) - start;
     assignLocaleComponent(this, start, size);
+    if (localeScript[0] == '\0') {
+        computeScript();
+    };
 }
 
 String8 ResTable_config::toString() const {
diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp
index 4999594..1941563 100644
--- a/libs/androidfw/tests/ConfigLocale_test.cpp
+++ b/libs/androidfw/tests/ConfigLocale_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <androidfw/LocaleData.h>
 #include <androidfw/ResourceTypes.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
@@ -28,7 +29,7 @@
      EXPECT_EQ('e', config.language[0]);
      EXPECT_EQ('n', config.language[1]);
 
-     char out[4] = { 1, 1, 1, 1};
+     char out[4] = {1, 1, 1, 1};
      config.unpackLanguage(out);
      EXPECT_EQ('e', out[0]);
      EXPECT_EQ('n', out[1]);
@@ -51,7 +52,7 @@
      EXPECT_EQ('U', config.country[0]);
      EXPECT_EQ('S', config.country[1]);
 
-     char out[4] = { 1, 1, 1, 1};
+     char out[4] = {1, 1, 1, 1};
      config.unpackRegion(out);
      EXPECT_EQ('U', out[0]);
      EXPECT_EQ('S', out[1]);
@@ -67,7 +68,7 @@
      EXPECT_EQ('\x99', config.language[0]);
      EXPECT_EQ('\xA4', config.language[1]);
 
-     char out[4] = { 1, 1, 1, 1};
+     char out[4] = {1, 1, 1, 1};
      config.unpackLanguage(out);
      EXPECT_EQ('e', out[0]);
      EXPECT_EQ('n', out[1]);
@@ -91,7 +92,7 @@
      EXPECT_EQ(char(0xbc), config.language[0]);
      EXPECT_EQ(char(0xd3), config.language[1]);
 
-     char out[4] = { 1, 1, 1, 1};
+     char out[4] = {1, 1, 1, 1};
      config.unpackLanguage(out);
      EXPECT_EQ('t', out[0]);
      EXPECT_EQ('g', out[1]);
@@ -103,7 +104,7 @@
      ResTable_config config;
      config.packRegion("419");
 
-     char out[4] = { 1, 1, 1, 1};
+     char out[4] = {1, 1, 1, 1};
      config.unpackRegion(out);
 
      EXPECT_EQ('4', out[0]);
@@ -124,6 +125,10 @@
 
      if (script != NULL) {
          memcpy(out->localeScript, script, 4);
+         out->localeScriptWasProvided = true;
+     } else {
+         out->computeScript();
+         out->localeScriptWasProvided = false;
      }
 
      if (variant != NULL) {
@@ -177,11 +182,12 @@
     EXPECT_EQ('n', test.language[1]);
     EXPECT_EQ('U', test.country[0]);
     EXPECT_EQ('S', test.country[1]);
-    EXPECT_EQ(0, test.localeScript[0]);
+    EXPECT_FALSE(test.localeScriptWasProvided);
+    EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
     EXPECT_EQ(0, test.localeVariant[0]);
 
     test.setBcp47Locale("eng-419");
-    char out[4] = { 1, 1, 1, 1};
+    char out[4] = {1, 1, 1, 1};
     test.unpackLanguage(out);
     EXPECT_EQ('e', out[0]);
     EXPECT_EQ('n', out[1]);
@@ -193,17 +199,397 @@
     EXPECT_EQ('1', out[1]);
     EXPECT_EQ('9', out[2]);
 
-
     test.setBcp47Locale("en-Latn-419");
-    memset(out, 1, 4);
     EXPECT_EQ('e', test.language[0]);
     EXPECT_EQ('n', test.language[1]);
-
     EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+    EXPECT_TRUE(test.localeScriptWasProvided);
+    memset(out, 1, 4);
     test.unpackRegion(out);
     EXPECT_EQ('4', out[0]);
     EXPECT_EQ('1', out[1]);
     EXPECT_EQ('9', out[2]);
+
+    test.setBcp47Locale("de-1901");
+    memset(out, 1, 4);
+    test.unpackLanguage(out);
+    EXPECT_EQ('d', out[0]);
+    EXPECT_EQ('e', out[1]);
+    EXPECT_EQ('\0', out[2]);
+    EXPECT_FALSE(test.localeScriptWasProvided);
+    EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+    memset(out, 1, 4);
+    test.unpackRegion(out);
+    EXPECT_EQ('\0', out[0]);
+    EXPECT_EQ(0, strcmp("1901", test.localeVariant));
+
+    test.setBcp47Locale("de-Latn-1901");
+    memset(out, 1, 4);
+    test.unpackLanguage(out);
+    EXPECT_EQ('d', out[0]);
+    EXPECT_EQ('e', out[1]);
+    EXPECT_EQ('\0', out[2]);
+    EXPECT_TRUE(test.localeScriptWasProvided);
+    EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+    memset(out, 1, 4);
+    test.unpackRegion(out);
+    EXPECT_EQ('\0', out[0]);
+    EXPECT_EQ(0, strcmp("1901", test.localeVariant));
 }
 
-}  // namespace android.
+TEST(ConfigLocaleTest, computeScript) {
+    ResTable_config config;
+
+    fillIn(NULL, NULL, NULL, NULL, &config);
+    EXPECT_EQ(0, memcmp("\0\0\0\0", config.localeScript, 4));
+
+    fillIn("zh", "TW", NULL, NULL, &config);
+    EXPECT_EQ(0, memcmp("Hant", config.localeScript, 4));
+
+    fillIn("zh", "CN", NULL, NULL, &config);
+    EXPECT_EQ(0, memcmp("Hans", config.localeScript, 4));
+
+    fillIn("az", NULL, NULL, NULL, &config);
+    EXPECT_EQ(0, memcmp("Latn", config.localeScript, 4));
+
+    fillIn("az", "AZ", NULL, NULL, &config);
+    EXPECT_EQ(0, memcmp("Latn", config.localeScript, 4));
+
+    fillIn("az", "IR", NULL, NULL, &config);
+    EXPECT_EQ(0, memcmp("Arab", config.localeScript, 4));
+
+    fillIn("peo", NULL, NULL, NULL, &config);
+    EXPECT_EQ(0, memcmp("Xpeo", config.localeScript, 4));
+
+    fillIn("qaa", NULL, NULL, NULL, &config);
+    EXPECT_EQ(0, memcmp("\0\0\0\0", config.localeScript, 4));
+}
+
+TEST(ConfigLocaleTest, getBcp47Locale_script) {
+    ResTable_config config;
+    fillIn("en", NULL, "Latn", NULL, &config);
+
+    char out[RESTABLE_MAX_LOCALE_LEN];
+    config.localeScriptWasProvided = true;
+    config.getBcp47Locale(out);
+    EXPECT_EQ(0, strcmp("en-Latn", out));
+
+    config.localeScriptWasProvided = false;
+    config.getBcp47Locale(out);
+    EXPECT_EQ(0, strcmp("en", out));
+}
+
+TEST(ConfigLocaleTest, match) {
+    ResTable_config supported, requested;
+
+    fillIn(NULL, NULL, NULL, NULL, &supported);
+    fillIn("fr", "CA", NULL, NULL, &requested);
+    // Empty locale matches everything (as a default).
+    EXPECT_TRUE(supported.match(requested));
+
+    fillIn("en", "CA", NULL, NULL, &supported);
+    fillIn("fr", "CA", NULL, NULL, &requested);
+    // Different languages don't match.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("qaa", "FR", NULL, NULL, &supported);
+    fillIn("qaa", "CA", NULL, NULL, &requested);
+    // If we can't infer the scripts, different regions don't match.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("qaa", "FR", "Latn", NULL, &supported);
+    fillIn("qaa", "CA", NULL, NULL, &requested);
+    // If we can't infer any of the scripts, different regions don't match.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("qaa", "FR", NULL, NULL, &supported);
+    fillIn("qaa", "CA", "Latn", NULL, &requested);
+    // If we can't infer any of the scripts, different regions don't match.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("qaa", NULL, NULL, NULL, &supported);
+    fillIn("qaa", "CA", NULL, NULL, &requested);
+    // language-only resources still support language+region requests, even if we can't infer the
+    // script.
+    EXPECT_TRUE(supported.match(requested));
+
+    fillIn("qaa", "CA", NULL, NULL, &supported);
+    fillIn("qaa", "CA", NULL, NULL, &requested);
+    // Even if we can't infer the scripts, exactly equal locales match.
+    EXPECT_TRUE(supported.match(requested));
+
+    fillIn("az", NULL, NULL, NULL, &supported);
+    fillIn("az", NULL, "Latn", NULL, &requested);
+    // If the resolved scripts are the same, it doesn't matter if they were explicitly provided
+    // or not, and they match.
+    EXPECT_TRUE(supported.match(requested));
+
+    fillIn("az", NULL, NULL, NULL, &supported);
+    fillIn("az", NULL, "Cyrl", NULL, &requested);
+    // If the resolved scripts are different, they don't match.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("az", NULL, NULL, NULL, &supported);
+    fillIn("az", "IR", NULL, NULL, &requested);
+    // If the resolved scripts are different, they don't match.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("az", "IR", NULL, NULL, &supported);
+    fillIn("az", NULL, "Arab", NULL, &requested);
+    // If the resolved scripts are the same, it doesn't matter if they were explicitly provided
+    // or not, and they match.
+    EXPECT_TRUE(supported.match(requested));
+
+    fillIn("en", NULL, NULL, NULL, &supported);
+    fillIn("en", "XA", NULL, NULL, &requested);
+    // en-XA is a pseudo-locale, and English resources are not a match for it.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("en", "XA", NULL, NULL, &supported);
+    fillIn("en", NULL, NULL, NULL, &requested);
+    // en-XA is a pseudo-locale, and its resources don't support English locales.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("en", "XA", NULL, NULL, &supported);
+    fillIn("en", "XA", NULL, NULL, &requested);
+    // Even if they are pseudo-locales, exactly equal locales match.
+    EXPECT_TRUE(supported.match(requested));
+
+    fillIn("ar", NULL, NULL, NULL, &supported);
+    fillIn("ar", "XB", NULL, NULL, &requested);
+    // ar-XB is a pseudo-locale, and Arabic resources are not a match for it.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("ar", "XB", NULL, NULL, &supported);
+    fillIn("ar", NULL, NULL, NULL, &requested);
+    // ar-XB is a pseudo-locale, and its resources don't support Arabic locales.
+    EXPECT_FALSE(supported.match(requested));
+
+    fillIn("ar", "XB", NULL, NULL, &supported);
+    fillIn("ar", "XB", NULL, NULL, &requested);
+    // Even if they are pseudo-locales, exactly equal locales match.
+    EXPECT_TRUE(supported.match(requested));
+}
+
+TEST(ConfigLocaleTest, isLocaleBetterThan_basics) {
+    ResTable_config config1, config2, request;
+
+    fillIn(NULL, NULL, NULL, NULL, &request);
+    fillIn("fr", "FR", NULL, NULL, &config1);
+    fillIn("fr", "CA", NULL, NULL, &config2);
+    EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("fr", "CA", NULL, NULL, &request);
+    fillIn(NULL, NULL, NULL, NULL, &config1);
+    fillIn(NULL, NULL, NULL, NULL, &config2);
+    EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("fr", "CA", NULL, NULL, &request);
+    fillIn("fr", "FR", NULL, NULL, &config1);
+    fillIn(NULL, NULL, NULL, NULL, &config2);
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("de", "DE", NULL, NULL, &request);
+    fillIn("de", "DE", NULL, "1901", &config1);
+    fillIn("de", "DE", NULL, "1996", &config2);
+    EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("de", "DE", NULL, "1901", &request);
+    fillIn("de", "DE", NULL, "1901", &config1);
+    fillIn("de", "DE", NULL, NULL, &config2);
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("de", "DE", NULL, "1901", &request);
+    fillIn("de", "DE", NULL, "1996", &config1);
+    fillIn("de", "DE", NULL, NULL, &config2);
+    EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+}
+
+TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) {
+    ResTable_config config1, config2, request;
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "419", NULL, NULL, &config1);
+    fillIn("es", "419", NULL, NULL, &config2);
+    // Both supported locales are the same, so none is better than the other.
+    EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "AR", NULL, NULL, &config1);
+    fillIn("es", "419", NULL, NULL, &config2);
+    // An exact locale match is better than a parent.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "419", NULL, NULL, &config1);
+    fillIn("es", NULL, NULL, NULL, &config2);
+    // A closer parent is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "419", NULL, NULL, &config1);
+    fillIn("es", "ES", NULL, NULL, &config2);
+    // A parent is better than a non-parent representative locale.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", NULL, NULL, NULL, &config1);
+    fillIn("es", "ES", NULL, NULL, &config2);
+    // A parent is better than a non-parent representative locale.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "PE", NULL, NULL, &config1);
+    fillIn("es", "ES", NULL, NULL, &config2);
+    // A closer locale is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "MX", NULL, NULL, &config1);
+    fillIn("es", "BO", NULL, NULL, &config2);
+    // A representative locale is better if they are equidistant.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "US", NULL, NULL, &config1);
+    fillIn("es", "BO", NULL, NULL, &config2);
+    // A representative locale is better if they are equidistant.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "MX", NULL, NULL, &config1);
+    fillIn("es", "US", NULL, NULL, &config2);
+    // If all is equal, the locale earlier in the dictionary is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "GQ", NULL, NULL, &request);
+    fillIn("es", "IC", NULL, NULL, &config1);
+    fillIn("es", "419", NULL, NULL, &config2);
+    // If all is equal, the locale earlier in the dictionary is better and
+    // letters are better than numbers.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("en", "GB", NULL, NULL, &request);
+    fillIn("en", "001", NULL, NULL, &config1);
+    fillIn("en", NULL, NULL, NULL, &config2);
+    // A closer parent is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("en", "PR", NULL, NULL, &request);
+    fillIn("en", NULL, NULL, NULL, &config1);
+    fillIn("en", "001", NULL, NULL, &config2);
+    // A parent is better than a non-parent.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("en", "DE", NULL, NULL, &request);
+    fillIn("en", "150", NULL, NULL, &config1);
+    fillIn("en", "001", NULL, NULL, &config2);
+    // A closer parent is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("en", "IN", NULL, NULL, &request);
+    fillIn("en", "AU", NULL, NULL, &config1);
+    fillIn("en", "US", NULL, NULL, &config2);
+    // A closer locale is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("en", "PR", NULL, NULL, &request);
+    fillIn("en", "001", NULL, NULL, &config1);
+    fillIn("en", "GB", NULL, NULL, &config2);
+    // A closer locale is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("en", "IN", NULL, NULL, &request);
+    fillIn("en", "GB", NULL, NULL, &config1);
+    fillIn("en", "AU", NULL, NULL, &config2);
+    // A representative locale is better if they are equidistant.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("en", "IN", NULL, NULL, &request);
+    fillIn("en", "AU", NULL, NULL, &config1);
+    fillIn("en", "CA", NULL, NULL, &config2);
+    // If all is equal, the locale earlier in the dictionary is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("pt", "MZ", NULL, NULL, &request);
+    fillIn("pt", "PT", NULL, NULL, &config1);
+    fillIn("pt", NULL, NULL, NULL, &config2);
+    // A closer parent is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("pt", "MZ", NULL, NULL, &request);
+    fillIn("pt", "PT", NULL, NULL, &config1);
+    fillIn("pt", "BR", NULL, NULL, &config2);
+    // A parent is better than a non-parent.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("zh", "MO", "Hant", NULL, &request);
+    fillIn("zh", "HK", "Hant", NULL, &config1);
+    fillIn("zh", "TW", "Hant", NULL, &config2);
+    // A parent is better than a non-parent.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("zh", "US", "Hant", NULL, &request);
+    fillIn("zh", "TW", "Hant", NULL, &config1);
+    fillIn("zh", "HK", "Hant", NULL, &config2);
+    // A representative locale is better if they are equidistant.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("ar", "DZ", NULL, NULL, &request);
+    fillIn("ar", "015", NULL, NULL, &config1);
+    fillIn("ar", NULL, NULL, NULL, &config2);
+    // A closer parent is better.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("ar", "EG", NULL, NULL, &request);
+    fillIn("ar", NULL, NULL, NULL, &config1);
+    fillIn("ar", "015", NULL, NULL, &config2);
+    // A parent is better than a non-parent.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("ar", "QA", NULL, NULL, &request);
+    fillIn("ar", "EG", NULL, NULL, &config1);
+    fillIn("ar", "BH", NULL, NULL, &config2);
+    // A representative locale is better if they are equidistant.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("ar", "QA", NULL, NULL, &request);
+    fillIn("ar", "SA", NULL, NULL, &config1);
+    fillIn("ar", "015", NULL, NULL, &config2);
+    // If all is equal, the locale earlier in the dictionary is better and
+    // letters are better than numbers.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+}
+
+}  // namespace android
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 1bfa308..db017fe 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1630,6 +1630,7 @@
 
     Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap);
     if (!texture) return;
+    const AutoTexture autoCleanup(texture);
 
     // 9 patches are built for stretching - always filter
     int textureFillFlags = TextureFillFlags::ForceFilter;
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index c5000e4c..5046d37 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -41,9 +41,7 @@
 void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force,
         GLenum renderTarget) {
 
-    if (mFirstWrap || force || wrapS != mWrapS || wrapT != mWrapT) {
-        mFirstWrap = false;
-
+    if (force || wrapS != mWrapS || wrapT != mWrapT) {
         mWrapS = wrapS;
         mWrapT = wrapT;
 
@@ -59,9 +57,7 @@
 void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force,
         GLenum renderTarget) {
 
-    if (mFirstFilter || force || min != mMinFilter || mag != mMagFilter) {
-        mFirstFilter = false;
-
+    if (force || min != mMinFilter || mag != mMagFilter) {
         mMinFilter = min;
         mMagFilter = mag;
 
@@ -92,14 +88,21 @@
     return true;
 }
 
+void Texture::resetCachedParams() {
+    mWrapS = GL_REPEAT;
+    mWrapT = GL_REPEAT;
+    mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
+    mMagFilter = GL_LINEAR;
+}
+
 void Texture::upload(GLint internalformat, uint32_t width, uint32_t height,
         GLenum format, GLenum type, const void* pixels) {
     GL_CHECKPOINT();
-    mCaches.textureState().activateTexture(0);
     bool needsAlloc = updateSize(width, height, internalformat);
     if (!mId) {
         glGenTextures(1, &mId);
         needsAlloc = true;
+        resetCachedParams();
     }
     mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId);
     if (needsAlloc) {
@@ -180,6 +183,10 @@
         *outFormat = GL_RGBA;
         *outType = GL_UNSIGNED_BYTE;
         break;
+    case kGray_8_SkColorType:
+        *outFormat = GL_LUMINANCE;
+        *outType = GL_UNSIGNED_BYTE;
+        break;
     default:
         LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType);
         break;
@@ -203,10 +210,12 @@
     // If the texture had mipmap enabled but not anymore,
     // force a glTexImage2D to discard the mipmap levels
     bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap();
+    bool setDefaultParams = false;
 
     if (!mId) {
         glGenTextures(1, &mId);
         needsAlloc = true;
+        setDefaultParams = true;
     }
 
     GLint format, type;
@@ -241,11 +250,8 @@
         }
     }
 
-    if (mFirstFilter) {
+    if (setDefaultParams) {
         setFilter(GL_NEAREST);
-    }
-
-    if (mFirstWrap) {
         setWrap(GL_CLAMP_TO_EDGE);
     }
 }
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 4e8e6dc..9749f73 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -149,26 +149,22 @@
 
     // Returns true if the size changed, false if it was the same
     bool updateSize(uint32_t width, uint32_t height, GLint format);
+    void resetCachedParams();
 
     GLuint mId = 0;
     uint32_t mWidth = 0;
     uint32_t mHeight = 0;
     GLint mFormat = 0;
 
-    /**
-     * Last wrap modes set on this texture.
+    /* See GLES spec section 3.8.14
+     * "In the initial state, the value assigned to TEXTURE_MIN_FILTER is
+     * NEAREST_MIPMAP_LINEAR and the value for TEXTURE_MAG_FILTER is LINEAR.
+     * s, t, and r wrap modes are all set to REPEAT."
      */
-    GLenum mWrapS = GL_CLAMP_TO_EDGE;
-    GLenum mWrapT = GL_CLAMP_TO_EDGE;
-
-    /**
-     * Last filters set on this texture.
-     */
-    GLenum mMinFilter = GL_NEAREST;
-    GLenum mMagFilter = GL_NEAREST;
-
-    bool mFirstFilter = true;
-    bool mFirstWrap = true;
+    GLenum mWrapS = GL_REPEAT;
+    GLenum mWrapT = GL_REPEAT;
+    GLenum mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
+    GLenum mMagFilter = GL_LINEAR;
 
     Caches& mCaches;
 }; // struct Texture
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
index 98c94df..54f38e8 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.cpp
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -41,6 +41,7 @@
         , texture(caches) {
     uint32_t width = computeIdealDimension(viewportWidth);
     uint32_t height = computeIdealDimension(viewportHeight);
+    caches.textureState().activateTexture(0);
     texture.resize(width, height, GL_RGBA);
     texture.blend = true;
     texture.setWrap(GL_CLAMP_TO_EDGE);
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index e71d6ee..75dcf16 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -340,6 +340,9 @@
     SkiaShader::apply(*mCaches, fill.skiaShaderData);
 
     GL_CHECKPOINT();
+    Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ?
+            fill.skiaShaderData.bitmapData.bitmapTexture : nullptr;
+    const AutoTexture autoCleanup(texture);
 
     // ------------------------------------
     // ---------- GL state setup ----------
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 5ad6b08..dc534be 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2163,7 +2163,7 @@
      * audio service.
      */
     private final ServiceEventHandlerDelegate mServiceEventHandlerDelegate =
-            new ServiceEventHandlerDelegate();
+            new ServiceEventHandlerDelegate(null);
 
     /**
      * Event types
@@ -2177,10 +2177,14 @@
     private class ServiceEventHandlerDelegate {
         private final Handler mHandler;
 
-        ServiceEventHandlerDelegate() {
+        ServiceEventHandlerDelegate(Handler handler) {
             Looper looper;
-            if ((looper = Looper.myLooper()) == null) {
-                looper = Looper.getMainLooper();
+            if (handler == null) {
+                if ((looper = Looper.myLooper()) == null) {
+                    looper = Looper.getMainLooper();
+                }
+            } else {
+                looper = handler.getLooper();
             }
 
             if (looper != null) {
@@ -2201,27 +2205,9 @@
                                 }
                                 break;
                             case MSSG_RECORDING_CONFIG_CHANGE:
-                                // optimizing for the case of a single callback
-                                AudioRecordingCallback singleCallback = null;
-                                ArrayList<AudioRecordingCallback> multipleCallbacks = null;
-                                synchronized(mRecordCallbackLock) {
-                                    if ((mRecordCallbackList != null)
-                                            && (mRecordCallbackList.size() != 0)) {
-                                        if (mRecordCallbackList.size() == 1) {
-                                            singleCallback = mRecordCallbackList.get(0);
-                                        } else {
-                                            multipleCallbacks =
-                                                    new ArrayList<AudioRecordingCallback>(
-                                                            mRecordCallbackList);
-                                        }
-                                    }
-                                }
-                                if (singleCallback != null) {
-                                    singleCallback.onRecordConfigChanged();
-                                } else if (multipleCallbacks != null) {
-                                    for (int i=0 ; i < multipleCallbacks.size() ; i++) {
-                                        multipleCallbacks.get(i).onRecordConfigChanged();
-                                    }
+                                final AudioRecordingCallback cb = (AudioRecordingCallback) msg.obj;
+                                if (cb != null) {
+                                    cb.onRecordConfigChanged();
                                 }
                                 break;
                             default:
@@ -2798,34 +2784,51 @@
     //====================================================================
     // Recording configuration
     /**
-     * @hide
-     * candidate for public API
+     * Interface for receiving update notifications about the recording configuration. Extend
+     * this abstract class and register it with
+     * {@link AudioManager#registerAudioRecordingCallback(AudioRecordingCallback, Handler)}
+     * to be notified.
+     * Use {@link AudioManager#getActiveRecordConfigurations()} to query the current configuration.
      */
     public static abstract class AudioRecordingCallback {
         /**
-         * @hide
-         * candidate for public API
+         * Called whenever the device recording configuration has changed.
          */
         public void onRecordConfigChanged() {}
     }
 
+    private static class AudioRecordingCallbackInfo {
+        final AudioRecordingCallback mCb;
+        final Handler mHandler;
+        AudioRecordingCallbackInfo(AudioRecordingCallback cb, Handler handler) {
+            mCb = cb;
+            mHandler = handler;
+        }
+    }
+
     /**
-     * @hide
-     * candidate for public API
-     * @param non-null callback
+     * Register a callback to be notified of audio recording changes through
+     * {@link AudioRecordingCallback}
+     * @param cb non-null callback to register
+     * @param handler the {@link Handler} object for the thread on which to execute
+     * the callback. If <code>null</code>, the {@link Handler} associated with the main
+     * {@link Looper} will be used.
      */
-    public void registerAudioRecordingCallback(@NonNull AudioRecordingCallback cb) {
+    public void registerAudioRecordingCallback(@NonNull AudioRecordingCallback cb, Handler handler)
+    {
         if (cb == null) {
             throw new IllegalArgumentException("Illegal null AudioRecordingCallback argument");
         }
+
         synchronized(mRecordCallbackLock) {
             // lazy initialization of the list of recording callbacks
             if (mRecordCallbackList == null) {
-                mRecordCallbackList = new ArrayList<AudioRecordingCallback>();
+                mRecordCallbackList = new ArrayList<AudioRecordingCallbackInfo>();
             }
             final int oldCbCount = mRecordCallbackList.size();
-            if (!mRecordCallbackList.contains(cb)) {
-                mRecordCallbackList.add(cb);
+            if (!hasRecordCallback_sync(cb)) {
+                mRecordCallbackList.add(new AudioRecordingCallbackInfo(cb,
+                        new ServiceEventHandlerDelegate(handler).getHandler()));
                 final int newCbCount = mRecordCallbackList.size();
                 if ((oldCbCount == 0) && (newCbCount > 0)) {
                     // register binder for callbacks
@@ -2844,9 +2847,9 @@
     }
 
     /**
-     * @hide
-     * candidate for public API
-     * @param non-null callback
+     * Unregister an audio recording callback previously registered with
+     * {@link #registerAudioRecordingCallback(AudioRecordingCallback, Handler)}.
+     * @param cb non-null callback to unregister
      */
     public void unregisterAudioRecordingCallback(@NonNull AudioRecordingCallback cb) {
         if (cb == null) {
@@ -2857,7 +2860,7 @@
                 return;
             }
             final int oldCbCount = mRecordCallbackList.size();
-            if (mRecordCallbackList.remove(cb)) {
+            if (removeRecordCallback_sync(cb)) {
                 final int newCbCount = mRecordCallbackList.size();
                 if ((oldCbCount > 0) && (newCbCount == 0)) {
                     // unregister binder for callbacks
@@ -2876,8 +2879,7 @@
     }
 
     /**
-     * @hide
-     * candidate for public API
+     * Returns the current active audio recording configurations of the device.
      * @return a non-null array of recording configurations. An array of length 0 indicates there is
      *     no recording active when queried.
      */
@@ -2902,18 +2904,57 @@
 
     /**
      * All operations on this list are sync'd on mRecordCallbackLock.
-     * List is lazy-initialized in {@link #registerAudioRecordingCallback(AudioRecordingCallback)}.
+     * List is lazy-initialized in
+     * {@link #registerAudioRecordingCallback(AudioRecordingCallback, Handler)}.
      * List can be null.
      */
-    private List<AudioRecordingCallback> mRecordCallbackList;
+    private List<AudioRecordingCallbackInfo> mRecordCallbackList;
     private final Object mRecordCallbackLock = new Object();
 
+    /**
+     * Must be called synchronized on mRecordCallbackLock
+     */
+    private boolean hasRecordCallback_sync(@NonNull AudioRecordingCallback cb) {
+        if (mRecordCallbackList != null) {
+            for (int i=0 ; i < mRecordCallbackList.size() ; i++) {
+                if (cb.equals(mRecordCallbackList.get(i).mCb)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Must be called synchronized on mRecordCallbackLock
+     */
+    private boolean removeRecordCallback_sync(@NonNull AudioRecordingCallback cb) {
+        if (mRecordCallbackList != null) {
+            for (int i=0 ; i < mRecordCallbackList.size() ; i++) {
+                if (cb.equals(mRecordCallbackList.get(i).mCb)) {
+                    mRecordCallbackList.remove(i);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private final IRecordingConfigDispatcher mRecCb = new IRecordingConfigDispatcher.Stub() {
 
         public void dispatchRecordingConfigChange() {
-            final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(
-                    MSSG_RECORDING_CONFIG_CHANGE/*what*/);
-            mServiceEventHandlerDelegate.getHandler().sendMessage(m);
+            synchronized(mRecordCallbackLock) {
+                if (mRecordCallbackList != null) {
+                    for (int i=0 ; i < mRecordCallbackList.size() ; i++) {
+                        final AudioRecordingCallbackInfo arci = mRecordCallbackList.get(i);
+                        if (arci.mHandler != null) {
+                            final Message m = arci.mHandler.obtainMessage(
+                                    MSSG_RECORDING_CONFIG_CHANGE/*what*/, arci.mCb/*obj*/);
+                            arci.mHandler.sendMessage(m);
+                        }
+                    }
+                }
+            }
         }
 
     };
diff --git a/media/java/android/media/AudioRecordConfiguration.java b/media/java/android/media/AudioRecordConfiguration.java
index aefe692..c7d219d 100644
--- a/media/java/android/media/AudioRecordConfiguration.java
+++ b/media/java/android/media/AudioRecordConfiguration.java
@@ -22,8 +22,9 @@
 import java.util.Objects;
 
 /**
- * @hide
- * Candidate for public API, see AudioManager.getActiveRecordConfiguration()
+ * The AudioRecordConfiguration class collects the information describing an audio recording
+ * session. This information is returned through the 
+ * {@link AudioManager#getActiveRecordConfigurations()} method.
  *
  */
 public class AudioRecordConfiguration implements Parcelable {
@@ -41,19 +42,23 @@
     }
 
     /**
-     * @return one of AudioSource.MIC, AudioSource.VOICE_UPLINK,
-     *       AudioSource.VOICE_DOWNLINK, AudioSource.VOICE_CALL,
-     *       AudioSource.CAMCORDER, AudioSource.VOICE_RECOGNITION,
-     *       AudioSource.VOICE_COMMUNICATION.
+     * Returns the audio source being used for the recording.
+     * @return one of {@link MediaRecorder.AudioSource#MIC},
+     *       {@link MediaRecorder.AudioSource#VOICE_UPLINK},
+     *       {@link MediaRecorder.AudioSource#VOICE_DOWNLINK},
+     *       {@link MediaRecorder.AudioSource#VOICE_CALL},
+     *       {@link MediaRecorder.AudioSource#CAMCORDER},
+     *       {@link MediaRecorder.AudioSource#VOICE_RECOGNITION},
+     *       {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
      */
     public int getClientAudioSource() { return mClientSource; }
 
     /**
-     * @return the session number of the recorder.
+     * Returns the session number of the recording, see {@link AudioRecord#getAudioSessionId()}.
+     * @return the session number.
      */
     public int getAudioSessionId() { return mSessionId; }
 
-
     public static final Parcelable.Creator<AudioRecordConfiguration> CREATOR
             = new Parcelable.Creator<AudioRecordConfiguration>() {
         /**
diff --git a/native/android/Android.mk b/native/android/Android.mk
index 1742bee..5386e6f 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -7,6 +7,7 @@
 #
 LOCAL_SRC_FILES:= \
     asset_manager.cpp \
+    choreographer.cpp \
     configuration.cpp \
     input.cpp \
     looper.cpp \
@@ -43,4 +44,7 @@
 
 LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
 
+# Required because of b/25642296
+LOCAL_CLANG_arm64 := false
+
 include $(BUILD_SHARED_LIBRARY)
diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp
new file mode 100644
index 0000000..cc2fd77
--- /dev/null
+++ b/native/android/choreographer.cpp
@@ -0,0 +1,205 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Choreographer"
+//#define LOG_NDEBUG 0
+
+#include <cinttypes>
+#include <queue>
+#include <thread>
+
+#include <android/choreographer.h>
+#include <androidfw/DisplayEventDispatcher.h>
+#include <gui/ISurfaceComposer.h>
+#include <utils/Looper.h>
+#include <utils/Mutex.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+struct FrameCallback {
+    AChoreographer_frameCallback callback;
+    void* data;
+    nsecs_t dueTime;
+
+    inline bool operator<(const FrameCallback& rhs) const {
+        // Note that this is intentionally flipped because we want callbacks due sooner to be at
+        // the head of the queue
+        return dueTime > rhs.dueTime;
+    }
+};
+
+
+class Choreographer : public DisplayEventDispatcher, public MessageHandler {
+public:
+    void postFrameCallback(AChoreographer_frameCallback cb, void* data);
+    void postFrameCallbackDelayed(AChoreographer_frameCallback cb, void* data, nsecs_t delay);
+
+    enum {
+        MSG_SCHEDULE_CALLBACKS = 0,
+        MSG_SCHEDULE_VSYNC = 1
+    };
+    virtual void handleMessage(const Message& message) override;
+
+    static Choreographer* getForThread();
+
+protected:
+    virtual ~Choreographer() = default;
+
+private:
+    Choreographer(const sp<Looper>& looper);
+    Choreographer(const Choreographer&) = delete;
+
+    virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);
+    virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
+
+    void scheduleCallbacks();
+
+    // Protected by mLock
+    std::priority_queue<FrameCallback> mCallbacks;
+
+    mutable Mutex mLock;
+
+    const sp<Looper> mLooper;
+    const std::thread::id mThreadId;
+};
+
+
+thread_local Choreographer* gChoreographer;
+Choreographer* Choreographer::getForThread() {
+    if (gChoreographer == nullptr) {
+        sp<Looper> looper = Looper::getForThread();
+        if (!looper.get()) {
+            ALOGW("No looper prepared for thread");
+            return nullptr;
+        }
+        gChoreographer = new Choreographer(looper);
+        status_t result = gChoreographer->initialize();
+        if (result != OK) {
+            ALOGW("Failed to initialize");
+            return nullptr;
+        }
+    }
+    return gChoreographer;
+}
+
+Choreographer::Choreographer(const sp<Looper>& looper) :
+    DisplayEventDispatcher(looper), mLooper(looper), mThreadId(std::this_thread::get_id()) {
+}
+
+void Choreographer::postFrameCallback(AChoreographer_frameCallback cb, void* data) {
+    postFrameCallbackDelayed(cb, data, 0);
+}
+
+void Choreographer::postFrameCallbackDelayed(
+        AChoreographer_frameCallback cb, void* data, nsecs_t delay) {
+    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    FrameCallback callback{cb, data, now + delay};
+    {
+        AutoMutex _l{mLock};
+        mCallbacks.push(callback);
+    }
+    if (callback.dueTime <= now) {
+        if (std::this_thread::get_id() != mThreadId) {
+            Message m{MSG_SCHEDULE_VSYNC};
+            mLooper->sendMessage(this, m);
+        } else {
+            scheduleVsync();
+        }
+    } else {
+        Message m{MSG_SCHEDULE_CALLBACKS};
+        mLooper->sendMessageDelayed(delay, this, m);
+    }
+}
+
+void Choreographer::scheduleCallbacks() {
+    AutoMutex _{mLock};
+    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    if (mCallbacks.top().dueTime <= now) {
+        ALOGV("choreographer %p ~ scheduling vsync", this);
+        scheduleVsync();
+        return;
+    }
+}
+
+
+void Choreographer::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t) {
+    if (id != ISurfaceComposer::eDisplayIdMain) {
+        ALOGV("choreographer %p ~ ignoring vsync signal for non-main display (id=%d)", this, id);
+        scheduleVsync();
+        return;
+    }
+    std::vector<FrameCallback> callbacks{};
+    {
+        AutoMutex _l{mLock};
+        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+        while (!mCallbacks.empty() && mCallbacks.top().dueTime < now) {
+            callbacks.push_back(mCallbacks.top());
+            mCallbacks.pop();
+        }
+    }
+    for (const auto& cb : callbacks) {
+        cb.callback(timestamp, cb.data);
+    }
+}
+
+void Choreographer::dispatchHotplug(nsecs_t, int32_t id, bool connected) {
+    ALOGV("choreographer %p ~ received hotplug event (id=%" PRId32 ", connected=%s), ignoring.",
+            this, id, toString(connected));
+}
+
+void Choreographer::handleMessage(const Message& message) {
+    switch (message.what) {
+    case MSG_SCHEDULE_CALLBACKS:
+        scheduleCallbacks();
+        break;
+    case MSG_SCHEDULE_VSYNC:
+        scheduleVsync();
+        break;
+    }
+}
+
+}
+
+/* Glue for the NDK interface */
+
+using android::Choreographer;
+
+static inline Choreographer* AChoreographer_to_Choreographer(AChoreographer* choreographer) {
+    return reinterpret_cast<Choreographer*>(choreographer);
+}
+
+static inline AChoreographer* Choreographer_to_AChoreographer(Choreographer* choreographer) {
+    return reinterpret_cast<AChoreographer*>(choreographer);
+}
+
+AChoreographer* AChoreographer_getInstance() {
+    return Choreographer_to_AChoreographer(Choreographer::getForThread());
+}
+
+void AChoreographer_postFrameCallback(AChoreographer* choreographer,
+        AChoreographer_frameCallback callback, void* data) {
+    AChoreographer_to_Choreographer(choreographer)->postFrameCallback(callback, data);
+}
+void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer,
+        AChoreographer_frameCallback callback, void* data, long delayMillis) {
+    AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
+            callback, data, ms2ns(delayMillis));
+}
diff --git a/packages/DocumentsUI/res/drawable/cabinet.png b/packages/DocumentsUI/res/drawable/cabinet.png
new file mode 100644
index 0000000..da44023
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/cabinet.png
Binary files differ
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 1b5911d..223d729 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -36,8 +36,8 @@
         android:background="@color/material_grey_50"
         android:visibility="gone"/>
 
-    <!-- The empty directory view -->
-    <LinearLayout
+    <!-- The empty container view -->
+    <FrameLayout
         android:id="@android:id/empty"
         android:gravity="center"
         android:layout_width="match_parent"
@@ -45,21 +45,34 @@
         android:orientation="vertical"
         android:visibility="gone">
 
-        <TextView
-            android:id="@+id/message"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/empty"
-            style="@android:style/TextAppearance.Material.Subhead" />
+        <LinearLayout
+            android:id="@+id/content"
+            android:gravity="center"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
 
-         <Button
-            android:id="@+id/button_retry"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/button_retry"
-            style="?android:attr/buttonBarPositiveButtonStyle" />
+            <ImageView
+                android:id="@+id/artwork"
+                android:src="@drawable/cabinet"
+                android:adjustViewBounds="true"
+                android:layout_height="250dp"
+                android:layout_width="fill_parent"
+                android:alpha="1"
+                android:layout_centerVertical="true"
+                android:layout_marginBottom="25dp"
+                android:scaleType="fitCenter"
+                android:contentDescription="@null" />
 
-    </LinearLayout>
+            <TextView
+                android:id="@+id/message"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/empty"
+                style="@android:style/TextAppearance.Material.Subhead" />
+
+        </LinearLayout>
+    </FrameLayout>
 
     <!-- This FrameLayout works around b/24189541 -->
     <FrameLayout
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 05c43b2..f926245 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -123,6 +123,8 @@
 
     <!-- Text shown when a directory of documents is empty [CHAR LIMIT=24] -->
     <string name="empty">No items</string>
+    <!-- Text shown when a file search returns no items [CHAR LIMIT=32] -->
+    <string name="no_results">No matches in %1$s</string>
 
     <!-- Toast shown when no app can be found to open the selected document [CHAR LIMIT=48] -->
     <string name="toast_no_application">Can\'t open file</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index d220f40..bfd40bb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -28,6 +28,7 @@
 import static com.android.internal.util.Preconditions.checkState;
 import static com.google.common.base.Preconditions.checkArgument;
 
+import android.annotation.StringRes;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.Fragment;
@@ -131,6 +132,7 @@
     private static final int LOADER_ID = 42;
     private static final int DELETE_UNDO_TIMEOUT = 5000;
     private static final int DELETE_JOB_DELAY = 5500;
+    private static final int EMPTY_REVEAL_DURATION = 250;
 
     private static final String EXTRA_TYPE = "type";
     private static final String EXTRA_ROOT = "root";
@@ -147,6 +149,7 @@
 
     private View mEmptyView;
     private RecyclerView mRecView;
+    private ListeningGestureDetector mGestureDetector;
 
     private int mType = TYPE_NORMAL;
     private String mStateKey;
@@ -166,63 +169,6 @@
     private MessageBar mMessageBar;
     private View mProgressBar;
 
-    public static void showDirectory(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
-        show(fm, TYPE_NORMAL, root, doc, null, anim);
-    }
-
-    public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
-        show(fm, TYPE_SEARCH, root, null, query, anim);
-    }
-
-    public static void showRecentsOpen(FragmentManager fm, int anim) {
-        show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
-    }
-
-    private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
-            String query, int anim) {
-        final Bundle args = new Bundle();
-        args.putInt(EXTRA_TYPE, type);
-        args.putParcelable(EXTRA_ROOT, root);
-        args.putParcelable(EXTRA_DOC, doc);
-        args.putString(EXTRA_QUERY, query);
-
-        final FragmentTransaction ft = fm.beginTransaction();
-        switch (anim) {
-            case ANIM_SIDE:
-                args.putBoolean(EXTRA_IGNORE_STATE, true);
-                break;
-            case ANIM_DOWN:
-                args.putBoolean(EXTRA_IGNORE_STATE, true);
-                ft.setCustomAnimations(R.animator.dir_down, R.animator.dir_frozen);
-                break;
-            case ANIM_UP:
-                ft.setCustomAnimations(R.animator.dir_frozen, R.animator.dir_up);
-                break;
-        }
-
-        final DirectoryFragment fragment = new DirectoryFragment();
-        fragment.setArguments(args);
-
-        ft.replace(R.id.container_directory, fragment);
-        ft.commitAllowingStateLoss();
-    }
-
-    private static String buildStateKey(RootInfo root, DocumentInfo doc) {
-        final StringBuilder builder = new StringBuilder();
-        builder.append(root != null ? root.authority : "null").append(';');
-        builder.append(root != null ? root.rootId : "null").append(';');
-        builder.append(doc != null ? doc.documentId : "null");
-        return builder.toString();
-    }
-
-    public static @Nullable DirectoryFragment get(FragmentManager fm) {
-        // TODO: deal with multiple directories shown at once
-        Fragment fragment = fm.findFragmentById(R.id.container_directory);
-        return fragment instanceof DirectoryFragment
-                ? (DirectoryFragment) fragment
-                : null;
-    }
-
     @Override
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -300,24 +246,9 @@
 
         mRecView.setAdapter(mAdapter);
 
-        GestureListener listener = new GestureListener();
-        final GestureDetector detector = new GestureDetector(this.getContext(), listener);
-        detector.setOnDoubleTapListener(listener);
+        mGestureDetector = new ListeningGestureDetector(this.getContext(), new GestureListener());
 
-        mRecView.addOnItemTouchListener(
-                new OnItemTouchListener() {
-                    @Override
-                    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
-                        detector.onTouchEvent(e);
-                        return false;
-                    }
-
-                    @Override
-                    public void onTouchEvent(RecyclerView rv, MotionEvent e) {}
-
-                    @Override
-                    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
-                });
+        mRecView.addOnItemTouchListener(mGestureDetector);
 
         // TODO: instead of inserting the view into the constructor, extract listener-creation code
         // and set the listener on the view after the fact.  Then the view doesn't need to be passed
@@ -931,25 +862,43 @@
         return mTuner.isDocumentEnabled(docMimeType, docFlags);
     }
 
-    void showEmptyView() {
-        mEmptyView.setVisibility(View.VISIBLE);
-        mRecView.setVisibility(View.GONE);
-        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
-        msg.setText(R.string.empty);
-        // No retry button for the empty view.
-        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
+    private void showEmptyDirectory() {
+        showEmptyView(R.string.empty);
     }
 
-    void showErrorView() {
-        mEmptyView.setVisibility(View.VISIBLE);
-        mRecView.setVisibility(View.GONE);
-        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
-        msg.setText(R.string.query_error);
-        // TODO: Enable this once the retry button does something.
-        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
+    private void showNoResults(RootInfo root) {
+        CharSequence msg = getContext().getResources().getText(R.string.no_results);
+        showEmptyView(String.format(String.valueOf(msg), root.title));
     }
 
-    void showRecyclerView() {
+    // Shows an error indicating documents couldn't be queried.
+    private void showQueryError() {
+        showEmptyView(R.string.query_error);
+    }
+
+    private void showEmptyView(@StringRes int id) {
+        showEmptyView(getContext().getResources().getText(id));
+    }
+
+    private void showEmptyView(CharSequence msg) {
+        View content = mEmptyView.findViewById(R.id.content);
+        TextView msgView = (TextView) mEmptyView.findViewById(R.id.message);
+        msgView.setText(msg);
+
+        content.animate().cancel();  // cancel any ongoing animations
+
+        content.setAlpha(0);
+        mEmptyView.setVisibility(View.VISIBLE);
+        mRecView.setVisibility(View.GONE);
+
+        // fade in the content, so it looks purdy like
+        content.animate()
+                .alpha(1f)
+                .setDuration(EMPTY_REVEAL_DURATION)
+                .setListener(null);
+    }
+
+    private void showDirectory() {
         mEmptyView.setVisibility(View.GONE);
         mRecView.setVisibility(View.VISIBLE);
     }
@@ -1129,11 +1078,7 @@
             view.setOnDragListener(mOnDragListener);
         }
 
-        // Temporary: attaching the listener to the title only.
-        // Attaching to the entire item conflicts with the item long click handler responsible
-        // for item selection.
-        final View title = view.findViewById(android.R.id.title);
-        title.setOnLongClickListener(mLongClickListener);
+        view.setOnLongClickListener(mLongClickListener);
     }
 
     private View.OnDragListener mOnDragListener = new View.OnDragListener() {
@@ -1208,24 +1153,6 @@
         }
     }
 
-    private View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
-        @Override
-        public boolean onLongClick(View v) {
-            final List<DocumentInfo> docs = getDraggableDocuments(v);
-            if (docs.isEmpty()) {
-                return false;
-            }
-            v.startDrag(
-                    getClipDataFromDocuments(docs),
-                    new DrawableShadowBuilder(getDragShadowIcon(docs)),
-                    null,
-                    View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
-                            View.DRAG_FLAG_GLOBAL_URI_WRITE
-            );
-            return true;
-        }
-    };
-
     private List<DocumentInfo> getDraggableDocuments(View currentItemView) {
         String modelId = getModelId(currentItemView);
         if (modelId == null) {
@@ -1336,19 +1263,80 @@
             mProgressBar.setVisibility(model.isLoading() ? View.VISIBLE : View.GONE);
 
             if (model.isEmpty()) {
-                showEmptyView();
+                if (getDisplayState().currentSearch != null) {
+                    showNoResults(getDisplayState().stack.root);
+                } else {
+                    showEmptyDirectory();
+                }
             } else {
-                showRecyclerView();
+                showDirectory();
                 mAdapter.notifyDataSetChanged();
             }
         }
 
         @Override
         public void onModelUpdateFailed(Exception e) {
-            showErrorView();
+            showQueryError();
         }
     }
 
+    private View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
+        @Override
+        public boolean onLongClick(View v) {
+            if (mGestureDetector.mouseSpawnedLastEvent()) {
+                List<DocumentInfo> docs = getDraggableDocuments(v);
+                if (docs.isEmpty()) {
+                    return false;
+                }
+                v.startDrag(
+                        getClipDataFromDocuments(docs),
+                        new DrawableShadowBuilder(getDragShadowIcon(docs)),
+                        null,
+                        View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
+                                View.DRAG_FLAG_GLOBAL_URI_WRITE
+                );
+                return true;
+            }
+
+            return false;
+        }
+    };
+
+    // Previously we listened to events with one class, only to bounce them forward
+    // to GestureDetector. We're still doing that here, but with a single class
+    // that reduces overall complexity in our glue code.
+    private static final class ListeningGestureDetector extends GestureDetector
+            implements OnItemTouchListener {
+
+        private int mLastTool = -1;
+
+        public ListeningGestureDetector(Context context, GestureListener listener) {
+            super(context, listener);
+            setOnDoubleTapListener(listener);
+        }
+
+        boolean mouseSpawnedLastEvent() {
+            return Events.isMouseType(mLastTool);
+        }
+
+        boolean touchSpawnedLastEvent() {
+            return Events.isTouchType(mLastTool);
+        }
+
+        @Override
+        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+            mLastTool = e.getToolType(0);
+            onTouchEvent(e);  // bounce this forward to our detecty heart
+            return false;
+        }
+
+        @Override
+        public void onTouchEvent(RecyclerView rv, MotionEvent e) {}
+
+        @Override
+        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
+    }
+
     /**
      * The gesture listener for items in the list/grid view. Interprets gestures and sends the
      * events to the target DocumentHolder, whence they are routed to the appropriate listener.
@@ -1398,4 +1386,62 @@
             }
         }
     }
+
+    public static void showDirectory(
+            FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
+        show(fm, TYPE_NORMAL, root, doc, null, anim);
+    }
+
+    public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
+        show(fm, TYPE_SEARCH, root, null, query, anim);
+    }
+
+    public static void showRecentsOpen(FragmentManager fm, int anim) {
+        show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
+    }
+
+    private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
+            String query, int anim) {
+        final Bundle args = new Bundle();
+        args.putInt(EXTRA_TYPE, type);
+        args.putParcelable(EXTRA_ROOT, root);
+        args.putParcelable(EXTRA_DOC, doc);
+        args.putString(EXTRA_QUERY, query);
+
+        final FragmentTransaction ft = fm.beginTransaction();
+        switch (anim) {
+            case ANIM_SIDE:
+                args.putBoolean(EXTRA_IGNORE_STATE, true);
+                break;
+            case ANIM_DOWN:
+                args.putBoolean(EXTRA_IGNORE_STATE, true);
+                ft.setCustomAnimations(R.animator.dir_down, R.animator.dir_frozen);
+                break;
+            case ANIM_UP:
+                ft.setCustomAnimations(R.animator.dir_frozen, R.animator.dir_up);
+                break;
+        }
+
+        final DirectoryFragment fragment = new DirectoryFragment();
+        fragment.setArguments(args);
+
+        ft.replace(R.id.container_directory, fragment);
+        ft.commitAllowingStateLoss();
+    }
+
+    private static String buildStateKey(RootInfo root, DocumentInfo doc) {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(root != null ? root.authority : "null").append(';');
+        builder.append(root != null ? root.rootId : "null").append(';');
+        builder.append(doc != null ? doc.documentId : "null");
+        return builder.toString();
+    }
+
+    public static @Nullable DirectoryFragment get(FragmentManager fm) {
+        // TODO: deal with multiple directories shown at once
+        Fragment fragment = fm.findFragmentById(R.id.container_directory);
+        return fragment instanceof DirectoryFragment
+                ? (DirectoryFragment) fragment
+                : null;
+    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 9cbcf8c..71e87cb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -17,6 +17,8 @@
 package com.android.documentsui.dirlist;
 
 import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DIRECTORY;
+import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DOCUMENT;
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.internal.util.Preconditions.checkState;
@@ -788,6 +790,10 @@
         int getChildCount();
         int getVisibleChildCount();
         void focusItem(int position);
+        /**
+         * Layout items are excluded from the GridModel.
+         */
+        boolean isLayoutItem(int adapterPosition);
     }
 
     /** Recycler view facade implementation backed by good ol' RecyclerView. */
@@ -944,6 +950,20 @@
                     });
             }
         }
+
+        @Override
+        public boolean isLayoutItem(int pos) {
+            // The band selection model only operates on documents and directories. Exclude other
+            // types of adapter items (e.g. whitespace items like dividers).
+            RecyclerView.ViewHolder vh = mView.findViewHolderForAdapterPosition(pos);
+            switch (vh.getItemViewType()) {
+                case ITEM_TYPE_DOCUMENT:
+                case ITEM_TYPE_DIRECTORY:
+                    return false;
+                default:
+                    return true;
+            }
+        }
     }
 
     public interface Callback {
@@ -1407,7 +1427,8 @@
         private void recordVisibleChildren() {
             for (int i = 0; i < mHelper.getVisibleChildCount(); i++) {
                 int adapterPosition = mHelper.getAdapterPositionAt(i);
-                if (!mKnownPositions.get(adapterPosition)) {
+                if (!mHelper.isLayoutItem(adapterPosition) &&
+                        !mKnownPositions.get(adapterPosition)) {
                     mKnownPositions.put(adapterPosition, true);
                     recordItemData(mHelper.getAbsoluteRectForChildViewAt(i), adapterPosition);
                 }
@@ -1493,31 +1514,29 @@
          * @param rect Rectangle including all covered items.
          */
         private void updateSelection(Rect rect) {
-            int columnStartIndex =
+            int columnStart =
                     Collections.binarySearch(mColumnBounds, new Limits(rect.left, rect.left));
-            checkState(columnStartIndex >= 0);
-            int columnEndIndex = columnStartIndex;
+            checkState(columnStart >= 0);
+            int columnEnd = columnStart;
 
-            for (int i = columnStartIndex; i < mColumnBounds.size()
+            for (int i = columnStart; i < mColumnBounds.size()
                     && mColumnBounds.get(i).lowerLimit <= rect.right; i++) {
-                columnEndIndex = i;
+                columnEnd = i;
             }
 
-            SparseIntArray firstColumn =
-                    mColumns.get(mColumnBounds.get(columnStartIndex).lowerLimit);
-            int rowStartIndex = firstColumn.indexOfKey(rect.top);
-            if (rowStartIndex < 0) {
+            int rowStart = Collections.binarySearch(mRowBounds, new Limits(rect.top, rect.top));
+            if (rowStart < 0) {
                 mPositionNearestOrigin = NOT_SET;
                 return;
             }
 
-            int rowEndIndex = rowStartIndex;
-            for (int i = rowStartIndex;
-                    i < firstColumn.size() && firstColumn.keyAt(i) <= rect.bottom; i++) {
-                rowEndIndex = i;
+            int rowEnd = rowStart;
+            for (int i = rowStart; i < mRowBounds.size()
+                    && mRowBounds.get(i).lowerLimit <= rect.bottom; i++) {
+                rowEnd = i;
             }
 
-            updateSelection(columnStartIndex, columnEndIndex, rowStartIndex, rowEndIndex);
+            updateSelection(columnStart, columnEnd, rowStart, rowEnd);
         }
 
         /**
@@ -1526,13 +1545,17 @@
          */
         private void updateSelection(
                 int columnStartIndex, int columnEndIndex, int rowStartIndex, int rowEndIndex) {
+            if (DEBUG) Log.d(TAG, String.format("updateSelection: %d, %d, %d, %d",
+                    columnStartIndex, columnEndIndex, rowStartIndex, rowEndIndex));
+
             mSelection.clear();
             for (int column = columnStartIndex; column <= columnEndIndex; column++) {
                 SparseIntArray items = mColumns.get(mColumnBounds.get(column).lowerLimit);
                 for (int row = rowStartIndex; row <= rowEndIndex; row++) {
                     // The default return value for SparseIntArray.get is 0, which is a valid
                     // position. Use a sentry value to prevent erroneously selecting item 0.
-                    int position = items.get(items.keyAt(row), NOT_SET);
+                    final int rowKey = mRowBounds.get(row).lowerLimit;
+                    int position = items.get(rowKey, NOT_SET);
                     if (position != NOT_SET) {
                         String id = mAdapter.getModelId(position);
                         if (id != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
index d647950..aca2d7a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
@@ -52,9 +52,11 @@
 
     private static final int DEFAULT_DELAY = 0;
     private static final int MAX_DELAY = 10 * 1000;  // ten seconds
+    private static final int POOL_SIZE = 2;  // "pool size", not *max* "pool size".
+    private static final int NOTIFICATION_ID_PROGRESS = 0;
+    private static final int NOTIFICATION_ID_FAILURE = 1;
 
     public static final String TAG = "FileOperationService";
-    private static final int POOL_SIZE = 2;  // "pool size", not *max* "pool size".
 
     public static final String EXTRA_JOB_ID = "com.android.documentsui.JOB_ID";
     public static final String EXTRA_DELAY = "com.android.documentsui.DELAY";
@@ -209,7 +211,7 @@
         // interactivity for the user in case the copy loop is stalled.
         // Try to cancel it even if we don't have a job id...in case there is some sad
         // orphan notification.
-        mNotificationManager.cancel(jobId, 0);
+        mNotificationManager.cancel(jobId, NOTIFICATION_ID_PROGRESS);
 
         // TODO: Guarantee the job is being finalized
     }
@@ -286,7 +288,7 @@
     @Override
     public void onStart(Job job) {
         if (DEBUG) Log.d(TAG, "onStart: " + job.id);
-        mNotificationManager.notify(job.id, 0, job.getSetupNotification());
+        mNotificationManager.notify(job.id, NOTIFICATION_ID_PROGRESS, job.getSetupNotification());
     }
 
     @Override
@@ -294,7 +296,7 @@
         if (DEBUG) Log.d(TAG, "onFinished: " + job.id);
 
         // Dismiss the ongoing copy notification when the copy is done.
-        mNotificationManager.cancel(job.id, 0);
+        mNotificationManager.cancel(job.id, NOTIFICATION_ID_PROGRESS);
 
         synchronized (mRunning) {
             deleteJob(job);
@@ -304,7 +306,8 @@
     @Override
     public void onProgress(CopyJob job) {
         if (DEBUG) Log.d(TAG, "onProgress: " + job.id);
-        mNotificationManager.notify(job.id, 0, job.getProgressNotification());
+        mNotificationManager.notify(
+                job.id, NOTIFICATION_ID_PROGRESS, job.getProgressNotification());
     }
 
     @Override
@@ -312,8 +315,8 @@
         if (DEBUG) Log.d(TAG, "onFailed: " + job.id);
         checkArgument(job.failed());
         Log.e(TAG, "Job failed on files: " + job.failedFiles.size() + ".");
-        mNotificationManager.notify(job.id, 0, job.getFailureNotification());
-        onFinished(job);  // failed jobs don't call finished, so we do.
+        mNotificationManager.notify(job.id, NOTIFICATION_ID_FAILURE, job.getFailureNotification());
+        onFinished(job);  // Failed jobs don't call finished, so we do.
     }
 
     private static final class JobRecord {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
index 7f6b1e1..7a238bd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
@@ -24,6 +24,7 @@
 import android.os.RemoteException;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
+import android.util.Log;
 
 import com.android.documentsui.R;
 import com.android.documentsui.model.DocumentInfo;
@@ -92,6 +93,15 @@
             }
         }
 
+        // Moving virtual files by bytes is not supported. This is because, it would involve
+        // conversion, and the source file should not be deleted in such case (as it's a different
+        // file).
+        if (src.isVirtualDocument()) {
+            Log.w(TAG, "Cannot move virtual files byte by byte.");
+            onFileFailed(src);
+            return false;
+        }
+
         // If we couldn't do an optimized copy...we fall back to vanilla byte copy.
         boolean copied = byteCopyDocument(src, dest);
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
index 5c04db9..7920c50 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
@@ -334,5 +334,10 @@
         public void focusItem(int i) {
             throw new UnsupportedOperationException();
         }
+
+        @Override
+        public boolean isLayoutItem(int adapterPosition) {
+            return false;
+        }
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
index c4cfd3a..0e79561 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
@@ -110,4 +110,9 @@
     @Override
     public void focusItem(int position) {
     }
+
+    @Override
+    public boolean isLayoutItem(int adapterPosition) {
+        return false;
+    }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
index 7edfcdb..69d2db7 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
@@ -16,6 +16,9 @@
 
 package com.android.documentsui.services;
 
+import static com.google.common.collect.Lists.newArrayList;
+
+import android.net.Uri;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.documentsui.model.DocumentInfo;
@@ -33,15 +36,23 @@
     }
 
     public void testMoveVirtualTypedFile() throws Exception {
-        runCopyVirtualTypedFileTest();
+        Uri testFile = mDocs.createVirtualFile(
+                mSrcRoot, "/virtual.sth", "virtual/mime-type",
+                FRUITY_BYTES, "application/pdf", "text/html");
+        createJob(newArrayList(testFile)).run();
 
-        mDocs.assertChildCount(mSrcRoot, 0);
+        mJobListener.waitForFinished();
+
+        // Should have failed, source not deleted. Moving by bytes for virtual files
+        // is not supported.
+        mDocs.assertChildCount(mDestRoot, 0);
+        mDocs.assertChildCount(mSrcRoot, 1);
     }
 
     public void testMoveVirtualNonTypedFile() throws Exception {
         runCopyVirtualNonTypedFileTest();
 
-        // should have failed, source not deleted
+        // Should have failed, source not deleted.
         mDocs.assertChildCount(mSrcRoot, 1);
     }
 
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 1459d53..d992bc3 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -21,6 +21,10 @@
     <string name="bugreport_in_progress_title">Bug report is being generated</string>
     <!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
     <string name="bugreport_finished_title">Bug report captured</string>
+    <!-- Title of notification indicating a bugreport is being updated before it can be shared. [CHAR LIMIT=50] -->
+    <string name="bugreport_updating_title">Adding details to the bug report</string>
+    <!-- Content notification indicating a bugreport is being updated before it can be shared, asking the user to wait [CHAR LIMIT=50] -->
+    <string name="bugreport_updating_wait">Please wait\u2026</string>
 
     <!-- Text of notification indicating that swipe left will share the captured bugreport. [CHAR LIMIT=100] -->
     <string name="bugreport_finished_text" product="watch">Swipe left to share your bug report</string>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 874a946d..5b83796 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -464,6 +464,7 @@
                     + info + ")");
             return;
         }
+        Log.v(TAG, "Sending 'Progress' notification for pid " + info.pid + ": " + percentText);
         NotificationManager.from(mContext).notify(TAG, info.pid, notification);
     }
 
@@ -852,7 +853,7 @@
     }
 
     /**
-     * Sends a notitication indicating the bugreport has finished so use can share it.
+     * Sends a notification indicating the bugreport has finished so use can share it.
      */
     private static void sendBugreportNotification(Context context, BugreportInfo info) {
         final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE);
@@ -878,10 +879,30 @@
             builder.setContentInfo(info.name);
         }
 
+        Log.v(TAG, "Sending 'Share' notification for pid " + info.pid + ": " + title);
         NotificationManager.from(context).notify(TAG, info.pid, builder.build());
     }
 
     /**
+     * Sends a notification indicating the bugreport is being updated so the user can wait until it
+     * finishes - at this point there is nothing to be done other than waiting, hence it has no
+     * pending action.
+     */
+    private static void sendBugreportBeingUpdatedNotification(Context context, int pid) {
+        final String title = context.getString(R.string.bugreport_updating_title);
+        final Notification.Builder builder = new Notification.Builder(context)
+                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                .setContentTitle(title)
+                .setTicker(title)
+                .setContentText(context.getString(R.string.bugreport_updating_wait))
+                .setLocalOnly(true)
+                .setColor(context.getColor(
+                        com.android.internal.R.color.system_notification_accent_color));
+        Log.v(TAG, "Sending 'Updating zip' notification for pid " + pid + ": " + title);
+        NotificationManager.from(context).notify(TAG, pid, builder.build());
+    }
+
+    /**
      * Sends a zipped bugreport notification.
      */
     private static void sendZippedBugreportNotification(final Context context,
@@ -938,11 +959,13 @@
             Log.d(TAG, "Not touching zip file since neither title nor description are set");
             return;
         }
+
         // It's not possible to add a new entry into an existing file, so we need to create a new
         // zip, copy all entries, then rename it.
+        sendBugreportBeingUpdatedNotification(mContext, info.pid); // ...and that takes time
         final File dir = info.bugreportFile.getParentFile();
         final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
-        Log.d(TAG, "Writing temporary zip file (" + tmpZip + ")");
+        Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
         try (ZipFile oldZip = new ZipFile(info.bugreportFile);
                 ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c1f97a8..4cdfcb4 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -229,7 +229,7 @@
                   android:resumeWhilePausing="true"
                   android:screenOrientation="behind"
                   android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|layoutDirection"
                   android:theme="@style/RecentsTheme.Wallpaper">
             <intent-filter>
                 <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
diff --git a/packages/SystemUI/res/drawable/recents_info_dark.xml b/packages/SystemUI/res/drawable/recents_info_dark.xml
new file mode 100644
index 0000000..b1a2242
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_info_dark.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/recents_task_bar_dark_icon_color"
+        android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM13.000000,17.000000l-2.000000,0.000000l0.000000,-6.000000l2.000000,0.000000L13.000000,17.000000zM13.000000,9.000000l-2.000000,0.000000L11.000000,7.000000l2.000000,0.000000L13.000000,9.000000z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/recents_info_light.xml b/packages/SystemUI/res/drawable/recents_info_light.xml
new file mode 100644
index 0000000..bc58c3b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_info_light.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM13.000000,17.000000l-2.000000,0.000000l0.000000,-6.000000l2.000000,0.000000L13.000000,17.000000zM13.000000,9.000000l-2.000000,0.000000L11.000000,7.000000l2.000000,0.000000L13.000000,9.000000z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml
deleted file mode 100644
index 1fcb2df..0000000
--- a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml
+++ /dev/null
@@ -1,322 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
--->
-
-<!--  navigation bar for sw600dp (small tablets) -->
-<com.android.systemui.statusbar.phone.NavigationBarView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
-    android:layout_height="match_parent"
-    android:layout_width="match_parent"
-    android:background="@drawable/system_bar_background"
-    >
-
-    <FrameLayout android:id="@+id/rot0"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent"
-        >
-
-        <LinearLayout
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            android:orientation="horizontal"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            android:id="@+id/nav_buttons"
-            android:animateLayoutChanges="true"
-            >
-
-            <!-- navigation controls -->
-            <View
-                android:layout_width="@dimen/navigation_extra_key_width"
-                android:layout_height="match_parent"
-                android:layout_weight="0"
-                android:layout_marginStart="2dp"
-                android:visibility="invisible"
-                />
-            <Space
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
-                android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_back"
-                android:scaleType="centerInside"
-                systemui:keyCode="4"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_back"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
-                android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_home"
-                android:scaleType="centerInside"
-                systemui:keyCode="3"
-                systemui:keyRepeat="true"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_home"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
-                android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_recent"
-                android:scaleType="centerInside"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_recent"
-                />
-            <Space
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                />
-            <FrameLayout
-                android:layout_width="@dimen/navigation_extra_key_width"
-                android:layout_height="match_parent"
-                android:layout_weight="0"
-                android:layout_marginEnd="2dp" >
-                <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/menu"
-                    android:layout_width="@dimen/navigation_extra_key_width"
-                    android:layout_height="match_parent"
-                    android:src="@drawable/ic_sysbar_menu"
-                    android:scaleType="centerInside"
-                    android:layout_marginEnd="2dp"
-                    systemui:keyCode="82"
-                    android:visibility="invisible"
-                    android:contentDescription="@string/accessibility_menu"
-                    />
-                <com.android.systemui.statusbar.policy.KeyButtonView
-                    android:id="@+id/ime_switcher"
-                    android:layout_width="@dimen/navigation_extra_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="2dp"
-                    android:scaleType="centerInside"
-                    android:src="@drawable/ic_ime_switcher_default"
-                    android:visibility="invisible"
-                    android:contentDescription="@string/accessibility_ime_switch_button" />
-            </FrameLayout>
-        </LinearLayout>
-
-        <!-- lights out layout to match exactly -->
-        <LinearLayout
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            android:orientation="horizontal"
-            android:id="@+id/lights_out"
-            android:visibility="gone"
-            >
-            <Space
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                />
-            <ImageView
-                android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
-                android:layout_height="match_parent"
-                android:layout_marginStart="40dp"
-                android:src="@drawable/ic_sysbar_lights_out_dot_small"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_back"
-                />
-            <ImageView
-                android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_lights_out_dot_large"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_home"
-                />
-            <ImageView
-                android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
-                android:layout_marginEnd="40dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_lights_out_dot_small"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_recent"
-                />
-            <Space
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                />
-        </LinearLayout>
-
-        <com.android.systemui.statusbar.policy.DeadZone
-            android:id="@+id/deadzone"
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            systemui:minSize="@dimen/navigation_bar_deadzone_size"
-            systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
-            systemui:holdTime="@integer/navigation_bar_deadzone_hold"
-            systemui:decayTime="@integer/navigation_bar_deadzone_decay"
-            systemui:orientation="horizontal"
-            android:layout_gravity="top"
-            />
-    </FrameLayout>
-
-    <FrameLayout android:id="@+id/rot90"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent"
-        android:visibility="gone"
-        android:paddingTop="0dp"
-        >
-
-        <LinearLayout
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            android:orientation="horizontal"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            android:id="@+id/nav_buttons"
-            android:animateLayoutChanges="true"
-            >
-
-            <!-- navigation controls -->
-            <View
-                android:layout_width="@dimen/navigation_extra_key_width"
-                android:layout_height="match_parent"
-                android:layout_weight="0"
-                android:layout_marginStart="2dp"
-                android:visibility="invisible"
-                />
-            <Space
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
-                android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_back"
-                android:scaleType="centerInside"
-                systemui:keyCode="4"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_back"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
-                android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_home"
-                android:scaleType="centerInside"
-                systemui:keyCode="3"
-                systemui:keyRepeat="true"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_home"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
-                android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_recent"
-                android:scaleType="centerInside"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_recent"
-                />
-            <Space
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                />
-            <FrameLayout
-                android:layout_width="@dimen/navigation_extra_key_width"
-                android:layout_height="match_parent"
-                android:layout_marginEnd="2dp"
-                android:layout_weight="0" >
-                <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/menu"
-                    android:layout_width="@dimen/navigation_extra_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="2dp"
-                    android:src="@drawable/ic_sysbar_menu"
-                    android:scaleType="centerInside"
-                    systemui:keyCode="82"
-                    android:visibility="invisible"
-                    android:contentDescription="@string/accessibility_menu" />
-                <com.android.systemui.statusbar.policy.KeyButtonView
-                    android:id="@+id/ime_switcher"
-                    android:layout_width="@dimen/navigation_extra_key_width"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="2dp"
-                    android:src="@drawable/ic_ime_switcher_default"
-                    android:visibility="invisible"
-                    android:contentDescription="@string/accessibility_ime_switch_button"
-                    android:scaleType="centerInside" />
-            </FrameLayout>
-        </LinearLayout>
-
-        <!-- lights out layout to match exactly -->
-        <LinearLayout
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            android:orientation="horizontal"
-            android:id="@+id/lights_out"
-            android:visibility="gone"
-            >
-            <Space
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                />
-            <ImageView
-                android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
-                android:layout_height="match_parent"
-                android:layout_marginStart="40dp"
-                android:src="@drawable/ic_sysbar_lights_out_dot_small"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_back"
-                />
-            <ImageView
-                android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_lights_out_dot_large"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_home"
-                />
-            <ImageView
-                android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
-                android:layout_marginEnd="40dp"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_lights_out_dot_small"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_recent"
-                />
-            <Space
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                />
-        </LinearLayout>
-
-        <!-- On tablets in landscape the navbar is on the bottom, so use a
-             horizontal dead zone. -->
-        <com.android.systemui.statusbar.policy.DeadZone
-            android:id="@+id/deadzone"
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            systemui:minSize="@dimen/navigation_bar_deadzone_size"
-            systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
-            systemui:holdTime="@integer/navigation_bar_deadzone_hold"
-            systemui:decayTime="@integer/navigation_bar_deadzone_decay"
-            systemui:orientation="horizontal"
-            android:layout_gravity="top"
-            />
-    </FrameLayout>
-</com.android.systemui.statusbar.phone.NavigationBarView>
diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
new file mode 100644
index 0000000..dd559c5
--- /dev/null
+++ b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
@@ -0,0 +1,90 @@
+<?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"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:id="@+id/start_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="start"
+            android:orientation="horizontal" />
+
+        <LinearLayout
+            android:id="@+id/center_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="horizontal" />
+
+        <LinearLayout
+            android:id="@+id/end_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="end"
+            android:orientation="horizontal" />
+
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/lights_out"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:id="@+id/start_group_lightsout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="start"
+            android:orientation="horizontal" />
+
+        <LinearLayout
+            android:id="@+id/center_group_lightsout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="horizontal" />
+
+        <LinearLayout
+            android:id="@+id/end_group_lightsout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="end"
+            android:orientation="horizontal" />
+
+    </FrameLayout>
+
+    <com.android.systemui.statusbar.policy.DeadZone
+        android:id="@+id/deadzone"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:layout_gravity="top"
+        systemui:minSize="@dimen/navigation_bar_deadzone_size"
+        systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+        systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+        systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+        systemui:orientation="horizontal"
+        />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/back.xml b/packages/SystemUI/res/layout/back.xml
new file mode 100644
index 0000000..d256622
--- /dev/null
+++ b/packages/SystemUI/res/layout/back.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<com.android.systemui.statusbar.policy.KeyButtonView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/back"
+    android:layout_width="@dimen/navigation_key_width"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    android:src="@drawable/ic_sysbar_back"
+    systemui:keyCode="4"
+    android:scaleType="center"
+    android:contentDescription="@string/accessibility_back"
+    android:paddingStart="@dimen/navigation_key_padding"
+    android:paddingEnd="@dimen/navigation_key_padding"
+    />
+
diff --git a/packages/SystemUI/res/layout/home.xml b/packages/SystemUI/res/layout/home.xml
new file mode 100644
index 0000000..f11592d
--- /dev/null
+++ b/packages/SystemUI/res/layout/home.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<com.android.systemui.statusbar.policy.KeyButtonView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/home"
+    android:layout_width="@dimen/navigation_key_width"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    android:src="@drawable/ic_sysbar_home"
+    systemui:keyCode="3"
+    android:scaleType="center"
+    android:contentDescription="@string/accessibility_home"
+    android:paddingStart="@dimen/navigation_key_padding"
+    android:paddingEnd="@dimen/navigation_key_padding"
+    />
+
diff --git a/packages/SystemUI/res/layout/menu_ime.xml b/packages/SystemUI/res/layout/menu_ime.xml
new file mode 100644
index 0000000..90b74d0
--- /dev/null
+++ b/packages/SystemUI/res/layout/menu_ime.xml
@@ -0,0 +1,44 @@
+<?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"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="@dimen/navigation_side_padding"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    >
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/menu"
+        android:layout_width="@dimen/navigation_extra_key_width"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="2dp"
+        android:src="@drawable/ic_sysbar_menu"
+        android:scaleType="centerInside"
+        systemui:keyCode="82"
+        android:visibility="invisible"
+        android:contentDescription="@string/accessibility_menu"
+        />
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/ime_switcher"
+        android:layout_width="@dimen/navigation_extra_key_width"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="2dp"
+        android:src="@drawable/ic_ime_switcher_default"
+        android:visibility="invisible"
+        android:contentDescription="@string/accessibility_ime_switch_button"
+        android:scaleType="centerInside"
+        />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/nav_key_space.xml b/packages/SystemUI/res/layout/nav_key_space.xml
new file mode 100644
index 0000000..3986841
--- /dev/null
+++ b/packages/SystemUI/res/layout/nav_key_space.xml
@@ -0,0 +1,25 @@
+<?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.
+*/
+-->
+
+<Space xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="@dimen/navigation_side_padding"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    >
+</Space>
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index 8498a4f..36e937d 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -22,309 +22,17 @@
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
-    android:background="@drawable/system_bar_background"
-    >
+    android:background="@drawable/system_bar_background">
 
-    <FrameLayout android:id="@+id/rot0"
-        android:layout_height="match_parent"
+    <com.android.systemui.statusbar.phone.NavigationBarInflaterView
+        android:id="@+id/navigation_inflater"
         android:layout_width="match_parent"
-        >
+        android:layout_height="match_parent">
 
-        <LinearLayout
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            android:orientation="horizontal"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            android:id="@+id/nav_buttons"
-            android:animateLayoutChanges="true"
-            >
+        <include android:id="@+id/rot0" layout="@layout/navigation_layout" />
 
-            <!-- navigation controls -->
-            <View
-                android:layout_width="@dimen/navigation_side_padding"
-                android:layout_height="match_parent"
-                android:layout_weight="0"
-                android:visibility="invisible"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
-                android:layout_width="@dimen/navigation_key_width"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_back"
-                systemui:keyCode="4"
-                android:layout_weight="0"
-                android:scaleType="center"
-                android:contentDescription="@string/accessibility_back"
-                />
-            <View
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:visibility="invisible"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
-                android:layout_width="@dimen/navigation_key_width"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_home"
-                systemui:keyCode="3"
-                systemui:keyRepeat="false"
-                android:layout_weight="0"
-                android:scaleType="center"
-                android:contentDescription="@string/accessibility_home"
-                />
-            <View
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:visibility="invisible"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
-                android:layout_width="@dimen/navigation_key_width"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_recent"
-                android:layout_weight="0"
-                android:scaleType="center"
-                android:contentDescription="@string/accessibility_recent"
-                />
-            <FrameLayout
-                android:layout_width="@dimen/navigation_side_padding"
-                android:layout_height="match_parent"
-                android:layout_weight="0" >
-                <com.android.systemui.statusbar.policy.KeyButtonView
-                    android:id="@+id/menu"
-                    android:layout_width="@dimen/navigation_extra_key_width"
-                    android:layout_height="match_parent"
-                    android:contentDescription="@string/accessibility_menu"
-                    android:src="@drawable/ic_sysbar_menu"
-                    android:visibility="invisible"
-                    android:scaleType="centerInside"
-                    android:layout_gravity="end"
-                    systemui:keyCode="82" />
+        <include android:id="@+id/rot90" layout="@layout/navigation_layout_rot90" />
 
-                <com.android.systemui.statusbar.policy.KeyButtonView
-                    android:id="@+id/ime_switcher"
-                    android:layout_width="@dimen/navigation_extra_key_width"
-                    android:layout_height="match_parent"
-                    android:contentDescription="@string/accessibility_ime_switch_button"
-                    android:scaleType="centerInside"
-                    android:src="@drawable/ic_ime_switcher_default"
-                    android:visibility="invisible"
-                    android:layout_gravity="end" />
-            </FrameLayout>
-
-        </LinearLayout>
-
-        <!-- lights out layout to match exactly -->
-        <LinearLayout
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            android:orientation="horizontal"
-            android:id="@+id/lights_out"
-            android:visibility="gone"
-            >
-            <ImageView
-                android:layout_width="@dimen/navigation_key_width"
-                android:layout_height="match_parent"
-                android:layout_marginStart="@dimen/navigation_side_padding"
-                android:src="@drawable/ic_sysbar_lights_out_dot_small"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_back"
-                />
-            <View
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:visibility="invisible"
-                />
-            <ImageView
-                android:layout_width="@dimen/navigation_key_width"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_lights_out_dot_large"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_home"
-                />
-            <View
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:visibility="invisible"
-                />
-            <ImageView
-                android:layout_width="@dimen/navigation_key_width"
-                android:layout_marginEnd="@dimen/navigation_side_padding"
-                android:layout_height="match_parent"
-                android:src="@drawable/ic_sysbar_lights_out_dot_small"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_recent"
-                />
-        </LinearLayout>
-
-        <com.android.systemui.statusbar.policy.DeadZone
-            android:id="@+id/deadzone"
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            systemui:minSize="@dimen/navigation_bar_deadzone_size"
-            systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
-            systemui:holdTime="@integer/navigation_bar_deadzone_hold"
-            systemui:decayTime="@integer/navigation_bar_deadzone_decay"
-            systemui:orientation="horizontal"
-            android:layout_gravity="top"
-            />
-    </FrameLayout>
-
-    <FrameLayout android:id="@+id/rot90"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent"
-        android:visibility="gone"
-        android:paddingTop="0dp"
-        >
-
-        <LinearLayout
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            android:orientation="vertical"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            android:id="@+id/nav_buttons"
-            android:animateLayoutChanges="true"
-            >
-
-            <!-- navigation controls -->
-            <FrameLayout
-                android:layout_weight="0"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/navigation_side_padding" >
-                <com.android.systemui.statusbar.policy.KeyButtonView
-                    android:id="@+id/ime_switcher"
-                    android:layout_width="match_parent"
-                    android:layout_height="@dimen/navigation_extra_key_width"
-                    android:contentDescription="@string/accessibility_ime_switch_button"
-                    android:scaleType="centerInside"
-                    android:src="@drawable/ic_ime_switcher_default"
-                    android:layout_gravity="top"
-                    android:visibility="invisible" />
-
-                <com.android.systemui.statusbar.policy.KeyButtonView
-                    android:id="@+id/menu"
-                    android:layout_width="match_parent"
-                    android:layout_height="40dp"
-                    android:contentDescription="@string/accessibility_menu"
-                    android:src="@drawable/ic_sysbar_menu"
-                    android:scaleType="centerInside"
-                    android:layout_gravity="top"
-                    android:visibility="invisible"
-                    systemui:keyCode="82" />
-            </FrameLayout>
-
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
-                android:layout_height="@dimen/navigation_key_width"
-                android:layout_width="match_parent"
-                android:src="@drawable/ic_sysbar_recent"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_recent"
-                />
-            <View
-                android:layout_height="match_parent"
-                android:layout_width="match_parent"
-                android:layout_weight="1"
-                android:visibility="invisible"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
-                android:layout_height="@dimen/navigation_key_width"
-                android:layout_width="match_parent"
-                android:src="@drawable/ic_sysbar_home"
-                android:scaleType="center"
-                systemui:keyCode="3"
-                systemui:keyRepeat="false"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_home"
-                />
-            <View
-                android:layout_height="match_parent"
-                android:layout_width="match_parent"
-                android:layout_weight="1"
-                android:visibility="invisible"
-                />
-            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
-                android:layout_height="@dimen/navigation_key_width"
-                android:layout_width="match_parent"
-                android:src="@drawable/ic_sysbar_back"
-                android:scaleType="center"
-                systemui:keyCode="4"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_back"
-                />
-            <View
-                android:layout_height="@dimen/navigation_side_padding"
-                android:layout_width="match_parent"
-                android:layout_weight="0"
-                android:visibility="invisible"
-                />
-        </LinearLayout>
-
-        <!-- lights out layout to match exactly -->
-        <LinearLayout
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            android:orientation="vertical"
-            android:id="@+id/lights_out"
-            android:visibility="gone"
-            >
-            <ImageView
-                android:layout_height="@dimen/navigation_key_width"
-                android:layout_marginTop="@dimen/navigation_side_padding"
-                android:layout_width="match_parent"
-                android:src="@drawable/ic_sysbar_lights_out_dot_small"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_recent"
-                />
-            <View
-                android:layout_height="match_parent"
-                android:layout_width="match_parent"
-                android:layout_weight="1"
-                android:visibility="invisible"
-                />
-            <ImageView
-                android:layout_height="@dimen/navigation_key_width"
-                android:layout_width="match_parent"
-                android:src="@drawable/ic_sysbar_lights_out_dot_large"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_home"
-                />
-            <View
-                android:layout_height="match_parent"
-                android:layout_width="match_parent"
-                android:layout_weight="1"
-                android:visibility="invisible"
-                />
-            <ImageView
-                android:layout_height="@dimen/navigation_key_width"
-                android:layout_marginBottom="@dimen/navigation_side_padding"
-                android:layout_width="match_parent"
-                android:src="@drawable/ic_sysbar_lights_out_dot_small"
-                android:scaleType="center"
-                android:layout_weight="0"
-                android:contentDescription="@string/accessibility_back"
-                />
-        </LinearLayout>
-
-        <com.android.systemui.statusbar.policy.DeadZone
-            android:id="@+id/deadzone"
-            android:layout_height="match_parent"
-            android:layout_width="match_parent"
-            systemui:minSize="@dimen/navigation_bar_deadzone_size"
-            systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
-            systemui:holdTime="@integer/navigation_bar_deadzone_hold"
-            systemui:decayTime="@integer/navigation_bar_deadzone_decay"
-            systemui:orientation="vertical"
-            android:layout_gravity="top"
-            />
-    </FrameLayout>
+    </com.android.systemui.statusbar.phone.NavigationBarInflaterView>
 
 </com.android.systemui.statusbar.phone.NavigationBarView>
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
new file mode 100644
index 0000000..7ebf4ed
--- /dev/null
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -0,0 +1,91 @@
+<?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"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:id="@+id/start_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="start"
+            android:orientation="horizontal" />
+
+        <LinearLayout
+            android:id="@+id/center_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="horizontal" />
+
+        <LinearLayout
+            android:id="@+id/end_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="end"
+            android:orientation="horizontal" />
+
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/lights_out"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:id="@+id/start_group_lightsout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="start"
+            android:orientation="horizontal" />
+
+        <LinearLayout
+            android:id="@+id/center_group_lightsout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="horizontal" />
+
+        <LinearLayout
+            android:id="@+id/end_group_lightsout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="end"
+            android:orientation="horizontal" />
+
+    </FrameLayout>
+
+    <com.android.systemui.statusbar.policy.DeadZone
+        android:id="@+id/deadzone"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:layout_gravity="top"
+        systemui:minSize="@dimen/navigation_bar_deadzone_size"
+        systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+        systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+        systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+        systemui:orientation="horizontal"
+        />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_layout_rot90.xml b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
new file mode 100644
index 0000000..46df973
--- /dev/null
+++ b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
@@ -0,0 +1,91 @@
+<?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"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+            android:id="@+id/start_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="bottom"
+            android:orientation="vertical" />
+
+        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+            android:id="@+id/center_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="vertical" />
+
+        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+            android:id="@+id/end_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="top"
+            android:orientation="vertical" />
+
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/lights_out"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+            android:id="@+id/start_group_lightsout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="bottom"
+            android:orientation="vertical" />
+
+        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+            android:id="@+id/center_group_lightsout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="vertical" />
+
+        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+            android:id="@+id/end_group_lightsout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="top"
+            android:orientation="vertical" />
+
+    </FrameLayout>
+
+    <com.android.systemui.statusbar.policy.DeadZone
+        android:id="@+id/deadzone"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:layout_gravity="top"
+        systemui:minSize="@dimen/navigation_bar_deadzone_size"
+        systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+        systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+        systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+        systemui:orientation="vertical"
+        />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index 03451b4..e550d9c 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -99,7 +99,8 @@
                     android:src="@*android:drawable/ic_notification_block"
                     android:layout_gravity="center_vertical|start"
                     android:layout_width="24dp"
-                    android:layout_height="24dp" />
+                    android:layout_height="24dp"
+                    android:tint="@color/notification_guts_icon_tint"/>
 
             <SeekBar
                     android:id="@+id/seekbar"
@@ -121,7 +122,8 @@
                     android:src="@*android:drawable/ic_notification_alert"
                     android:layout_gravity="center_vertical|end"
                     android:layout_width="24dp"
-                    android:layout_height="24dp"/>
+                    android:layout_height="24dp"
+                    android:tint="@color/notification_guts_icon_tint" />
 
         </FrameLayout>
 
diff --git a/packages/SystemUI/res/layout/notification_public_default.xml b/packages/SystemUI/res/layout/notification_public_default.xml
deleted file mode 100644
index 044ba09..0000000
--- a/packages/SystemUI/res/layout/notification_public_default.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2014 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/status_bar_latest_event_content"
-    android:layout_width="match_parent"
-    android:layout_height="64dp"
-    >
-    <ImageView android:id="@+id/icon"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:layout_marginTop="12dp"
-        android:layout_marginStart="12dp"
-        android:layout_marginEnd="12dp"
-        android:scaleType="centerInside"
-        />
-    <DateTimeView android:id="@+id/time"
-        android:textAppearance="@android:style/TextAppearance.Material.Notification.Time"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="8dp"
-        android:layout_alignParentEnd="true"
-        android:layout_alignBaseline="@+id/title"
-        android:singleLine="true"
-        android:gravity="center"
-        android:paddingStart="8dp"
-        android:visibility="gone"
-        />
-    <TextView android:id="@+id/title"
-        android:textAppearance="@android:style/TextAppearance.Material.Notification.Title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_toEndOf="@id/icon"
-        android:layout_toStartOf="@id/time"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:fadingEdge="horizontal"
-        />
-    <ImageView android:id="@+id/profile_badge_line3"
-        android:layout_width="@*android:dimen/notification_badge_size"
-        android:layout_height="@*android:dimen/notification_badge_size"
-        android:layout_below="@id/title"
-        android:layout_marginStart="4dp"
-        android:layout_marginEnd="8dp"
-        android:layout_alignParentEnd="true"
-        android:scaleType="fitCenter"
-        android:visibility="gone"
-        />
-    <TextView android:id="@+id/text"
-        android:textAppearance="@android:style/TextAppearance.Material.Notification"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignStart="@id/title"
-        android:layout_below="@id/title"
-        android:layout_toStartOf="@id/profile_badge_line3"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:fadingEdge="horizontal"
-        />
-</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/recent_apps.xml b/packages/SystemUI/res/layout/recent_apps.xml
new file mode 100644
index 0000000..eb8ee43c
--- /dev/null
+++ b/packages/SystemUI/res/layout/recent_apps.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<com.android.systemui.statusbar.policy.KeyButtonView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/recent_apps"
+    android:layout_width="@dimen/navigation_key_width"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    android:src="@drawable/ic_sysbar_recent"
+    android:scaleType="center"
+    android:contentDescription="@string/accessibility_recent"
+    android:paddingStart="@dimen/navigation_key_padding"
+    android:paddingEnd="@dimen/navigation_key_padding"
+    />
+
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 07ac39a..5c67f80 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -23,12 +23,10 @@
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/icon"
         android:contentDescription="@string/recents_app_info_button_label"
-        android:layout_width="@dimen/recents_task_view_application_icon_size"
-        android:layout_height="@dimen/recents_task_view_application_icon_size"
-        android:layout_marginStart="8dp"
+        android:layout_width="@dimen/recents_task_view_header_icon_width"
+        android:layout_height="@dimen/recents_task_view_header_icon_height"
         android:layout_gravity="center_vertical|start"
-        android:padding="8dp"
-        android:background="@drawable/recents_button_bg" />
+        android:padding="9dp" />
     <TextView
         android:id="@+id/title"
         android:layout_width="match_parent"
@@ -46,30 +44,36 @@
         android:fadingEdge="horizontal" />
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/move_task"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginEnd="52dp"
+        android:layout_width="@dimen/recents_task_view_header_button_width"
+        android:layout_height="@dimen/recents_task_view_header_button_height"
+        android:layout_marginEnd="@dimen/recents_task_view_header_button_width"
         android:layout_gravity="center_vertical|end"
-        android:padding="12dp"
+        android:padding="15dp"
         android:background="@drawable/recents_button_bg"
         android:src="@drawable/star"
         android:visibility="gone" />
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/dismiss_task"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginEnd="4dp"
+        android:layout_width="@dimen/recents_task_view_header_button_width"
+        android:layout_height="@dimen/recents_task_view_header_button_height"
         android:layout_gravity="center_vertical|end"
-        android:padding="12dp"
+        android:padding="15dp"
         android:background="@drawable/recents_button_bg"
         android:visibility="invisible"
         android:src="@drawable/recents_dismiss_light" />
-    <ProgressBar
-        android:id="@+id/focus_timer_indicator"
-        style="?android:attr/progressBarStyleHorizontal"
-        android:layout_width="match_parent"
-        android:layout_height="5dp"
-        android:layout_gravity="bottom"
-        android:indeterminateOnly="false"
-        android:visibility="invisible" />
+
+    <!-- The progress indicator shows if auto-paging is enabled -->
+    <ViewStub android:id="@+id/focus_timer_indicator_stub"
+               android:inflatedId="@+id/focus_timer_indicator"
+               android:layout="@layout/recents_task_view_header_progress_bar"
+               android:layout_width="match_parent"
+               android:layout_height="5dp"
+               android:layout_gravity="bottom" />
+
+    <!-- The app overlay shows as the user long-presses on the app icon -->
+    <ViewStub android:id="@+id/app_overlay_stub"
+               android:inflatedId="@+id/app_overlay"
+               android:layout="@layout/recents_task_view_header_overlay"
+               android:layout_width="match_parent"
+               android:layout_height="match_parent" />
 </com.android.systemui.recents.views.TaskViewHeader>
diff --git a/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
new file mode 100644
index 0000000..dabfc80
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
@@ -0,0 +1,50 @@
+<?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_width="match_parent"
+    android:layout_height="match_parent">
+    <com.android.systemui.recents.views.FixedSizeImageView
+        android:id="@+id/app_icon"
+        android:contentDescription="@string/recents_app_info_button_label"
+        android:layout_width="@dimen/recents_task_view_header_icon_width"
+        android:layout_height="@dimen/recents_task_view_header_icon_height"
+        android:layout_gravity="center_vertical|start"
+        android:padding="9dp" />
+    <TextView
+        android:id="@+id/app_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical|start"
+        android:layout_marginStart="64dp"
+        android:layout_marginEnd="112dp"
+        android:textSize="16sp"
+        android:textColor="#ffffffff"
+        android:text="@string/recents_empty_message"
+        android:fontFamily="sans-serif-medium"
+        android:singleLine="true"
+        android:maxLines="2"
+        android:ellipsize="marquee"
+        android:fadingEdge="horizontal" />
+    <com.android.systemui.recents.views.FixedSizeImageView
+        android:id="@+id/app_info"
+        android:layout_width="@dimen/recents_task_view_header_button_width"
+        android:layout_height="@dimen/recents_task_bar_height"
+        android:layout_gravity="center_vertical|end"
+        android:padding="15dp"
+        android:background="@drawable/recents_button_bg"
+        android:src="@drawable/recents_info_light" />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml b/packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml
new file mode 100644
index 0000000..f352632
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<ProgressBar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="?android:attr/progressBarStyleHorizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:indeterminateOnly="false"
+    android:visibility="invisible" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
index 198e658..c634cd6 100644
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ b/packages/SystemUI/res/layout/signal_cluster_view.xml
@@ -77,7 +77,7 @@
     </FrameLayout>
     <View
         android:id="@+id/wifi_signal_spacer"
-        android:layout_width="4dp"
+        android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
         android:layout_height="4dp"
         android:visibility="gone"
         />
@@ -112,7 +112,7 @@
     </FrameLayout>
     <View
         android:id="@+id/wifi_airplane_spacer"
-        android:layout_width="4dp"
+        android:layout_width="@dimen/status_bar_airplane_spacer_width"
         android:layout_height="4dp"
         android:visibility="gone"
         />
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index bea9f78..39c16d7 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -70,7 +70,8 @@
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
                 android:singleLine="true"
-                android:paddingStart="7dp"
+                android:paddingStart="@dimen/status_bar_clock_starting_padding"
+                android:paddingEnd="@dimen/status_bar_clock_end_padding"
                 android:gravity="center_vertical|start"
                 />
         </com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index 943e846..e9448db 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -30,11 +30,11 @@
         android:id="@+id/signal_cluster"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="2.5dp"/>
+        android:layout_marginStart="@dimen/signal_cluster_margin_start"/>
 
     <!-- battery must be padded below to match assets -->
     <com.android.systemui.BatteryMeterView android:id="@+id/battery"
-        android:layout_height="14.5dp"
-        android:layout_width="9.5dp"
+        android:layout_height="@dimen/status_bar_battery_icon_height"
+        android:layout_width="@dimen/status_bar_battery_icon_width"
         android:layout_marginBottom="@dimen/battery_margin_bottom"/>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index f084bc2..be5b856 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -34,4 +34,7 @@
          In sw600dp we want the buttons centered so this fills the space,
          (screen_pinning_request_width - 3 * screen_pinning_request_button_width) / 2 -->
     <dimen name="screen_pinning_request_side_width">2dp</dimen>
+
+    <dimen name="navigation_key_width">162dp</dimen>
+    <dimen name="navigation_key_padding">42dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 6795da4..db4da10 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -35,11 +35,15 @@
 
     <!-- Recents: The relative range of visible tasks from the current scroll position
          while the stack is focused. -->
-    <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+    <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
     <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
 
     <!-- Recents: The relative range of visible tasks from the current scroll position
          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">2.5</item>
+
+    <!-- Nav bar button default ordering/layout -->
+    <string name="config_navBarLayout" translatable="false">space;back,home,recent;menu_ime</string>
+
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 49dbac2..71f92fd 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -90,4 +90,7 @@
     <dimen name="screen_pinning_request_side_width">8dp</dimen>
 
     <dimen name="fab_margin">24dp</dimen>
+
+    <dimen name="navigation_key_width">128dp</dimen>
+    <dimen name="navigation_key_padding">25dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index a80a5de..b2190ec 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -75,13 +75,13 @@
     <color name="notification_legacy_background_color">#ff1a1a1a</color>
 
     <!-- The color of the material notification background -->
-    <color name="notification_material_background_color">#fffafafa</color>
+    <color name="notification_material_background_color">#ffffffff</color>
 
     <!-- The color of the material notification background when dimmed -->
-    <color name="notification_material_background_dimmed_color">#d4ffffff</color>
+    <color name="notification_material_background_dimmed_color">#f2ffffff</color>
 
     <!-- The color of the material notification background when low priority -->
-    <color name="notification_material_background_low_priority_color">#ffe0e0e0</color>
+    <color name="notification_material_background_low_priority_color">#fff5f5f5</color>
 
     <!-- The color of the material notification background for media notifications when no custom
          color is specified -->
@@ -100,9 +100,11 @@
     <color name="current_user_border_color">@color/system_accent_color</color>
 
     <!-- The "inside" of a notification, reached via longpress -->
-    <color name="notification_guts_bg_color">@*android:color/material_grey_50</color>
+    <color name="notification_guts_bg_color">#eeeeee</color>
     <color name="notification_guts_slider_color">@*android:color/material_deep_teal_500</color>
     <color name="notification_guts_secondary_slider_color">#858383</color>
+    <color name="notification_guts_icon_tint">#8a000000</color>
+    <color name="notification_guts_disabled_icon_tint">#4d000000</color>
 
     <color name="assist_orb_color">#ffffff</color>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e98ec82..aedc2c5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -147,6 +147,9 @@
     <!-- The duration for animating the task decorations in after transitioning from an app. -->
     <integer name="recents_task_enter_from_app_duration">200</integer>
 
+    <!-- The duration for animating the task decorations in after transitioning from an app. -->
+    <integer name="recents_task_enter_from_affiliated_app_duration">125</integer>
+
     <!-- The duration for animating the task decorations out before transitioning to an app. -->
     <integer name="recents_task_exit_to_app_duration">125</integer>
 
@@ -194,7 +197,7 @@
 
     <!-- Recents: The relative range of visible tasks from the current scroll position
          while the stack is focused. -->
-    <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+    <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
     <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
 
     <!-- Recents: The relative range of visible tasks from the current scroll position
@@ -285,5 +288,8 @@
     <!-- SystemUIFactory component -->
     <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
 
+    <!-- Nav bar button default ordering/layout -->
+    <string name="config_navBarLayout" translatable="false">space,back;home;recent,menu_ime</string>
+
 </resources>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 47eb05a..46a0f2a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -33,9 +33,30 @@
     <!-- Height of notification icons in the status bar -->
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
 
-    <!-- The font size for the clock -->
+    <!-- Height of the battery icon in the status bar. -->
+    <dimen name="status_bar_battery_icon_height">14.5dp</dimen>
+
+    <!-- Width of the battery icon in the status bar. -->
+    <dimen name="status_bar_battery_icon_width">9.5dp</dimen>
+
+    <!-- The font size for the clock in the status bar. -->
     <dimen name="status_bar_clock_size">14sp</dimen>
 
+    <!-- The starting padding for the clock in the status bar. -->
+    <dimen name="status_bar_clock_starting_padding">7dp</dimen>
+
+    <!-- The end padding for the clock in the status bar. -->
+    <dimen name="status_bar_clock_end_padding">0dp</dimen>
+
+    <!-- Spacing after the wifi signals that is present if there are any icons following it. -->
+    <dimen name="status_bar_wifi_signal_spacer_width">4dp</dimen>
+
+    <!-- Spacing before the airplane mode icon if there are any icons preceding it. -->
+    <dimen name="status_bar_airplane_spacer_width">4dp</dimen>
+
+    <!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. -->
+    <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
+
     <!-- Height of a small notification in the status bar-->
     <dimen name="notification_min_height">84dp</dimen>
 
@@ -75,6 +96,11 @@
     <!-- The width of the view containing navigation buttons -->
     <dimen name="navigation_key_width">70dp</dimen>
 
+    <dimen name="navigation_key_padding">0dp</dimen>
+
+    <dimen name="navigation_key_width_sw600dp_land">162dp</dimen>
+    <dimen name="navigation_key_padding_sw600dp_land">42dp</dimen>
+
     <!-- The width of the view containing the menu/ime navigation bar icons -->
     <dimen name="navigation_extra_key_width">36dp</dimen>
 
@@ -191,8 +217,13 @@
     <!-- Default distance from each snap target that GlowPadView considers a "hit" -->
     <dimen name="glowpadview_inner_radius">15dip</dimen>
 
-    <!-- The size of the application icon in the recents task view. -->
-    <dimen name="recents_task_view_application_icon_size">48dp</dimen>
+    <!-- The size of the icon in the recents task view header. -->
+    <dimen name="recents_task_view_header_icon_width">64dp</dimen>
+    <dimen name="recents_task_view_header_icon_height">@dimen/recents_task_bar_height</dimen>
+
+    <!-- The size of a button in the recents task view header. -->
+    <dimen name="recents_task_view_header_button_width">@dimen/recents_task_bar_height</dimen>
+    <dimen name="recents_task_view_header_button_height">@dimen/recents_task_bar_height</dimen>
 
     <!-- The radius of the rounded corners on a task view. -->
     <dimen name="recents_task_view_rounded_corners_radius">2dp</dimen>
@@ -210,7 +241,7 @@
     <dimen name="recents_task_view_highlight">1dp</dimen>
 
     <!-- The amount to offset when animating into an affiliate group. -->
-    <dimen name="recents_task_view_affiliate_group_enter_offset">64dp</dimen>
+    <dimen name="recents_task_view_affiliate_group_enter_offset">32dp</dimen>
 
     <!-- The height of a task view bar. -->
     <dimen name="recents_task_bar_height">56dp</dimen>
@@ -516,6 +547,9 @@
 
     <dimen name="fake_shadow_size">8dp</dimen>
 
+    <!-- Starting margin before the signal cluster -->
+    <dimen name="signal_cluster_margin_start">2.5dp</dimen>
+
     <!-- Padding between signal cluster and battery icon -->
     <dimen name="signal_cluster_battery_padding">7dp</dimen>
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 13128b7..0bd350b 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -38,8 +38,8 @@
     <item type="id" name="qs_icon_tag"/>
     <item type="id" name="scrim"/>
     <item type="id" name="scrim_target"/>
-    <item type="id" name="hun_scrim_alpha_start"/>
-    <item type="id" name="hun_scrim_alpha_end"/>
+    <item type="id" name="scrim_alpha_start"/>
+    <item type="id" name="scrim_alpha_end"/>
     <item type="id" name="notification_power"/>
     <item type="id" name="notification_screenshot"/>
     <item type="id" name="notification_hidden"/>
@@ -50,6 +50,11 @@
 
     <!-- For notification icons for which targetSdk < L, this caches whether the icon is grayscale -->
     <item type="id" name="icon_is_grayscale" />
+    <item type="id" name="clip_children_tag" />
+    <item type="id" name="clip_children_set_tag" />
+    <item type="id" name="clip_to_padding_tag" />
+    <item type="id" name="image_icon_tag" />
+    <item type="id" name="contains_transformed_view" />
     <item type="id" name="is_clicked_heads_up_tag" />
 </resources>
 
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index d28da41..fd4161f 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -367,6 +367,7 @@
         }
         anim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
+                updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
                 mCallback.onChildDismissed(view);
                 if (endAction != null) {
                     endAction.run();
@@ -381,9 +382,17 @@
                 updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
             }
         });
+        prepareDismissAnimation(animView, anim);
         anim.start();
     }
 
+    /**
+     * Called to update the dismiss animation.
+     */
+    protected void prepareDismissAnimation(View view, Animator anim) {
+        // Do nothing
+    }
+
     public void snapChild(final View view, float velocity) {
         final View animView = mCallback.getChildContentView(view);
         final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(animView);
@@ -401,14 +410,14 @@
                 mCallback.onChildSnappedBack(animView);
             }
         });
-        updateSnapBackAnimation(anim);
+        prepareSnapBackAnimation(animView, anim);
         anim.start();
     }
 
     /**
      * Called to update the snap back animation.
      */
-    protected void updateSnapBackAnimation(Animator anim) {
+    protected void prepareSnapBackAnimation(View view, Animator anim) {
         // Do nothing
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index db55f28..3a3b19d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -183,7 +183,7 @@
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         if (!plan.hasTasks()) {
-            loader.preloadTasks(plan, launchState.launchedFromHome);
+            loader.preloadTasks(plan, -1, launchState.launchedFromHome);
         }
         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
         loadOpts.runningTaskId = launchState.launchedToTaskId;
@@ -192,24 +192,14 @@
         loader.loadTasks(this, plan, loadOpts);
 
         TaskStack stack = plan.getTaskStack();
-        ArrayList<Task> tasks = stack.getStackTasks();
-        int taskCount = stack.getTaskCount();
         mRecentsView.setTaskStack(stack);
 
-        // Mark the task that is the launch target
-        int launchTaskIndexInStack = 0;
-        if (launchState.launchedToTaskId != -1) {
-            for (int j = 0; j < taskCount; j++) {
-                Task t = tasks.get(j);
-                if (t.key.id == launchState.launchedToTaskId) {
-                    t.isLaunchTarget = true;
-                    launchTaskIndexInStack = tasks.size() - j - 1;
-                    break;
-                }
-            }
-        }
-
         // Animate the SystemUI scrims into view
+        Task launchTarget = stack.getLaunchTarget();
+        int taskCount = stack.getTaskCount();
+        int launchTaskIndexInStack = launchTarget != null
+                ? stack.indexOfStackTask(launchTarget)
+                : 0;
         boolean hasStatusBarScrim = taskCount > 0;
         boolean animateStatusBarScrim = launchState.launchedFromHome;
         boolean hasNavBarScrim = (taskCount > 0) && !config.hasTransposedNavBar;
@@ -527,7 +517,7 @@
             launchOpts.loadThumbnails = false;
             launchOpts.onlyLoadForCache = true;
             RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
-            loader.preloadTasks(loadPlan, false);
+            loader.preloadTasks(loadPlan, -1, false);
             loader.loadTasks(this, loadPlan, launchOpts);
             EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack()));
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 3151fd7..881aa6a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -37,6 +37,8 @@
         public static final boolean EnableSearchBar = false;
         // This disables the bitmap and icon caches
         public static final boolean DisableBackgroundCache = false;
+        // Enables the task affiliations
+        public static final boolean EnableAffiliatedTaskGroups = true;
         // Enables the simulated task affiliations
         public static final boolean EnableSimulatedTaskGroups = false;
         // Defines the number of mock task affiliations per group
@@ -75,7 +77,7 @@
      * @return whether we are enabling the fast toggle indicator.
      */
     public boolean isFastToggleIndicatorEnabled() {
-        return mFastToggleIndicator;
+        return mFastToggleRecents && mFastToggleIndicator;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 7c25d24..f8cbf65 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -118,7 +118,7 @@
 
                 // Load the next task only if we aren't svelte
                 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-                loader.preloadTasks(plan, true);
+                loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
                 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
                 // This callback is made when a new activity is launched and the old one is paused
                 // so ignore the current activity and try and preload the thumbnail for the
@@ -198,7 +198,7 @@
         // We can use a new plan since the caches will be the same.
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, true /* isTopTaskHome */);
+        loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
         launchOpts.numVisibleTasks = loader.getIconCacheSize();
         launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
@@ -370,7 +370,7 @@
         sInstanceLoadPlan = loader.createLoadPlan(mContext);
         if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
             sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
-            loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
+            loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value);
             TaskStack stack = sInstanceLoadPlan.getTaskStack();
             if (stack.getTaskCount() > 0) {
                 // We try and draw the thumbnail transition bitmap in parallel before
@@ -399,7 +399,7 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, true /* isTopTaskHome */);
+        loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
@@ -451,7 +451,7 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, true /* isTopTaskHome */);
+        loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
@@ -651,7 +651,7 @@
         mDummyStackView.updateLayoutForStack(stack);
         final Task toTask = new Task();
         final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
-                topTask.id, toTask);
+                toTask);
         ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
             @Override
             public void run() {
@@ -721,7 +721,7 @@
             // Update the destination rect
             Task toTask = new Task();
             TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
-                    topTask.id, toTask);
+                    toTask);
             RectF toTaskRect = toTransform.rect;
             Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
             if (thumbnail != null) {
@@ -754,31 +754,20 @@
      * Returns the transition rect for the given task id.
      */
     private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
-            TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
+            TaskStackView stackView, Task runningTaskOut) {
         // Find the running task in the TaskStack
-        Task task = null;
-        ArrayList<Task> tasks = stack.getStackTasks();
-        if (runningTaskId != -1) {
-            // Otherwise, try and find the task with the
-            int taskCount = tasks.size();
-            for (int i = taskCount - 1; i >= 0; i--) {
-                Task t = tasks.get(i);
-                if (t.key.id == runningTaskId) {
-                    task = t;
-                    runningTaskOut.copyFrom(t);
-                    break;
-                }
-            }
-        }
-        if (task == null) {
+        Task launchTask = stack.getLaunchTarget();
+        if (launchTask != null) {
+            runningTaskOut.copyFrom(launchTask);
+        } else {
             // If no task is specified or we can not find the task just use the front most one
-            task = tasks.get(tasks.size() - 1);
-            runningTaskOut.copyFrom(task);
+            launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
+            runningTaskOut.copyFrom(launchTask);
         }
 
         // Get the transform for the running task
         stackView.getScroller().setStackScrollToInitialState();
-        mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
+        mTmpTransform = stackView.getStackAlgorithm().getStackTransform(launchTask,
                 stackView.getScroller().getStackScroll(), mTmpTransform, null);
         return mTmpTransform;
     }
@@ -826,7 +815,7 @@
             sInstanceLoadPlan = loader.createLoadPlan(mContext);
         }
         if (mReloadTasks || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
-            loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
+            loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
         }
         TaskStack stack = sInstanceLoadPlan.getTaskStack();
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index 80597bc..f6655c7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -36,6 +36,7 @@
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskViewAnimation;
 
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -143,7 +144,6 @@
 
     private Context mContext;
     private LayoutInflater mLayoutInflater;
-    private final List<Task> mTasks = new ArrayList<>();
     private final List<Row> mRows = new ArrayList<>();
     private final SparseIntArray mTaskRowCount = new SparseIntArray();
     private TaskStack mStack;
@@ -268,8 +268,8 @@
 
     public void onTaskRemoved(Task task, int position) {
         // Since this is removed from the history, we need to update the stack as well to ensure
-        // that the model is correct
-        mStack.removeTask(task);
+        // that the model is correct. Since the stack is hidden, we can update it immediately.
+        mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
         removeTaskRow(position);
         if (mRows.isEmpty()) {
             dismissHistory();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
new file mode 100644
index 0000000..72511de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
@@ -0,0 +1,52 @@
+/*
+ * 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.recents.misc;
+
+import android.animation.TypeEvaluator;
+import android.graphics.RectF;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>RectF</code> values.
+ */
+public class RectFEvaluator implements TypeEvaluator<RectF> {
+
+    private RectF mRect = new RectF();
+
+    /**
+     * This function returns the result of linearly interpolating the start and
+     * end Rect values, with <code>fraction</code> representing the proportion
+     * between the start and end values. The calculation is a simple parametric
+     * calculation on each of the separate components in the Rect objects
+     * (left, top, right, and bottom).
+     *
+     * <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p>
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start Rect
+     * @param endValue   The end Rect
+     * @return A linear interpolation between the start and end values, given the
+     *         <code>fraction</code> parameter.
+     */
+    @Override
+    public RectF evaluate(float fraction, RectF startValue, RectF endValue) {
+        float left = startValue.left + ((endValue.left - startValue.left) * fraction);
+        float top = startValue.top + ((endValue.top - startValue.top) * fraction);
+        float right = startValue.right + ((endValue.right - startValue.right) * fraction);
+        float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction);
+        mRect.set(left, top, right, bottom);
+        return mRect;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index adc1e92..3f52ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -554,39 +555,41 @@
         }
     }
 
-    /** Returns the activity label */
-    public String getActivityLabel(ActivityInfo info) {
+    /**
+     * Returns the activity label, badging if necessary.
+     */
+    public String getBadgedActivityLabel(ActivityInfo info, int userId) {
         if (mPm == null) return null;
 
         // If we are mocking, then return a mock label
         if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
-            return "Recent Task";
+            return "Recent Task: " + userId;
         }
 
-        return info.loadLabel(mPm).toString();
+        return getBadgedLabel(info.loadLabel(mPm).toString(), userId);
     }
 
-    /** Returns the application label */
-    public String getApplicationLabel(Intent baseIntent, int userId) {
+    /**
+     * Returns the application label, badging if necessary.
+     */
+    public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
         if (mPm == null) return null;
 
         // If we are mocking, then return a mock label
         if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
-            return "Recent Task";
+            return "Recent Task App: " + userId;
         }
 
-        ResolveInfo ri = mPm.resolveActivityAsUser(baseIntent, 0, userId);
-        CharSequence label = (ri != null) ? ri.loadLabel(mPm) : null;
-        return (label != null) ? label.toString() : null;
+        return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId);
     }
 
-    /** Returns the content description for a given task */
-    public String getContentDescription(Intent baseIntent, int userId, String activityLabel,
-            Resources res) {
-        String applicationLabel = getApplicationLabel(baseIntent, userId);
-        if (applicationLabel == null) {
-            return getBadgedLabel(activityLabel, userId);
-        }
+    /**
+     * Returns the content description for a given task, badging it if necessary.  The content
+     * description joins the app and activity labels.
+     */
+    public String getBadgedContentDescription(ActivityInfo info, int userId, Resources res) {
+        String activityLabel = info.loadLabel(mPm).toString();
+        String applicationLabel = info.applicationInfo.loadLabel(mPm).toString();
         String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
         return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
                 : res.getString(R.string.accessibility_recents_task_header,
@@ -610,6 +613,22 @@
     }
 
     /**
+     * Returns the application icon for the ApplicationInfo for a user, badging if
+     * necessary.
+     */
+    public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
+        if (mPm == null) return null;
+
+        // If we are mocking, then return a mock label
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
+            return new ColorDrawable(0xFF666666);
+        }
+
+        Drawable icon = appInfo.loadIcon(mPm);
+        return getBadgedIcon(icon, userId);
+    }
+
+    /**
      * Returns the task description icon, loading and badging it if it necessary.
      */
     public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 822ad77..9cdd703 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -25,7 +25,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArraySet;
-
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
@@ -92,7 +93,7 @@
     }
 
     /**
-     * An optimization to preload the raw list of tasks.  The raw tasks are saved in least-recent
+     * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
      * to most-recent order.
      */
     public synchronized void preloadRawTasks(boolean isTopTaskHome) {
@@ -107,7 +108,7 @@
     }
 
     /**
-     * Preloads the list of recent tasks from the system.  After this call, the TaskStack will
+     * Preloads the list of recent tasks from the system. After this call, the TaskStack will
      * have a list of all the recent tasks with their metadata, not including icons or
      * thumbnails which were not cached and have to be loaded.
      *
@@ -115,13 +116,16 @@
      * - least-recent to most-recent stack tasks
      * - least-recent to most-recent freeform tasks
      */
-    public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
+    public synchronized void preloadPlan(RecentsTaskLoader loader, int topTaskId,
+            boolean isTopTaskHome) {
         Resources res = mContext.getResources();
         ArrayList<Task> allTasks = new ArrayList<>();
         if (mRawTasks == null) {
             preloadRawTasks(isTopTaskHome);
         }
 
+        SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
+        SparseIntArray affiliatedTaskCounts = new SparseIntArray();
         String dismissDescFormat = mContext.getString(
                 R.string.accessibility_recents_item_will_be_dismissed);
         long lastStackActiveTime = Prefs.getLong(mContext,
@@ -131,6 +135,17 @@
         for (int i = 0; i < taskCount; i++) {
             ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
 
+            // Affiliated tasks are returned in a specific order from ActivityManager but without a
+            // lastActiveTime since it hasn't yet been started. However, we later sort the task list
+            // by lastActiveTime, which rearranges the tasks. For now, we need to workaround this
+            // by updating the lastActiveTime of this task to the lastActiveTime of the task it is
+            // affiliated with, in the same order that we encounter it in the original list (just
+            // its index in the task group for the task it is affiliated with).
+            if (t.persistentId != t.affiliatedTaskId) {
+                t.lastActiveTime = affiliatedTasks.get(t.affiliatedTaskId).lastActiveTime +
+                        affiliatedTaskCounts.get(t.affiliatedTaskId, 0) + 1;
+            }
+
             // Compose the task key
             Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
                     t.userId, t.firstActiveTime, t.lastActiveTime);
@@ -140,13 +155,14 @@
             boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
             boolean isStackTask = isFreeformTask || (!isHistoricalTask(t) ||
                     (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS)));
+            boolean isLaunchTarget = taskKey.id == topTaskId;
             if (isStackTask && newLastStackActiveTime < 0) {
                 newLastStackActiveTime = t.lastActiveTime;
             }
 
             // Load the title, icon, and color
             String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
-            String contentDescription = loader.getAndUpdateContentDescription(taskKey, title, res);
+            String contentDescription = loader.getAndUpdateContentDescription(taskKey, res);
             String dismissDescription = String.format(dismissDescFormat, contentDescription);
             Drawable icon = isStackTask
                     ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
@@ -157,9 +173,11 @@
             // Add the task to the stack
             Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
                     thumbnail, title, contentDescription, dismissDescription, activityColor,
-                    !isStackTask, t.bounds, t.taskDescription);
+                    !isStackTask, isLaunchTarget, t.bounds, t.taskDescription);
 
             allTasks.add(task);
+            affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
+            affiliatedTasks.put(taskKey.id, taskKey);
         }
         if (newLastStackActiveTime != -1) {
             Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 28338d83..44ad239 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -314,8 +314,8 @@
     }
 
     /** Preloads recents tasks using the specified plan to store the output. */
-    public void preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome) {
-        plan.preloadPlan(this, isTopTaskHome);
+    public void preloadTasks(RecentsTaskLoadPlan plan, int topTaskId, boolean isTopTaskHome) {
+        plan.preloadPlan(this, topTaskId, isTopTaskHome);
     }
 
     /** Begins loading the heavy task data according to the specified options. */
@@ -436,7 +436,7 @@
         // All short paths failed, load the label from the activity info and cache it
         ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
         if (activityInfo != null) {
-            label = ssp.getActivityLabel(activityInfo);
+            label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
             mActivityLabelCache.put(taskKey, label);
             return label;
         }
@@ -449,8 +449,7 @@
      * Returns the cached task content description if the task key is not expired, updating the
      * cache if it is.
      */
-    String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
-            Resources res) {
+    String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Return the cached content description if it exists
@@ -458,13 +457,11 @@
         if (label != null) {
             return label;
         }
-        // If the given activity label is empty, don't compute or cache the content description
-        if (activityLabel.isEmpty()) {
-            return "";
-        }
 
-        label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
-        if (label != null) {
+        // All short paths failed, load the label from the activity info and cache it
+        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
+        if (activityInfo != null) {
+            label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, res);
             mContentDescriptionCache.put(taskKey, label);
             return label;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 29e7077..193bd17 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -154,7 +154,8 @@
     public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
                 Bitmap thumbnail, String title, String contentDescription,
                 String dismissDescription, int colorPrimary, boolean isHistorical,
-                Rect bounds, ActivityManager.TaskDescription taskDescription) {
+                boolean isLaunchTarget, Rect bounds,
+                ActivityManager.TaskDescription taskDescription) {
         boolean isInAffiliationGroup = (affiliationTaskId != key.id);
         boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
         this.key = key;
@@ -170,6 +171,7 @@
                 Color.WHITE) > 3f;
         this.bounds = bounds;
         this.taskDescription = taskDescription;
+        this.isLaunchTarget = isLaunchTarget;
         this.isHistorical = isHistorical;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 327cdf8..de1daa8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -42,6 +42,7 @@
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.views.DropTarget;
+import com.android.systemui.recents.views.TaskViewAnimation;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -226,12 +227,12 @@
          * Notifies when a task has been removed from the stack.
          */
         void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
-            Task newFrontMostTask);
+            Task newFrontMostTask, TaskViewAnimation animation);
 
         /**
          * Notifies when a task has been removed from the history.
          */
-        void onHistoryTaskRemoved(TaskStack stack, Task removedTask);
+        void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation);
     }
 
     /**
@@ -520,21 +521,24 @@
         }
     }
 
-    /** Removes a task */
-    public void removeTask(Task t) {
+    /**
+     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+     * how they should update themselves.
+     */
+    public void removeTask(Task t, TaskViewAnimation animation) {
         if (mStackTaskList.contains(t)) {
-            boolean wasFrontMostTask = (getStackFrontMostTask() == t);
+            boolean wasFrontMostTask = (getStackFrontMostTask(false /* includeFreeform */) == t);
             removeTaskImpl(mStackTaskList, t);
-            Task newFrontMostTask = getStackFrontMostTask();
+            Task newFrontMostTask = getStackFrontMostTask(false  /* includeFreeform */);
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask);
+                mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask, animation);
             }
         } else if (mHistoryTaskList.contains(t)) {
             removeTaskImpl(mHistoryTaskList, t);
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onHistoryTaskRemoved(this, t);
+                mCb.onHistoryTaskRemoved(this, t, animation);
             }
         }
         mRawTaskList.remove(t);
@@ -564,7 +568,8 @@
             Task task = mRawTaskList.get(i);
             if (!newTasksMap.containsKey(task.key)) {
                 if (notifyStackChanges) {
-                    mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null);
+                    mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null,
+                            TaskViewAnimation.IMMEDIATE);
                 }
             }
             task.setGroup(null);
@@ -611,14 +616,14 @@
     /**
      * Gets the front-most task in the stack.
      */
-    public Task getStackFrontMostTask() {
+    public Task getStackFrontMostTask(boolean includeFreeformTasks) {
         ArrayList<Task> stackTasks = mStackTaskList.getTasks();
         if (stackTasks.isEmpty()) {
             return null;
         }
         for (int i = stackTasks.size() - 1; i >= 0; i--) {
             Task task = stackTasks.get(i);
-            if (!task.isFreeformTask()) {
+            if (!task.isFreeformTask() || includeFreeformTasks) {
                 return task;
             }
         }
@@ -843,12 +848,17 @@
             for (int i = 0; i < taskCount; i++) {
                 Task t = tasks.get(i);
                 TaskGrouping group;
-                int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
-                        IndividualTaskIdOffset + t.key.id;
-                if (mAffinitiesGroups.containsKey(affiliation)) {
-                    group = getGroupWithAffiliation(affiliation);
+                if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
+                    int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
+                            IndividualTaskIdOffset + t.key.id;
+                    if (mAffinitiesGroups.containsKey(affiliation)) {
+                        group = getGroupWithAffiliation(affiliation);
+                    } else {
+                        group = new TaskGrouping(affiliation);
+                        addGroup(group);
+                    }
                 } else {
-                    group = new TaskGrouping(affiliation);
+                    group = new TaskGrouping(t.key.id);
                     addGroup(group);
                 }
                 group.addTask(t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index e727652..e448101 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -513,7 +513,7 @@
                     taskViewRect.right, taskViewRect.bottom);
 
             // Remove the task view after it is docked
-            mTaskStackView.updateLayout(false /* boundScroll */);
+            mTaskStackView.updateLayoutAlgorithm(false /* boundScroll */);
             stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform,
                     null);
             tmpTransform.alpha = 0;
@@ -529,11 +529,14 @@
                                     ssp.startTaskInDockedMode(getContext(), event.taskView,
                                             event.task.key.id, dockState.createMode);
 
-                                    mTaskStackView.getStack().removeTask(event.task);
+                                    // Animate the stack accordingly
+                                    TaskViewAnimation stackAnim = new TaskViewAnimation(
+                                            TaskStackView.DEFAULT_SYNC_STACK_DURATION,
+                                            mFastOutSlowInInterpolator);
+                                    mTaskStackView.getStack().removeTask(event.task, stackAnim);
                                 }
                             }));
 
-
             MetricsLogger.action(mContext,
                     MetricsLogger.ACTION_WINDOW_DOCK_DRAG_DROP);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 2930f4d..ccbb329 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -130,6 +130,7 @@
                     // Move the task view slightly lower so we can animate it in
                     RectF bounds = new RectF(mTmpTransform.rect);
                     bounds.offset(0, taskViewAffiliateGroupEnterOffset);
+                    tv.setClipViewInStack(false);
                     tv.setAlpha(0f);
                     tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top,
                             (int) bounds.right, (int) bounds.bottom);
@@ -165,6 +166,8 @@
 
         int taskViewEnterFromAppDuration = res.getInteger(
                 R.integer.recents_task_enter_from_app_duration);
+        int taskViewEnterFromAffiliatedAppDuration = res.getInteger(
+                R.integer.recents_task_enter_from_affiliated_app_duration);
         int taskViewEnterFromHomeDuration = res.getInteger(
                 R.integer.recents_task_enter_from_home_duration);
         int taskViewEnterFromHomeStaggerDelay = res.getInteger(
@@ -174,7 +177,7 @@
         List<TaskView> taskViews = mStackView.getTaskViews();
         int taskViewCount = taskViews.size();
         for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
+            final TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
             boolean currentTaskOccludesLaunchTarget = false;
             if (launchTargetTask != null) {
@@ -195,8 +198,14 @@
                     // Animate the task up if it was occluding the launch target
                     if (currentTaskOccludesLaunchTarget) {
                         TaskViewAnimation taskAnimation = new TaskViewAnimation(
-                                taskViewEnterFromAppDuration, PhoneStatusBar.ALPHA_IN,
-                                postAnimationTrigger.decrementOnAnimationEnd());
+                                taskViewEnterFromAffiliatedAppDuration, PhoneStatusBar.ALPHA_IN,
+                                new AnimatorListenerAdapter() {
+                                    @Override
+                                    public void onAnimationEnd(Animator animation) {
+                                        postAnimationTrigger.decrement();
+                                        tv.setClipViewInStack(false);
+                                    }
+                                });
                         postAnimationTrigger.increment();
                         mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
                     }
@@ -286,7 +295,7 @@
             } else if (currentTaskOccludesLaunchTarget) {
                 // Animate this task out of view
                 TaskViewAnimation taskAnimation = new TaskViewAnimation(
-                        taskViewExitToAppDuration, mFastOutLinearInInterpolator,
+                        taskViewExitToAppDuration, PhoneStatusBar.ALPHA_OUT,
                         postAnimationTrigger.decrementOnAnimationEnd());
                 postAnimationTrigger.increment();
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 68ff63c..e99509c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -373,7 +373,7 @@
      * Computes the minimum and maximum scroll progress values and the progress values for each task
      * in the stack.
      */
-    void update(TaskStack stack, ArraySet<Task> ignoreTasksSet) {
+    void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Clear the progress map
@@ -393,7 +393,7 @@
         ArrayList<Task> stackTasks = new ArrayList<>();
         for (int i = 0; i < tasks.size(); i++) {
             Task task = tasks.get(i);
-            if (ignoreTasksSet.contains(task)) {
+            if (ignoreTasksSet.contains(task.key)) {
                 continue;
             }
             if (task.isFreeformTask()) {
@@ -434,19 +434,25 @@
             mFreeformLayoutAlgorithm.update(freeformTasks, this);
             mInitialScrollP = mMaxScrollP;
         } else {
+            Task launchTask = stack.getLaunchTarget();
+            int launchTaskIndex = launchTask != null
+                    ? stack.indexOfStackTask(launchTask)
+                    : mNumStackTasks - 1;
             if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
                 mInitialScrollP = mMinScrollP;
             } else if (getDefaultFocusState() > 0f) {
                 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
                 if (launchState.launchedFromHome) {
-                    mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
+                    mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, launchTaskIndex));
                 } else {
-                    mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 2);
+                    mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP,
+                            launchTaskIndex - 1));
                 }
             } else {
                 float offsetPct = (float) (mTaskRect.height() / 2) / mStackRect.height();
                 float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
-                mInitialScrollP = (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX);
+                mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP,
+                        launchTaskIndex - mUnfocusedRange.getAbsoluteX(normX)));
             }
         }
     }
@@ -553,7 +559,8 @@
 
             boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
             if (isFrontMostTaskInGroup) {
-                getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null);
+                getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null,
+                        false /* ignoreSingleTaskCase */);
                 float screenY = tmpTransform.rect.top;
                 boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
                 if (hasVisibleThumbnail) {
@@ -596,13 +603,21 @@
                 return transformOut;
             }
             return getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
-                    frontTransform);
+                    frontTransform, false /* ignoreSingleTaskCase */);
         }
     }
 
-    /** Update/get the transform */
+    /**
+     * Update/get the transform.
+     *
+     * @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
+     *                             into account the special single-task case.  This is only used
+     *                             internally to ensure that we can calculate the transform for any
+     *                             position in the stack.
+     */
     public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
-            TaskViewTransform transformOut, TaskViewTransform frontTransform) {
+            TaskViewTransform transformOut, TaskViewTransform frontTransform,
+            boolean ignoreSingleTaskCase) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Compute the focused and unfocused offset
@@ -632,7 +647,7 @@
         int y;
         float z;
         float relP;
-        if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+        if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
             // When there is exactly one task, then decouple the task from the stack and just move
             // in screen space
             p = (mMinScrollP - stackScroll) / mNumStackTasks;
@@ -762,8 +777,8 @@
                 mFocusState * (mFocusedRange.relativeMin - mUnfocusedRange.relativeMin);
         float max = mUnfocusedRange.relativeMax +
                 mFocusState * (mFocusedRange.relativeMax - mUnfocusedRange.relativeMax);
-        getStackTransform(min, 0f, mBackOfStackTransform, null);
-        getStackTransform(max, 0f, mFrontOfStackTransform, null);
+        getStackTransform(min, 0f, mBackOfStackTransform, null, true /* ignoreSingleTaskCase */);
+        getStackTransform(max, 0f, mFrontOfStackTransform, null, true /* ignoreSingleTaskCase */);
         mBackOfStackTransform.visible = true;
         mFrontOfStackTransform.visible = true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 7583a19..de045f4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -30,6 +30,7 @@
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.MutableBoolean;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -77,7 +78,6 @@
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -100,10 +100,12 @@
     private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
     private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
 
-    private static final int DEFAULT_SYNC_STACK_DURATION = 200;
+    public static final int DEFAULT_SYNC_STACK_DURATION = 200;
     private static final int DRAG_SCALE_DURATION = 175;
     private static final float DRAG_SCALE_FACTOR = 1.05f;
 
+    private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
+
     TaskStack mStack;
     TaskStackLayoutAlgorithm mLayoutAlgorithm;
     TaskStackViewScroller mStackScroller;
@@ -116,7 +118,8 @@
 
     ArrayList<TaskView> mTaskViews = new ArrayList<>();
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
-    TaskViewAnimation mDeferredTaskViewUpdateAnimation = null;
+    ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>();
+    TaskViewAnimation mDeferredTaskViewLayoutAnimation = null;
 
     DozeTrigger mUIDozeTrigger;
     Task mFocusedTask;
@@ -137,7 +140,6 @@
     int[] mTmpVisibleRange = new int[2];
     Rect mTmpRect = new Rect();
     ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
-    ArraySet<Task> mTmpTaskSet = new ArraySet<>();
     List<TaskView> mTmpTaskViews = new ArrayList<>();
     TaskViewTransform mTmpTransform = new TaskViewTransform();
     LayoutInflater mInflater;
@@ -345,30 +347,82 @@
     }
 
     /**
-     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
-     * This call ignores freeform tasks.
+     * Adds a task to the ignored set.
      */
-    private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
-            ArrayList<Task> tasks, float stackScroll,
-            int[] visibleRangeOut, ArraySet<Task> ignoreTasksSet) {
-        int taskTransformCount = taskTransforms.size();
+    void addIgnoreTask(Task task) {
+        mIgnoreTasks.add(task.key);
+    }
+
+    /**
+     * Removes a task from the ignored set.
+     */
+    void removeIgnoreTask(Task task) {
+        mIgnoreTasks.remove(task.key);
+    }
+
+    /**
+     * Returns whether the specified {@param task} is ignored.
+     */
+    boolean isIgnoredTask(Task task) {
+        return mIgnoreTasks.contains(task.key);
+    }
+
+    /**
+     * Computes the task transforms at the current stack scroll for all visible tasks. If a valid
+     * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the
+     * visible range includes all tasks at the target stack scroll. This is useful for ensure that
+     * all views necessary for a transition or animation will be visible at the start.
+     *
+     * This call ignores freeform tasks.
+     *
+     * @param taskTransforms The set of task view transforms to reuse, this list will be sized to
+     *                       match the size of {@param tasks}
+     * @param tasks The set of tasks for which to generate transforms
+     * @param curStackScroll The current stack scroll
+     * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to.
+     *                          The range of the union of the visible views at the current and
+     *                          target stack scrolls will be returned.
+     * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range.
+     *                       Transforms will still be calculated for the ignore tasks.
+     */
+    boolean computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms,
+            ArrayList<Task> tasks, float curStackScroll, float targetStackScroll,
+            int[] visibleRangeOut, ArraySet<Task.TaskKey> ignoreTasksSet) {
         int taskCount = tasks.size();
         int frontMostVisibleIndex = -1;
         int backMostVisibleIndex = -1;
+        boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
 
         // We can reuse the task transforms where possible to reduce object allocation
         Utilities.matchTaskListSize(tasks, taskTransforms);
 
         // Update the stack transforms
         TaskViewTransform frontTransform = null;
+        TaskViewTransform frontTransformAtTarget = null;
+        TaskViewTransform transform = null;
+        TaskViewTransform transformAtTarget = null;
         for (int i = taskCount - 1; i >= 0; i--) {
             Task task = tasks.get(i);
-            if (ignoreTasksSet.contains(task)) {
-                continue;
+
+            // Calculate the current and (if necessary) the target transform for the task
+            transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
+                    taskTransforms.get(i), frontTransform);
+            if (useTargetStackScroll && !transform.visible) {
+                // If we have a target stack scroll and the task is not currently visible, then we
+                // just update the transform at the new scroll
+                // TODO: Optimize this
+                transformAtTarget = mLayoutAlgorithm.getStackTransform(task,
+                        targetStackScroll, new TaskViewTransform(), frontTransformAtTarget);
+                if (transformAtTarget.visible) {
+                    transform.copyFrom(transformAtTarget);
+                }
             }
 
-            TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
-                    taskTransforms.get(i), frontTransform);
+            // For ignore tasks, only calculate the stack transform and skip the calculation of the
+            // visible stack indices
+            if (ignoreTasksSet.contains(task.key)) {
+                continue;
+            }
 
             // For freeform tasks, only calculate the stack transform and skip the calculation of
             // the visible stack indices
@@ -392,7 +446,9 @@
                     break;
                 }
             }
+
             frontTransform = transform;
+            frontTransformAtTarget = transformAtTarget;
         }
         if (visibleRangeOut != null) {
             visibleRangeOut[0] = frontMostVisibleIndex;
@@ -402,33 +458,48 @@
     }
 
     /**
-     * Updates the children {@link TaskView}s to match the tasks in the current {@link TaskStack}.
-     * This call does not update the {@link TaskView}s to their position in the layout except when
-     * they are initially picked up from the pool, when they will be placed in a suitable initial
-     * position.
+     * Binds the visible {@link TaskView}s at the given target scroll.
+     *
+     * @see #bindVisibleTaskViews(float, ArraySet<Task.TaskKey>)
      */
-    private void bindTaskViewsWithStack(ArraySet<Task> ignoreTasksSet) {
-        final float stackScroll = mStackScroller.getStackScroll();
+    void bindVisibleTaskViews(float targetStackScroll) {
+        bindVisibleTaskViews(targetStackScroll, mIgnoreTasks);
+    }
+
+    /**
+     * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the
+     * current {@link TaskStack}. This call does not continue on to update their position to the
+     * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will
+     * be added/removed from the view hierarchy and placed in the correct Z order and initial
+     * position (if not currently on screen).
+     *
+     * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s
+     *                          includes those visible at the current stack scroll, and all at the
+     *                          target stack scroll.
+     * @param ignoreTasksSet The set of tasks to ignore in this rebinding of the visible
+     *                       {@link TaskView}s
+     */
+    void bindVisibleTaskViews(float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet) {
         final int[] visibleStackRange = mTmpVisibleRange;
 
         // Get all the task transforms
         final ArrayList<Task> tasks = mStack.getStackTasks();
-        final boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms,
-                tasks, stackScroll, visibleStackRange, ignoreTasksSet);
+        final boolean isValidVisibleRange = computeVisibleTaskTransforms(mCurrentTaskTransforms,
+                tasks, mStackScroller.getStackScroll(), targetStackScroll, visibleStackRange,
+                ignoreTasksSet);
 
         // Return all the invisible children to the pool
-        final List<TaskView> taskViews = getTaskViews();
-        final int taskViewCount = taskViews.size();
-        int lastFocusedTaskIndex = -1;
         mTmpTaskViewMap.clear();
-        mTmpTaskViewMap.ensureCapacity(tasks.size());
+        List<TaskView> taskViews = getTaskViews();
+        int lastFocusedTaskIndex = -1;
+        int taskViewCount = taskViews.size();
         for (int i = taskViewCount - 1; i >= 0; i--) {
-            final TaskView tv = taskViews.get(i);
-            final Task task = tv.getTask();
-            final int taskIndex = mStack.indexOfStackTask(task);
+            TaskView tv = taskViews.get(i);
+            Task task = tv.getTask();
+            int taskIndex = mStack.indexOfStackTask(task);
 
             // Skip ignored tasks
-            if (ignoreTasksSet.contains(task)) {
+            if (ignoreTasksSet.contains(task.key)) {
                 continue;
             }
 
@@ -445,13 +516,13 @@
         }
 
         // Pick up all the newly visible children
-        int lastVisStackIndex = isValidVisibleStackRange ? visibleStackRange[1] : 0;
-        for (int i = mStack.getTaskCount() - 1; i >= lastVisStackIndex; i--) {
-            final Task task = tasks.get(i);
-            final TaskViewTransform transform = mCurrentTaskTransforms.get(i);
+        int lastVisStackIndex = isValidVisibleRange ? visibleStackRange[1] : 0;
+        for (int i = tasks.size() - 1; i >= lastVisStackIndex; i--) {
+            Task task = tasks.get(i);
+            TaskViewTransform transform = mCurrentTaskTransforms.get(i);
 
             // Skip ignored tasks
-            if (ignoreTasksSet.contains(task)) {
+            if (ignoreTasksSet.contains(task.key)) {
                 continue;
             }
 
@@ -502,26 +573,34 @@
     }
 
     /**
-     * Cancels any existing {@link TaskView} animations, and updates each {@link TaskView} to its
-     * current position as defined by the {@link TaskStackLayoutAlgorithm}.
+     * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
+     * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
+     * animations that are current running on those task views, and will ensure that the children
+     * {@link TaskView}s will match the set of visible tasks in the stack.
      *
-     * @param ignoreTasks the set of tasks to ignore in the relayout
+     * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>)
      */
-    private void updateTaskViewsToLayout(TaskViewAnimation animation, Task... ignoreTasks) {
-        // Keep track of the ignore tasks
-        ArraySet<Task> ignoreTasksSet = mTmpTaskSet;
-        ignoreTasksSet.clear();
-        ignoreTasksSet.ensureCapacity(ignoreTasks.length);
-        Collections.addAll(ignoreTasksSet, ignoreTasks);
+    void relayoutTaskViews(TaskViewAnimation animation) {
+        relayoutTaskViews(animation, mIgnoreTasks);
+    }
 
+    /**
+     * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
+     * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
+     * animations that are current running on those task views, and will ensure that the children
+     * {@link TaskView}s will match the set of visible tasks in the stack.
+     *
+     * @param ignoreTasksSet the set of tasks to ignore in the relayout
+     */
+    void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
         // If we had a deferred animation, cancel that
-        mDeferredTaskViewUpdateAnimation = null;
+        mDeferredTaskViewLayoutAnimation = null;
 
         // Cancel all task view animations
         cancelAllTaskViewAnimations();
 
-        // Fetch the current set of TaskViews
-        bindTaskViewsWithStack(ignoreTasksSet);
+        // Synchronize the current set of TaskViews
+        bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet);
 
         // Animate them to their final transforms with the given animation
         List<TaskView> taskViews = getTaskViews();
@@ -531,7 +610,7 @@
             final int taskIndex = mStack.indexOfStackTask(tv.getTask());
             final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
 
-            if (ignoreTasksSet.contains(tv.getTask())) {
+            if (ignoreTasksSet.contains(tv.getTask().key)) {
                 continue;
             }
 
@@ -542,8 +621,8 @@
     /**
      * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
      */
-    private void updateTaskViewsToLayoutOnNextFrame(TaskViewAnimation animation) {
-        mDeferredTaskViewUpdateAnimation = animation;
+    void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) {
+        mDeferredTaskViewLayoutAnimation = animation;
         postInvalidateOnAnimation();
     }
 
@@ -558,13 +637,62 @@
     }
 
     /**
-     * Cancels all {@link TaskView} animations.
+     * Returns the current task transforms of all tasks, falling back to the stack layout if there
+     * is no {@link TaskView} for the task.
      */
-    private void cancelAllTaskViewAnimations() {
+    public void getCurrentTaskTransforms(ArrayList<Task> tasks,
+            ArrayList<TaskViewTransform> transformsOut) {
+        Utilities.matchTaskListSize(tasks, transformsOut);
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            Task task = tasks.get(i);
+            TaskViewTransform transform = transformsOut.get(i);
+            TaskView tv = getChildViewForTask(task);
+            if (tv != null) {
+                transform.fillIn(tv);
+            } else {
+                mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
+                        transform, null);
+            }
+            transform.visible = true;
+        }
+    }
+
+    /**
+     * Returns the task transforms for all the tasks in the stack if the stack was at the given
+     * {@param stackScroll}.
+     */
+    public void getLayoutTaskTransforms(float stackScroll, ArrayList<Task> tasks,
+            ArrayList<TaskViewTransform> transformsOut) {
+        Utilities.matchTaskListSize(tasks, transformsOut);
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            Task task = tasks.get(i);
+            TaskViewTransform transform = transformsOut.get(i);
+            mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null);
+            transform.visible = true;
+        }
+    }
+
+    /**
+     * Cancels all {@link TaskView} animations.
+     *
+     * @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>)
+     */
+    void cancelAllTaskViewAnimations() {
+        cancelAllTaskViewAnimations(mIgnoreTasks);
+    }
+
+    /**
+     * Cancels all {@link TaskView} animations.
+     *
+     * @param ignoreTasksSet The set of tasks to continue running their animations.
+     */
+    void cancelAllTaskViewAnimations(ArraySet<Task.TaskKey> ignoreTasksSet) {
         List<TaskView> taskViews = getTaskViews();
         for (int i = taskViews.size() - 1; i >= 0; i--) {
             final TaskView tv = taskViews.get(i);
-            tv.cancelTransformAnimation();
+            if (!ignoreTasksSet.contains(tv.getTask().key)) {
+                tv.cancelTransformAnimation();
+            }
         }
     }
 
@@ -577,11 +705,22 @@
         // Update the clip on each task child
         List<TaskView> taskViews = getTaskViews();
         TaskView tmpTv = null;
+        TaskView prevVisibleTv = null;
         int taskViewCount = taskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
             TaskView tv = taskViews.get(i);
             TaskView frontTv = null;
             int clipBottom = 0;
+
+            if (mIgnoreTasks.contains(tv.getTask().key)) {
+                // For each of the ignore tasks, update the translationZ of its TaskView to be
+                // between the translationZ of the tasks immediately underneath it
+                if (prevVisibleTv != null) {
+                    tv.setTranslationZ(Math.max(tv.getTranslationZ(),
+                            prevVisibleTv.getTranslationZ() + 0.1f));
+                }
+            }
+
             if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
                 // Find the next view to clip against
                 for (int j = i + 1; j < taskViewCount; j++) {
@@ -609,33 +748,37 @@
             if (!config.useHardwareLayers) {
                 tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
             }
+            prevVisibleTv = tv;
         }
         mTaskViewsClipDirty = false;
     }
 
     /**
+     * Updates the layout algorithm min and max virtual scroll bounds.
+     *
+     * @see #updateLayoutAlgorithm(boolean, ArraySet<Task.TaskKey>)
+     */
+    void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
+        updateLayoutAlgorithm(boundScrollToNewMinMax, mIgnoreTasks);
+    }
+
+    /**
      * Updates the min and max virtual scroll bounds.
      *
-     * @param ignoreTasks the set of tasks to ignore in the relayout
+     * @param ignoreTasksSet the set of tasks to ignore in the relayout
      */
-    void updateLayout(boolean boundScrollToNewMinMax, Task... ignoreTasks) {
-        // Keep track of the ingore tasks
-        ArraySet<Task> ignoreTasksSet = mTmpTaskSet;
-        ignoreTasksSet.clear();
-        ignoreTasksSet.ensureCapacity(ignoreTasks.length);
-        Collections.addAll(ignoreTasksSet, ignoreTasks);
-
+    void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
+            ArraySet<Task.TaskKey> ignoreTasksSet) {
         // Compute the min and max scroll values
         mLayoutAlgorithm.update(mStack, ignoreTasksSet);
 
-        // Update the freeform workspace
+        // Update the freeform workspace background
         SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.hasFreeformWorkspaceSupport()) {
             mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
             mFreeformWorkspaceBackground.setBounds(mTmpRect);
         }
 
-        // Debug logging
         if (boundScrollToNewMinMax) {
             mStackScroller.boundScroll();
         }
@@ -671,8 +814,6 @@
 
         // Reset the last focused task state if changed
         if (mFocusedTask != null) {
-            resetFocusedTask(mFocusedTask);
-
             // Cancel the timer indicator, if applicable
             if (showTimerIndicator) {
                 final TaskView tv = getChildViewForTask(mFocusedTask);
@@ -680,6 +821,8 @@
                     tv.getHeaderView().cancelFocusTimerIndicator();
                 }
             }
+
+            resetFocusedTask(mFocusedTask);
         }
 
         boolean willScroll = false;
@@ -937,10 +1080,10 @@
             // Notify accessibility
             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
         }
-        if (mDeferredTaskViewUpdateAnimation != null) {
-            updateTaskViewsToLayout(mDeferredTaskViewUpdateAnimation);
+        if (mDeferredTaskViewLayoutAnimation != null) {
+            relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
             mTaskViewsClipDirty = true;
-            mDeferredTaskViewUpdateAnimation = null;
+            mDeferredTaskViewLayoutAnimation = null;
         }
         if (mTaskViewsClipDirty) {
             clipTaskViews();
@@ -948,30 +1091,16 @@
     }
 
     /**
-     * Computes the stack and task rects.
-     *
-     * @param ignoreTasks the set of tasks to ignore in the relayout
-     */
-    public void computeRects(Rect taskStackBounds, boolean boundScroll, Task... ignoreTasks) {
-        // Compute the rects in the stack algorithm
-        mLayoutAlgorithm.initialize(taskStackBounds,
-                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
-
-        // Update the scroll bounds
-        updateLayout(boundScroll, ignoreTasks);
-    }
-
-    /**
      * This is ONLY used from the Recents component to update the dummy stack view for purposes
      * of getting the task rect to animate to.
      */
     public void updateLayoutForStack(TaskStack stack) {
         mStack = stack;
-        updateLayout(false);
+        updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
     }
 
     /**
-     * Computes the maximum number of visible tasks and thumbnails.  Requires that
+     * Computes the maximum number of visible tasks and thumbnails. Requires that
      * updateLayoutForStack() is called first.
      */
     public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
@@ -1002,16 +1131,18 @@
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
-        // Compute our stack/task rects
-        computeRects(mStackBounds, false);
+        // Compute the rects in the stack algorithm
+        mLayoutAlgorithm.initialize(mStackBounds,
+                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+        updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
 
         // If this is the first layout, then scroll to the front of the stack, then update the
         // TaskViews with the stack so that we can lay them out
         if (mAwaitingFirstLayout) {
             mStackScroller.setStackScrollToInitialState();
         }
-        mTmpTaskSet.clear();
-        bindTaskViewsWithStack(mTmpTaskSet);
+        // Rebind all the views, including the ignore ones
+        bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET);
 
         // Measure each of the TaskViews
         mTmpTaskViews.clear();
@@ -1066,7 +1197,8 @@
                 mStackScroller.boundScroll();
             }
         }
-        updateTaskViewsToLayout(TaskViewAnimation.IMMEDIATE);
+        // Relayout all of the task views including the ignored ones
+        relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET);
         clipTaskViews();
 
         if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
@@ -1088,9 +1220,12 @@
 
         // Set the task focused state without requesting view focus, and leave the focus animations
         // until after the enter-animation
+        Task launchTask = mStack.getLaunchTarget();
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
-        int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
+        int focusedTaskIndex = launchTask != null
+                ? mStack.indexOfStackTask(launchTask)
+                : launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
         if (focusedTaskIndex != -1) {
             setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
                     false /* requestViewFocus */);
@@ -1106,8 +1241,29 @@
     }
 
     public boolean isTouchPointInView(float x, float y, TaskView tv) {
-        return (tv.getLeft() <= x && x <= tv.getRight()) &&
-                (tv.getTop() <= y && y <= tv.getBottom());
+        mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
+        mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY());
+        return mTmpRect.contains((int) x, (int) y);
+    }
+
+    /**
+     * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when
+     * calculating the scroll position before and after a layout change.
+     */
+    public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) {
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            Task task = tasks.get(i);
+
+            // Ignore deleting tasks
+            if (mIgnoreTasks.contains(task.key)) {
+                if (i == tasks.size() - 1) {
+                    isFrontMostTask.value = true;
+                }
+                continue;
+            }
+            return task;
+        }
+        return null;
     }
 
     @Override
@@ -1152,70 +1308,38 @@
     @Override
     public void onStackTaskAdded(TaskStack stack, Task newTask) {
         // Update the min/max scroll and animate other task views into their new positions
-        updateLayout(true);
+        updateLayoutAlgorithm(true /* boundScroll */);
 
         // Animate all the tasks into place
-        updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+        relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
                 mFastOutSlowInInterpolator));
     }
 
+    /**
+     * We expect that the {@link TaskView} associated with the removed task is already hidden.
+     */
     @Override
     public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
-            Task newFrontMostTask) {
+            Task newFrontMostTask, TaskViewAnimation animation) {
         if (mFocusedTask == removedTask) {
             resetFocusedTask(removedTask);
         }
 
-        if (!removedTask.isFreeformTask()) {
-            // Remove the view associated with this task, we can't rely on updateTransforms
-            // to work here because the task is no longer in the list
-            TaskView tv = getChildViewForTask(removedTask);
-            if (tv != null) {
-                mViewPool.returnViewToPool(tv);
-            }
-
-            // Get the stack scroll of the task to anchor to (since we are removing something, the
-            // front most task will be our anchor task)
-            Task anchorTask = mStack.getStackFrontMostTask();
-            float prevAnchorTaskScroll = 0;
-            if (anchorTask != null) {
-                prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
-            }
-
-            // Update the min/max scroll and animate other task views into their new positions
-            updateLayout(true);
-
-            if (wasFrontMostTask) {
-                // Since the max scroll progress is offset from the bottom of the stack, just scroll
-                // to ensure that the new front most task is now fully visible
-                mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
-            } else if (anchorTask != null) {
-                // Otherwise, offset the scroll by the movement of the anchor task
-                float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
-                float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
-                if (mLayoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) {
-                    // If we are focused, we don't want the front task to move, but otherwise, we
-                    // allow the back task to move up, and the front task to move back
-                    stackScrollOffset /= 2;
-                }
-                mStackScroller.setStackScroll(mStackScroller.getStackScroll() + stackScrollOffset);
-                mStackScroller.boundScroll();
-            }
-        } else {
-            // Remove the view associated with this task, we can't rely on updateTransforms
-            // to work here because the task is no longer in the list
-            TaskView tv = getChildViewForTask(removedTask);
-            if (tv != null) {
-                mViewPool.returnViewToPool(tv);
-            }
-
-            // Update the min/max scroll and animate other task views into their new positions
-            updateLayout(true);
+        // Remove the view associated with this task, we can't rely on updateTransforms
+        // to work here because the task is no longer in the list
+        TaskView tv = getChildViewForTask(removedTask);
+        if (tv != null) {
+            mViewPool.returnViewToPool(tv);
         }
 
-        // Animate all the tasks into place
-        updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
-                mFastOutSlowInInterpolator));
+        // Remove the task from the ignored set
+        removeIgnoreTask(removedTask);
+
+        // If requested, relayout with the given animation
+        if (animation != null) {
+            updateLayoutAlgorithm(true /* boundScroll */);
+            relayoutTaskViews(animation);
+        }
 
         // Update the new front most task's action button
         if (mScreenPinningEnabled && newFrontMostTask != null) {
@@ -1232,7 +1356,8 @@
     }
 
     @Override
-    public void onHistoryTaskRemoved(TaskStack stack, Task removedTask) {
+    public void onHistoryTaskRemoved(TaskStack stack, Task removedTask,
+            TaskViewAnimation animation) {
         // To be implemented
     }
 
@@ -1302,7 +1427,8 @@
         }
 
         // Restore the action button visibility if it is the front most task view
-        if (mScreenPinningEnabled && tv.getTask() == mStack.getStackFrontMostTask()) {
+        if (mScreenPinningEnabled && tv.getTask() ==
+                mStack.getStackFrontMostTask(false /* includeFreeform */)) {
             tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
         }
     }
@@ -1316,7 +1442,10 @@
 
     @Override
     public void onTaskViewClipStateChanged(TaskView tv) {
-        clipTaskViews();
+        if (!mTaskViewsClipDirty) {
+            mTaskViewsClipDirty = true;
+            invalidate();
+        }
     }
 
     /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@@ -1324,7 +1453,9 @@
     @Override
     public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) {
         mUIDozeTrigger.poke();
-        updateTaskViewsToLayoutOnNextFrame(animation);
+        if (animation != null) {
+            relayoutTaskViewsOnNextFrame(animation);
+        }
 
         if (shouldShowHistoryButton() &&
                 prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD &&
@@ -1354,7 +1485,7 @@
                     tv.dismissTask();
                 } else {
                     // Otherwise, remove the task from the stack immediately
-                    mStack.removeTask(t);
+                    mStack.removeTask(t, TaskViewAnimation.IMMEDIATE);
                 }
             }
         }
@@ -1402,7 +1533,7 @@
     }
 
     public final void onBusEvent(TaskViewDismissedEvent event) {
-        removeTaskViewFromStack(event.taskView);
+        removeTaskViewFromStack(event.taskView, event.task);
         EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
     }
 
@@ -1419,7 +1550,10 @@
         // Poke the doze trigger on user interaction
         mUIDozeTrigger.poke();
         if (event.showTimerIndicator && mFocusedTask != null) {
-            getChildViewForTask(mFocusedTask).getHeaderView().cancelFocusTimerIndicator();
+            TaskView tv = getChildViewForTask(mFocusedTask);
+            if (tv != null) {
+                tv.getHeaderView().cancelFocusTimerIndicator();
+            }
         }
     }
 
@@ -1455,23 +1589,29 @@
     }
 
     public final void onBusEvent(DragDropTargetChangedEvent event) {
+        TaskViewAnimation animation = new TaskViewAnimation(250, mFastOutSlowInInterpolator);
         if (event.dropTarget instanceof TaskStack.DockState) {
             // Calculate the new task stack bounds that matches the window size that Recents will
             // have after the drop
+            addIgnoreTask(event.task);
             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
             mStackBounds.set(dockState.getDockedTaskStackBounds(getMeasuredWidth(),
                     getMeasuredHeight(), mDividerSize, mLayoutAlgorithm.mSystemInsets,
                     getResources()));
-            computeRects(mStackBounds, true /* boundScroll */, event.task /* ignoreTask */);
-            updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator),
-                    event.task /* ignoreTask */);
+            mLayoutAlgorithm.initialize(mStackBounds,
+                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+            updateLayoutAlgorithm(true /* boundScroll */);
         } else {
-            // Restore the pre-drag task stack bounds
+            // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
+            // task view, so add it back to the ignore set after updating the layout
             mStackBounds.set(mStableStackBounds);
-            computeRects(mStackBounds, true /* boundScroll */);
-            updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator),
-                    event.task /* ignoreTask */);
+            removeIgnoreTask(event.task);
+            mLayoutAlgorithm.initialize(mStackBounds,
+                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+            updateLayoutAlgorithm(true /* boundScroll */);
+            addIgnoreTask(event.task);
         }
+        relayoutTaskViews(animation);
     }
 
     public final void onBusEvent(final DragEndEvent event) {
@@ -1487,14 +1627,14 @@
 
         if (hasChangedStacks) {
             // Move the task to the right position in the stack (ie. the front of the stack if
-            // freeform or the front of the stack if fullscreen).  Note, we MUST move the tasks
+            // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
             // before we update their stack ids, otherwise, the keys will have changed.
             if (event.dropTarget == mFreeformWorkspaceDropTarget) {
                 mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
             } else if (event.dropTarget == mStackDropTarget) {
                 mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
             }
-            updateLayout(true);
+            updateLayoutAlgorithm(true /* boundScroll */);
 
             // Move the task to the new stack in the system after the animation completes
             event.addPostAnimationCallback(new Runnable() {
@@ -1522,11 +1662,12 @@
         mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
                 mTmpTransform, null);
         event.getAnimationTrigger().increment();
-        updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+        relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
                 mFastOutSlowInInterpolator));
         updateTaskViewToTransform(event.taskView, mTmpTransform,
                 new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, mFastOutSlowInInterpolator,
                         event.getAnimationTrigger().decrementOnAnimationEnd()));
+        removeIgnoreTask(event.task);
     }
 
     public final void onBusEvent(StackViewScrolledEvent event) {
@@ -1593,15 +1734,14 @@
      * Removes the task from the stack, and updates the focus to the next task in the stack if the
      * removed TaskView was focused.
      */
-    private void removeTaskViewFromStack(TaskView tv) {
-        Task task = tv.getTask();
-
+    private void removeTaskViewFromStack(TaskView tv, Task task) {
         // Announce for accessibility
         tv.announceForAccessibility(getContext().getString(
-                R.string.accessibility_recents_item_dismissed, tv.getTask().title));
+                R.string.accessibility_recents_item_dismissed, task.title));
 
         // Remove the task from the stack
-        mStack.removeTask(task);
+        mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+                mFastOutSlowInInterpolator));
     }
 
     /**
@@ -1622,7 +1762,7 @@
     }
 
     /**
-     * Returns the insert index for the task in the current set of task views.  If the given task
+     * Returns the insert index for the task in the current set of task views. If the given task
      * is already in the task view list, then this method returns the insert index assuming it
      * is first removed at the previous index.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 32f02ac..5335b14 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -151,6 +151,7 @@
 
     /** Animates the stack scroll into bounds */
     ObjectAnimator animateBoundScroll() {
+        // TODO: Take duration for snap back
         float curScroll = getStackScroll();
         float newScroll = getBoundedStackScroll(curScroll);
         if (Float.compare(newScroll, curScroll) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 4813c19..e9f6f39 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -21,13 +21,16 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
-import android.util.Log;
+import android.util.ArrayMap;
+import android.util.MutableBoolean;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewParent;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
@@ -37,19 +40,25 @@
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
+import com.android.systemui.recents.misc.RectFEvaluator;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.Task;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
+import java.util.ArrayList;
 import java.util.List;
 
-/* Handles touch events for a TaskStackView. */
+/**
+ * Handles touch events for a TaskStackView.
+ */
 class TaskStackViewTouchHandler implements SwipeHelper.Callback {
 
-    private static final String TAG = "TaskStackViewTouchHandler";
-    private static final boolean DEBUG = false;
+    private static final int INACTIVE_POINTER_ID = -1;
 
-    private static int INACTIVE_POINTER_ID = -1;
+    private static final RectFEvaluator RECT_EVALUATOR = new RectFEvaluator();
+    private static final Interpolator STACK_TRANSFORM_INTERPOLATOR =
+            new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f);
 
     Context mContext;
     TaskStackView mSv;
@@ -74,6 +83,15 @@
     final int mWindowTouchSlop;
 
     private final StackViewScrolledEvent mStackViewScrolledEvent = new StackViewScrolledEvent();
+
+    // The current and final set of task transforms, sized to match the list of tasks in the stack
+    private ArrayList<Task> mCurrentTasks = new ArrayList<>();
+    private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
+    private ArrayList<TaskViewTransform> mFinalTaskTransforms = new ArrayList<>();
+    private ArrayMap<View, Animator> mSwipeHelperAnimations = new ArrayMap<>();
+    private TaskViewTransform mTmpTransform = new TaskViewTransform();
+    private float mTargetStackScroll;
+
     SwipeHelper mSwipeHelper;
     boolean mInterceptedBySwipeHelper;
 
@@ -97,8 +115,14 @@
             }
 
             @Override
-            protected void updateSnapBackAnimation(Animator anim) {
+            protected void prepareDismissAnimation(View v, Animator anim) {
+                mSwipeHelperAnimations.put(v, anim);
+            }
+
+            @Override
+            protected void prepareSnapBackAnimation(View v, Animator anim) {
                 anim.setInterpolator(mSv.mFastOutSlowInInterpolator);
+                mSwipeHelperAnimations.put(v, anim);
             }
         };
         mSwipeHelper.setDisableHardwareLayers(true);
@@ -119,21 +143,6 @@
         }
     }
 
-    /** Returns the view at the specified coordinates */
-    TaskView findViewAtPoint(int x, int y) {
-        List<TaskView> taskViews = mSv.getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            if (tv.getVisibility() == View.VISIBLE) {
-                if (mSv.isTouchPointInView(x, y, tv)) {
-                    return tv;
-                }
-            }
-        }
-        return null;
-    }
-
     /** Touch preprocessing for handling below */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         // Pass through to swipe helper if we are swiping
@@ -179,6 +188,15 @@
                 mScroller.stopBoundScrollAnimation();
                 Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
 
+                // Finish any existing task animations from the delete
+                mSv.cancelAllTaskViewAnimations();
+                // Finish any of the swipe helper animations
+                ArrayMap<View, Animator> existingAnimators = new ArrayMap<>(mSwipeHelperAnimations);
+                for (int i = 0; i < existingAnimators.size(); i++) {
+                    existingAnimators.get(existingAnimators.keyAt(i)).end();
+                }
+                mSwipeHelperAnimations.clear();
+
                 // Initialize the velocity tracker
                 initOrResetVelocityTracker();
                 mVelocityTracker.addMovement(ev);
@@ -214,9 +232,6 @@
                     float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
                     float curScrollP = mDownScrollP + deltaP;
                     mScroller.setStackScroll(curScrollP);
-                    if (DEBUG) {
-                        Log.d(TAG, "scroll: " + curScrollP);
-                    }
                     mStackViewScrolledEvent.updateY(y - mLastY);
                     EventBus.getDefault().send(mStackViewScrolledEvent);
                 }
@@ -343,12 +358,19 @@
 
     @Override
     public View getChildAtPosition(MotionEvent ev) {
-        return findViewAtPoint((int) ev.getX(), (int) ev.getY());
+        TaskView tv = findViewAtPoint((int) ev.getX(), (int) ev.getY());
+        if (tv != null && canChildBeDismissed(tv)) {
+            return tv;
+        }
+        return null;
     }
 
     @Override
     public boolean canChildBeDismissed(View v) {
-        return true;
+        // Disallow dismissing an already dismissed task
+        TaskView tv = (TaskView) v;
+        return !mSwipeHelperAnimations.containsKey(v) &&
+                (mSv.getStack().indexOfStackTask(tv.getTask()) != -1);
     }
 
     @Override
@@ -364,34 +386,113 @@
         if (parent != null) {
             parent.requestDisallowInterceptTouchEvent(true);
         }
+
+        // Add this task to the set of tasks we are deleting
+        mSv.addIgnoreTask(tv.getTask());
+
+        // Determine if we are animating the other tasks while dismissing this task
+        mCurrentTasks = mSv.getStack().getStackTasks();
+        MutableBoolean isFrontMostTask = new MutableBoolean(false);
+        Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
+        TaskStackViewScroller stackScroller = mSv.getScroller();
+        if (anchorTask != null) {
+            // Get the current set of task transforms
+            mSv.getCurrentTaskTransforms(mCurrentTasks, mCurrentTaskTransforms);
+
+            // Get the stack scroll of the task to anchor to (since we are removing something, the
+            // front most task will be our anchor task)
+            float prevAnchorTaskScroll = 0;
+            boolean pullStackForward = mCurrentTasks.size() > 0;
+            if (pullStackForward) {
+                prevAnchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
+            }
+
+            // Calculate where the views would be without the deleting tasks
+            mSv.updateLayoutAlgorithm(false /* boundScroll */);
+
+            float newStackScroll = stackScroller.getStackScroll();
+            if (isFrontMostTask.value) {
+                // Bound the stack scroll to pull tasks forward if necessary
+                newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll);
+            } else if (pullStackForward) {
+                // Otherwise, offset the scroll by the movement of the anchor task
+                float anchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
+                float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
+                if (mSv.getStackAlgorithm().getFocusState() !=
+                        TaskStackLayoutAlgorithm.STATE_FOCUSED) {
+                    // If we are focused, we don't want the front task to move, but otherwise, we
+                    // allow the back task to move up, and the front task to move back
+                    stackScrollOffset /= 2;
+                }
+                newStackScroll = stackScroller.getBoundedStackScroll(stackScroller.getStackScroll()
+                        + stackScrollOffset);
+            }
+
+            // Pick up the newly visible views, not including the deleting tasks
+            mSv.bindVisibleTaskViews(newStackScroll);
+
+            // Get the final set of task transforms (with task removed)
+            mSv.getLayoutTaskTransforms(newStackScroll, mCurrentTasks, mFinalTaskTransforms);
+
+            // Set the target to scroll towards upon dismissal
+            mTargetStackScroll = newStackScroll;
+
+            /*
+             * Post condition: All views that will be visible as a part of the gesture are retrieved
+             *                 and at their initial positions.  The stack is still at the current
+             *                 scroll, but the layout is updated without the task currently being
+             *                 dismissed.
+             */
+        }
     }
 
     @Override
     public boolean updateSwipeProgress(View v, boolean dismissable, float swipeProgress) {
+        updateTaskViewTransforms(getDismissFraction(v));
         return true;
     }
 
+    /**
+     * Called after the {@link TaskView} is finished animating away.
+     */
     @Override
     public void onChildDismissed(View v) {
         TaskView tv = (TaskView) v;
+
         // Re-enable clipping with the stack (we will reuse this view)
         tv.setClipViewInStack(true);
         // Re-enable touch events from this task view
         tv.setTouchEnabled(true);
+        // Update the scroll to the final scroll position from onBeginDrag()
+        mSv.getScroller().setStackScroll(mTargetStackScroll, null);
         // Remove the task view from the stack
         EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
+        // Stop tracking this deletion animation
+        mSwipeHelperAnimations.remove(v);
         // Keep track of deletions by keyboard
         MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
                 Constants.Metrics.DismissSourceSwipeGesture);
     }
 
+    /**
+     * Called after the {@link TaskView} is finished animating back into the list.
+     * onChildDismissed() calls.
+     */
     @Override
     public void onChildSnappedBack(View v) {
         TaskView tv = (TaskView) v;
+
         // Re-enable clipping with the stack
         tv.setClipViewInStack(true);
         // Re-enable touch events from this task view
         tv.setTouchEnabled(true);
+
+        // Stop tracking this deleting task, and update the layout to include this task again.  The
+        // stack scroll does not need to be reset, since the scroll has not actually changed in
+        // onBeginDrag().
+        mSv.removeIgnoreTask(tv.getTask());
+        mSv.updateLayoutAlgorithm(false /* boundScroll */);
+        mSwipeHelperAnimations.remove(v);
     }
 
     @Override
@@ -414,4 +515,59 @@
         return 0;
     }
 
+    /**
+     * Interpolates the non-deleting tasks to their final transforms from their current transforms.
+     */
+    private void updateTaskViewTransforms(float dismissFraction) {
+        List<TaskView> taskViews = mSv.getTaskViews();
+        int taskViewCount = taskViews.size();
+        for (int i = 0; i < taskViewCount; i++) {
+            TaskView tv = taskViews.get(i);
+            Task task = tv.getTask();
+
+            if (mSv.isIgnoredTask(task)) {
+                continue;
+            }
+
+            int taskIndex = mCurrentTasks.indexOf(task);
+            TaskViewTransform fromTransform = mCurrentTaskTransforms.get(taskIndex);
+            TaskViewTransform toTransform = mFinalTaskTransforms.get(taskIndex);
+
+            mTmpTransform.copyFrom(fromTransform);
+            // We only really need to interpolate the bounds, progress and translation
+            mTmpTransform.rect.set(RECT_EVALUATOR.evaluate(dismissFraction, fromTransform.rect,
+                    toTransform.rect));
+            mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction;
+            mTmpTransform.translationZ = fromTransform.translationZ +
+                    (toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
+
+            mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE);
+        }
+    }
+
+    /** Returns the view at the specified coordinates */
+    private TaskView findViewAtPoint(int x, int y) {
+        List<Task> tasks = mSv.getStack().getStackTasks();
+        int taskCount = tasks.size();
+        for (int i = taskCount - 1; i >= 0; i--) {
+            TaskView tv = mSv.getChildViewForTask(tasks.get(i));
+            if (tv != null && tv.getVisibility() == View.VISIBLE) {
+                if (mSv.isTouchPointInView(x, y, tv)) {
+                    return tv;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the fraction which we should interpolate the other task views based on the dismissal
+     * of this given task.
+     *
+     * TODO: We can interpolate this to adjust when the other tasks should respond to the dismissal
+     */
+    private float getDismissFraction(View v) {
+        float fraction = Math.min(1f, Math.abs(v.getTranslationX() / mSv.getWidth()));
+        return STACK_TRANSFORM_INTERPOLATOR.getInterpolation(fraction);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 9b72702..32bebb3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -290,6 +290,7 @@
         setDim(0);
         setVisibility(View.VISIBLE);
         getViewBounds().reset();
+        getHeaderView().reset();
         TaskViewTransform.reset(this);
 
         mActionButtonView.setScaleX(1f);
@@ -459,16 +460,14 @@
     public void showActionButton(boolean fadeIn, int fadeInDuration) {
         mActionButtonView.setVisibility(View.VISIBLE);
 
-        if (fadeIn) {
-            if (mActionButtonView.getAlpha() < 1f) {
-                mActionButtonView.animate()
-                        .alpha(1f)
-                        .scaleX(1f)
-                        .scaleY(1f)
-                        .setDuration(fadeInDuration)
-                        .setInterpolator(PhoneStatusBar.ALPHA_IN)
-                        .start();
-            }
+        if (fadeIn && mActionButtonView.getAlpha() < 1f) {
+            mActionButtonView.animate()
+                    .alpha(1f)
+                    .scaleX(1f)
+                    .scaleY(1f)
+                    .setDuration(fadeInDuration)
+                    .setInterpolator(PhoneStatusBar.ALPHA_IN)
+                    .start();
         } else {
             mActionButtonView.setScaleX(1f);
             mActionButtonView.setScaleY(1f);
@@ -484,29 +483,27 @@
      */
     public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown,
             final Animator.AnimatorListener animListener) {
-        if (fadeOut) {
-            if (mActionButtonView.getAlpha() > 0f) {
-                if (scaleDown) {
-                    float toScale = 0.9f;
-                    mActionButtonView.animate()
-                            .scaleX(toScale)
-                            .scaleY(toScale);
-                }
+        if (fadeOut && mActionButtonView.getAlpha() > 0f) {
+            if (scaleDown) {
+                float toScale = 0.9f;
                 mActionButtonView.animate()
-                        .alpha(0f)
-                        .setDuration(fadeOutDuration)
-                        .setInterpolator(PhoneStatusBar.ALPHA_OUT)
-                        .withEndAction(new Runnable() {
-                            @Override
-                            public void run() {
-                                if (animListener != null) {
-                                    animListener.onAnimationEnd(null);
-                                }
-                                mActionButtonView.setVisibility(View.INVISIBLE);
-                            }
-                        })
-                        .start();
+                        .scaleX(toScale)
+                        .scaleY(toScale);
             }
+            mActionButtonView.animate()
+                    .alpha(0f)
+                    .setDuration(fadeOutDuration)
+                    .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+                    .withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (animListener != null) {
+                                animListener.onAnimationEnd(null);
+                            }
+                            mActionButtonView.setVisibility(View.INVISIBLE);
+                        }
+                    })
+                    .start();
         } else {
             mActionButtonView.setAlpha(0f);
             mActionButtonView.setVisibility(View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index e7717ac..827ee40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,22 +16,27 @@
 
 package com.android.systemui.recents.views;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
-import android.graphics.PorterDuff;
 import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.RippleDrawable;
-import android.support.v4.graphics.ColorUtils;
 import android.os.CountDownTimer;
+import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewStub;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
@@ -39,10 +44,10 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import com.android.internal.logging.MetricsLogger;
-
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
@@ -59,6 +64,8 @@
         implements View.OnClickListener, View.OnLongClickListener {
 
     private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.125f;
+    private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f;
+    private static final int OVERLAY_REVEAL_DURATION = 250;
     private static final long FOCUS_INDICATOR_INTERVAL_MS = 30;
 
     /**
@@ -69,8 +76,6 @@
         private Paint mHighlightPaint = new Paint();
         private Paint mBackgroundPaint = new Paint();
 
-        private float[] mTmpHSL = new float[3];
-
         public HighlightColorDrawable() {
             mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
             mBackgroundPaint.setAntiAlias(true);
@@ -122,11 +127,16 @@
     Task mTask;
 
     // Header views
-    ImageView mMoveTaskButton;
-    ImageView mDismissButton;
     ImageView mIconView;
     TextView mTitleView;
-    int mMoveTaskTargetStackId = INVALID_STACK_ID;
+    ImageView mMoveTaskButton;
+    ImageView mDismissButton;
+    ViewStub mAppOverlayViewStub;
+    FrameLayout mAppOverlayView;
+    ImageView mAppIconView;
+    ImageView mAppInfoView;
+    TextView mAppTitleView;
+    ViewStub mFocusTimerIndicatorStub;
     ProgressBar mFocusTimerIndicator;
 
     // Header drawables
@@ -140,21 +150,26 @@
     Drawable mDarkFreeformIcon;
     Drawable mLightFullscreenIcon;
     Drawable mDarkFullscreenIcon;
+    Drawable mLightInfoIcon;
+    Drawable mDarkInfoIcon;
     int mTaskBarViewLightTextColor;
     int mTaskBarViewDarkTextColor;
+    int mMoveTaskTargetStackId = INVALID_STACK_ID;
 
     // Header background
     private HighlightColorDrawable mBackground;
+    private HighlightColorDrawable mOverlayBackground;
+    private float[] mTmpHSL = new float[3];
 
     // Header dim, which is only used when task view hardware layers are not used
     private Paint mDimLayerPaint = new Paint();
 
     Interpolator mFastOutSlowInInterpolator;
     Interpolator mFastOutLinearInInterpolator;
+    Interpolator mLinearOutSlowInInterpolator;
 
-    long mFocusIndicatorProgress;
     private CountDownTimer mFocusTimerCountDown;
-    long mFocusTimerDuration;
+    private long mFocusTimerDuration;
 
     public TaskViewHeader(Context context) {
         this(context, null);
@@ -184,36 +199,45 @@
         mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
         mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
         mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
+        mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
+        mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
 
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.fast_out_slow_in);
         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.fast_out_linear_in);
+        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.linear_out_slow_in);
 
         // Configure the background and dim
         mBackground = new HighlightColorDrawable();
         mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
         setBackground(mBackground);
+        mOverlayBackground = new HighlightColorDrawable();
         mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
         mDimLayerPaint.setAntiAlias(true);
         mFocusTimerDuration = res.getInteger(R.integer.recents_auto_advance_duration);
     }
 
+    /**
+     * Resets this header along with the TaskView.
+     */
+    public void reset() {
+        hideAppOverlay(true /* immediate */);
+    }
+
     @Override
     protected void onFinishInflate() {
         // Initialize the icon and description views
         mIconView = (ImageView) findViewById(R.id.icon);
+        mIconView.setClickable(false);
         mIconView.setOnLongClickListener(this);
         mTitleView = (TextView) findViewById(R.id.title);
         mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
         mDismissButton.setOnClickListener(this);
         mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
-        mFocusTimerIndicator = (ProgressBar) findViewById(R.id.focus_timer_indicator);
-
-        // Hide the backgrounds if they are ripple drawables
-        if (mIconView.getBackground() instanceof RippleDrawable) {
-            mIconView.setBackground(null);
-        }
+        mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub);
+        mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub);
     }
 
     /**
@@ -228,6 +252,7 @@
 
         mTaskViewRect.set(0, 0, width, height);
         boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE;
+        boolean isFreeformTask = (mTask != null) && mTask.isFreeformTask();
         int appIconWidth = mIconView.getMeasuredWidth();
         int activityDescWidth = (mTask != null)
                 ? (int) mTitleView.getPaint().measureText(mTask.title)
@@ -239,19 +264,20 @@
 
         // Priority-wise, we show the activity icon first, the dismiss icon if there is room, the
         // move-task icon if there is room, and then finally, the activity label if there is room
-        if (width < (appIconWidth + dismissIconWidth)) {
+        if (isFreeformTask && width < (appIconWidth + dismissIconWidth)) {
             mTitleView.setVisibility(View.INVISIBLE);
             if (updateMoveTaskButton) {
                 mMoveTaskButton.setVisibility(View.INVISIBLE);
             }
             mDismissButton.setVisibility(View.INVISIBLE);
-        } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth)) {
+        } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth +
+                moveTaskIconWidth)) {
             mTitleView.setVisibility(View.INVISIBLE);
             if (updateMoveTaskButton) {
                 mMoveTaskButton.setVisibility(View.INVISIBLE);
             }
             mDismissButton.setVisibility(View.VISIBLE);
-        } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth +
+        } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth + moveTaskIconWidth +
                 activityDescWidth)) {
             mTitleView.setVisibility(View.INVISIBLE);
             if (updateMoveTaskButton) {
@@ -273,11 +299,6 @@
     }
 
     @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return super.verifyDrawable(who) || (who == mBackground);
-    }
-
-    @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
 
@@ -288,6 +309,10 @@
 
     /** Starts the focus timer. */
     public void startFocusTimerIndicator() {
+        if (mFocusTimerIndicator == null) {
+            return;
+        }
+
         mFocusTimerIndicator.setVisibility(View.VISIBLE);
         mFocusTimerIndicator.setMax((int) mFocusTimerDuration);
         if (mFocusTimerCountDown == null) {
@@ -308,7 +333,11 @@
 
     /** Cancels the focus timer. */
     public void cancelFocusTimerIndicator() {
-        if (mFocusTimerCountDown != null && mFocusTimerIndicator != null) {
+        if (mFocusTimerIndicator == null) {
+            return;
+        }
+
+        if (mFocusTimerCountDown != null) {
             mFocusTimerCountDown.cancel();
             mFocusTimerIndicator.setProgress(0);
             mFocusTimerIndicator.setVisibility(View.INVISIBLE);
@@ -337,6 +366,10 @@
     private void updateBackgroundColor(float dimAlpha) {
         if (mTask != null) {
             mBackground.setColorAndDim(mTask.colorPrimary, dimAlpha);
+            // TODO: Consider using the saturation of the color to adjust the lightness as well
+            ColorUtils.colorToHSL(mTask.colorPrimary, mTmpHSL);
+            mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
+            mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
             mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
         }
     }
@@ -382,10 +415,15 @@
             mMoveTaskButton.setOnClickListener(this);
         }
 
-        mFocusTimerIndicator.getProgressDrawable()
-                .setColorFilter(
-                        getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
-                        PorterDuff.Mode.SRC_IN);
+        if (Recents.getDebugFlags().isFastToggleIndicatorEnabled()) {
+            if (mFocusTimerIndicator == null) {
+                mFocusTimerIndicator = (ProgressBar) mFocusTimerIndicatorStub.inflate();
+            }
+            mFocusTimerIndicator.getProgressDrawable()
+                    .setColorFilter(
+                            getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
+                            PorterDuff.Mode.SRC_IN);
+        }
 
         // In accessibility, a single click on the focused app info button will show it
         if (ssp.isTouchExplorationEnabled()) {
@@ -447,8 +485,11 @@
     @Override
     public void onClick(View v) {
         if (v == mIconView) {
-            // In accessibility, a single click on the focused app info button will show it
-            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
+            SystemServicesProxy ssp = Recents.getSystemServices();
+            if (ssp.isTouchExplorationEnabled()) {
+                // In accessibility, a single click on the focused app info button will show it
+                EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
+            }
         } else if (v == mDismissButton) {
             TaskView tv = Utilities.findParent(this, TaskView.class);
             tv.dismissTask();
@@ -463,15 +504,95 @@
                     : new Rect();
             EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds,
                     mMoveTaskTargetStackId, false));
+        } else if (v == mAppInfoView) {
+            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
+        } else if (v == mAppIconView) {
+            hideAppOverlay(false /* immediate */);
         }
     }
 
     @Override
     public boolean onLongClick(View v) {
         if (v == mIconView) {
-            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
+            showAppOverlay();
+            return true;
+        } else if (v == mAppIconView) {
+            hideAppOverlay(false /* immediate */);
             return true;
         }
         return false;
     }
+
+    /**
+     * Shows the application overlay.
+     */
+    private void showAppOverlay() {
+        // Skip early if the task is invalid
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ComponentName cn = mTask.key.getComponent();
+        int userId = mTask.key.userId;
+        ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
+        if (activityInfo == null) {
+            return;
+        }
+
+        // Inflate the overlay if necessary
+        if (mAppOverlayView == null) {
+            mAppOverlayView = (FrameLayout) mAppOverlayViewStub.inflate();
+            mAppOverlayView.setBackground(mOverlayBackground);
+            mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
+            mAppIconView.setOnClickListener(this);
+            mAppIconView.setOnLongClickListener(this);
+            mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
+            mAppInfoView.setOnClickListener(this);
+            mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
+        }
+
+        // Update the overlay contents for the current app
+        mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
+        mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
+                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
+        mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
+                userId));
+        mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
+                ? mLightInfoIcon
+                : mDarkInfoIcon);
+        mAppOverlayView.setVisibility(View.VISIBLE);
+
+        int x = mIconView.getLeft() + mIconView.getWidth() / 2;
+        int y = mIconView.getTop() + mIconView.getHeight() / 2;
+        Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
+                getWidth());
+        revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
+        revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
+        revealAnim.start();
+    }
+
+    /**
+     * Hide the application overlay.
+     */
+    private void hideAppOverlay(boolean immediate) {
+        // Skip if we haven't even loaded the overlay yet
+        if (mAppOverlayView == null) {
+            return;
+        }
+
+        if (immediate) {
+            mAppOverlayView.setVisibility(View.GONE);
+        } else {
+            int x = mIconView.getLeft() + mIconView.getWidth() / 2;
+            int y = mIconView.getTop() + mIconView.getHeight() / 2;
+            Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
+                    getWidth(), 0);
+            revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
+            revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
+            revealAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mAppOverlayView.setVisibility(View.GONE);
+                }
+            });
+            revealAnim.start();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 538c248..85b7c82 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -88,12 +88,39 @@
     public float alpha = 1f;
 
     public boolean visible = false;
-    float p = 0f;
+
+    // This is the relative task progress of this task, relative to the stack scroll at which this
+    // transform was computed
+    public float p = 0f;
 
     // This is a window-space rect used for positioning the task in the stack and freeform workspace
     public RectF rect = new RectF();
 
     /**
+     * Fills int this transform from the state of the given TaskView.
+     */
+    public void fillIn(TaskView tv) {
+        translationZ = tv.getTranslationZ();
+        scale = tv.getScaleX();
+        alpha = tv.getAlpha();
+        visible = true;
+        p = tv.getTaskProgress();
+        rect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
+    }
+
+    /**
+     * Copies the transform state from another {@link TaskViewTransform}.
+     */
+    public void copyFrom(TaskViewTransform other) {
+        translationZ = other.translationZ;
+        scale = other.scale;
+        alpha = other.alpha;
+        visible = other.visible;
+        p = other.p;
+        rect.set(other.rect);
+    }
+
+    /**
      * Resets the current transform.
      */
     public void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index c16703e8..9e83dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -167,8 +167,7 @@
     public boolean startDragging(boolean animate) {
         mHandle.setTouching(true, animate);
         mDockSide = mWindowManagerProxy.getDockSide();
-        mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
-                mFlingAnimationUtils.getMinVelocityPxPerSecond(), mDisplayWidth,
+        mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
                 mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
         if (mDockSide != WindowManager.DOCKED_INVALID) {
             mWindowManagerProxy.setResizing(true);
@@ -180,9 +179,9 @@
         }
     }
 
-    public void stopDragging(int position, float velocity) {
+    public void stopDragging(int position, float velocity, boolean avoidDismissStart) {
         mHandle.setTouching(false, true /* animate */);
-        fling(position, velocity);
+        fling(position, velocity, avoidDismissStart);
         mWindowManager.setSlippery(true);
         releaseBackground();
     }
@@ -225,7 +224,7 @@
                 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
                     int position = calculatePosition(x, y);
                     SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position,
-                            0 /* velocity */);
+                            0 /* velocity */, false /* hardDismiss */);
                     resizeStack(calculatePosition(x, y), snapTarget.position, snapTarget);
                 }
                 break;
@@ -239,7 +238,7 @@
                 mVelocityTracker.computeCurrentVelocity(1000);
                 int position = calculatePosition(x, y);
                 stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
-                        : mVelocityTracker.getXVelocity());
+                        : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */);
                 mMoving = false;
                 break;
         }
@@ -250,8 +249,12 @@
         event.setLocation(event.getRawX(), event.getRawY());
     }
 
-    private void fling(int position, float velocity) {
-        final SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
+    private void fling(int position, float velocity, boolean avoidDismissStart) {
+        SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
+        if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
+            snapTarget = mSnapAlgorithm.getFirstSplitTarget();
+        }
+        final SnapTarget finalTarget = snapTarget;
 
         ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
         anim.addUpdateListener(new AnimatorUpdateListener() {
@@ -260,13 +263,13 @@
                 resizeStack((Integer) animation.getAnimatedValue(),
                         animation.getAnimatedFraction() == 1f
                                 ? TASK_POSITION_SAME
-                                : snapTarget.position, snapTarget);
+                                : finalTarget.position, finalTarget);
             }
         });
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                commitSnapFlags(snapTarget);
+                commitSnapFlags(finalTarget);
                 mWindowManagerProxy.setResizing(false);
                 mDockSide = WindowManager.DOCKED_INVALID;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index da3cd54..38d24ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -176,26 +176,31 @@
     };
 
     @Override
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        if (mDimmed && !mActivated) {
-            return handleTouchEventDimmed(event);
-        } else {
-            return super.dispatchTouchEvent(event);
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (mDimmed && !mActivated
+                && ev.getActionMasked() == MotionEvent.ACTION_DOWN && disallowSingleClick(ev)) {
+            return true;
         }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    protected boolean disallowSingleClick(MotionEvent ev) {
+        return false;
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         boolean result;
-        if (mDimmed && mActivated) {
+        if (mDimmed) {
+            boolean wasActivated = mActivated;
             result = handleTouchEventDimmed(event);
+            if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
+                mFalsingManager.onNotificationDoubleTap();
+                removeCallbacks(mTapTimeoutRunnable);
+            }
         } else {
             result = super.onTouchEvent(event);
         }
-        if (mActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
-            mFalsingManager.onNotificationDoubleTap();
-            removeCallbacks(mTapTimeoutRunnable);
-        }
         return result;
     }
 
@@ -608,8 +613,6 @@
                     onFinishedRunnable.run();
                 }
                 if (!mWasCancelled) {
-                    mAppearAnimationFraction = -1;
-                    setOutlineRect(null);
                     enableAppearDrawing(false);
                 }
             }
@@ -630,6 +633,7 @@
     private void cancelAppearAnimation() {
         if (mAppearAnimator != null) {
             mAppearAnimator.cancel();
+            mAppearAnimator = null;
         }
     }
 
@@ -735,6 +739,8 @@
             mDrawingAppearAnimation = enable;
             if (!enable) {
                 setContentAlpha(1.0f);
+                mAppearAnimationFraction = -1;
+                setOutlineRect(null);
             }
             invalidate();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index ce4eff5..2592486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1265,18 +1265,22 @@
     }
 
     /**
-     * Called when the size of the notification panel changes
+     * Called when the notification panel layouts
      */
-    public void onPanelHeightChanged() {
+    public void onPanelLaidOut() {
         if (mState == StatusBarState.KEYGUARD) {
             // Since the number of notifications is determined based on the height of the view, we
             // need to update them.
-            updateRowStates();
+            int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
+            int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+            if (maxBefore != maxNotifications) {
+                updateRowStates();
+            }
         }
     }
 
     @Override
-    public void onExpandClicked(View clickedView, boolean nowExpanded) {
+    public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
     }
 
     protected class H extends Handler {
@@ -1359,6 +1363,7 @@
                     parent, false);
             row.setExpansionLogger(this, entry.notification.getKey());
             row.setGroupManager(mGroupManager);
+            row.setHeadsUpManager(mHeadsUpManager);
             row.setRemoteInputController(mRemoteInputController);
             row.setOnExpandClickListener(this);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
new file mode 100644
index 0000000..f71f092
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+import android.view.View;
+
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+/**
+ * A helper to fade views in and out.
+ */
+public class CrossFadeHelper {
+    public static final long ANIMATION_DURATION_LENGTH = 210;
+
+    public static void fadeOut(final View view, final Runnable endRunnable) {
+        view.animate().cancel();
+        view.animate()
+                .alpha(0f)
+                .setDuration(ANIMATION_DURATION_LENGTH)
+                .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (endRunnable != null) {
+                            endRunnable.run();
+                        }
+                        view.setVisibility(View.INVISIBLE);
+                    }
+                });
+        if (view.hasOverlappingRendering()) {
+            view.animate().withLayer();
+        }
+
+    }
+
+    public static void fadeIn(final View view) {
+        view.animate().cancel();
+        if (view.getVisibility() == View.INVISIBLE) {
+            view.setAlpha(0.0f);
+            view.setVisibility(View.VISIBLE);
+        }
+        view.animate()
+                .alpha(1f)
+                .setDuration(ANIMATION_DURATION_LENGTH)
+                .setInterpolator(PhoneStatusBar.ALPHA_IN)
+                .withEndAction(null);
+        if (view.hasOverlappingRendering()) {
+            view.animate().withLayer();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 83853a2..52e6a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -25,8 +25,10 @@
 import android.os.Build;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.NotificationHeaderView;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.Chronometer;
@@ -35,7 +37,9 @@
 
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
 import com.android.systemui.statusbar.stack.StackScrollState;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -59,6 +63,11 @@
     private boolean mHasUserChangedExpansion;
     /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
     private boolean mUserExpanded;
+
+    /**
+     * Has this notification been expanded while it was pinned
+     */
+    private boolean mExpandedWhenPinned;
     /** Is the user touching this row */
     private boolean mUserLocked;
     /** Are we showing the "public" version */
@@ -103,6 +112,7 @@
     private boolean mIsSystemChildExpanded;
     private boolean mIsPinned;
     private FalsingManager mFalsingManager;
+    private HeadsUpManager mHeadsUpManager;
     private NotificationHeaderUtil mHeaderUtil = new NotificationHeaderUtil(this);
 
     private boolean mJustClicked;
@@ -115,14 +125,19 @@
         public void onClick(View v) {
             if (!mShowingPublic && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
                 mGroupManager.toggleGroupExpansion(mStatusBarNotification);
-                mOnExpandClickListener.onExpandClicked(ExpandableNotificationRow.this,
+                mOnExpandClickListener.onExpandClicked(mEntry,
                         mGroupManager.isGroupExpanded(mStatusBarNotification));
             } else {
-                boolean nowExpanded = !isExpanded();
-                setUserExpanded(nowExpanded);
+                boolean nowExpanded;
+                if (isPinned()) {
+                    nowExpanded = !mExpandedWhenPinned;
+                    mExpandedWhenPinned = nowExpanded;
+                } else {
+                    nowExpanded = !isExpanded();
+                    setUserExpanded(nowExpanded);
+                }
                 notifyHeightChanged(true);
-                mOnExpandClickListener.onExpandClicked(ExpandableNotificationRow.this,
-                        nowExpanded);
+                mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
             }
         }
     };
@@ -303,6 +318,16 @@
     }
 
     @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getActionMasked() != MotionEvent.ACTION_DOWN
+                || !isChildInGroup() || isGroupExpanded()) {
+            return super.onTouchEvent(event);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
     protected boolean shouldHideBackground() {
         return super.shouldHideBackground() || mShowNoBackground;
     }
@@ -358,9 +383,9 @@
     }
 
     public void startChildAnimation(StackScrollState finalState,
-            StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) {
+            StackStateAnimator stateAnimator, long delay, long duration) {
         if (mIsSummaryWithChildren) {
-            mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay,
+            mChildrenContainer.startAnimationToState(finalState, stateAnimator, delay,
                     duration);
         }
     }
@@ -386,6 +411,12 @@
      */
     public void setPinned(boolean pinned) {
         mIsPinned = pinned;
+        if (pinned) {
+            setIconAnimationRunning(true);
+            mExpandedWhenPinned = false;
+        } else if (mExpandedWhenPinned) {
+            setUserExpanded(true);
+        }
         setChronometerRunning(mLastChronometerRunning);
     }
 
@@ -393,11 +424,22 @@
         return mIsPinned;
     }
 
-    public int getHeadsUpHeight() {
+    /**
+     * @param atLeastMinHeight should the value returned be at least the minimum height.
+     *                         Used to avoid cyclic calls
+     * @return the height of the heads up notification when pinned
+     */
+    public int getPinnedHeadsUpHeight(boolean atLeastMinHeight) {
         if (mIsSummaryWithChildren) {
             return mChildrenContainer.getIntrinsicHeight();
         }
-        return mHeadsUpHeight;
+        if(mExpandedWhenPinned) {
+            return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
+        } else if (atLeastMinHeight) {
+            return Math.max(getMinHeight(), mHeadsUpHeight);
+        } else {
+            return mHeadsUpHeight;
+        }
     }
 
     /**
@@ -461,6 +503,10 @@
         mOnExpandClickListener = onExpandClickListener;
     }
 
+    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+        mHeadsUpManager = headsUpManager;
+    }
+
     public interface ExpansionLogger {
         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
@@ -517,8 +563,10 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
+        mPublicLayout.setContainingNotification(this);
         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
         mPrivateLayout.setExpandClickListener(mExpandClickListener);
+        mPrivateLayout.setContainingNotification(this);
         mPublicLayout.setExpandClickListener(mExpandClickListener);
         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@@ -551,13 +599,14 @@
     private void updateChildrenVisibility() {
         mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE
                 : INVISIBLE);
-        if (mChildrenContainer == null) {
-            return;
+        if (mChildrenContainer != null) {
+            mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
+                    : INVISIBLE);
         }
-        mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
-                : INVISIBLE);
-        mNotificationHeader.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
-                : INVISIBLE);
+        if (mNotificationHeader != null) {
+            mNotificationHeader.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
+                    : INVISIBLE);
+        }
         // The limits might have changed if the view suddenly became a group or vice versa
         updateLimits();
     }
@@ -601,6 +650,12 @@
         mPrivateLayout.updateExpandButtons(isExpandable());
     }
 
+    @Override
+    public void setClipToActualHeight(boolean clipToActualHeight) {
+        super.setClipToActualHeight(clipToActualHeight);
+        getShowingLayout().setClipToActualHeight(clipToActualHeight);
+    }
+
     /**
      * @return whether the user has changed the expansion state
      */
@@ -718,7 +773,6 @@
         if (isUserLocked()) {
             return getActualHeight();
         }
-        boolean inExpansionState = isExpanded();
         if (mGuts != null && mGuts.areGutsExposed()) {
             return mGuts.getHeight();
         } else if ((isChildInGroup() && !isGroupExpanded())) {
@@ -728,12 +782,14 @@
         } else if (mIsSummaryWithChildren && !mOnKeyguard) {
             return mChildrenContainer.getIntrinsicHeight();
         } else if (mIsHeadsUp) {
-            if (inExpansionState) {
+            if (isPinned()) {
+                return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
+            } else if (isExpanded()) {
                 return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
             } else {
                 return Math.max(getMinHeight(), mHeadsUpHeight);
             }
-        } else if (inExpansionState) {
+        } else if (isExpanded()) {
             return getMaxExpandHeight();
         } else {
             return getMinHeight();
@@ -818,6 +874,12 @@
         }
     }
 
+    @Override
+    public void notifyHeightChanged(boolean needsAnimation) {
+        super.notifyHeightChanged(needsAnimation);
+        getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
+    }
+
     public void setSensitive(boolean sensitive) {
         mSensitive = sensitive;
     }
@@ -961,8 +1023,12 @@
 
     @Override
     public int getMinHeight() {
-        if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
+        if (mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
+                return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
+        } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
             return mChildrenContainer.getMinHeight();
+        } else if (mIsHeadsUp) {
+            return mHeadsUpHeight;
         }
         NotificationContentView showingLayout = getShowingLayout();
         return showingLayout.getMinHeight();
@@ -1001,7 +1067,7 @@
             addView(mNotificationHeader, indexOfChild(mChildrenContainer) + 1);
         } else {
             header.reapply(getContext(), mNotificationHeader);
-            mNotificationHeaderWrapper.notifyContentUpdated();
+            mNotificationHeaderWrapper.notifyContentUpdated(mEntry.notification);
         }
         updateHeaderExpandButton();
         updateChildrenHeaderAppearance();
@@ -1059,6 +1125,17 @@
         mLoggingKey = key;
     }
 
+    @Override
+    protected boolean disallowSingleClick(MotionEvent event) {
+        float x = event.getX();
+        float y = event.getY();
+        NotificationHeaderView header = getNotificationHeader();
+        if (header != null) {
+            return header.isInTouchRect(x, y);
+        }
+        return super.disallowSingleClick(event);
+    }
+
     private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
         final boolean nowExpanded = isExpanded();
         if (wasExpanded != nowExpanded && mLogger != null) {
@@ -1067,6 +1144,6 @@
     }
 
     public interface OnExpandClickListener {
-        void onExpandClicked(View clickedView, boolean nowExpanded);
+        void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index d6855a5..c190864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -45,6 +45,7 @@
     private static Rect mClipRect = new Rect();
     private boolean mWillBeGone;
     private int mMinClipTopAmount = 0;
+    private boolean mClipToActualHeight = true;
 
     public ExpandableView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -326,12 +327,21 @@
     }
 
     private void updateClipping() {
-        int top = mClipTopOptimization;
-        if (top >= getActualHeight()) {
-            top = getActualHeight() - 1;
+        if (mClipToActualHeight) {
+            int top = mClipTopOptimization;
+            if (top >= getActualHeight()) {
+                top = getActualHeight() - 1;
+            }
+            mClipRect.set(0, top, getWidth(), getActualHeight());
+            setClipBounds(mClipRect);
+        } else {
+            setClipBounds(null);
         }
-        mClipRect.set(0, top, getWidth(), getActualHeight());
-        setClipBounds(mClipRect);
+    }
+
+    public void setClipToActualHeight(boolean clipToActualHeight) {
+        mClipToActualHeight = clipToActualHeight;
+        updateClipping();
     }
 
     public int getClipTopOptimization() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 02a39e7..36cf906 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -20,9 +20,6 @@
 import android.app.RemoteInput;
 import android.content.Context;
 import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.os.Build;
 import android.service.notification.StatusBarNotification;
@@ -32,13 +29,13 @@
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
+import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 
@@ -49,7 +46,6 @@
  */
 public class NotificationContentView extends FrameLayout {
 
-    private static final long ANIMATION_DURATION_LENGTH = 170;
     private static final int VISIBLE_TYPE_CONTRACTED = 0;
     private static final int VISIBLE_TYPE_EXPANDED = 1;
     private static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -57,9 +53,16 @@
 
     private final Rect mClipBounds = new Rect();
     private final int mRoundRectRadius;
-    private final Interpolator mLinearInterpolator = new LinearInterpolator();
     private final boolean mRoundRectClippingEnabled;
     private final int mMinContractedHeight;
+    private final OnLayoutChangeListener mLayoutUpdater = new OnLayoutChangeListener() {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                int oldLeft,
+                int oldTop, int oldRight, int oldBottom) {
+            selectLayout(false /* animate */, false /* force */);
+        }
+    };
 
 
     private View mContractedChild;
@@ -76,7 +79,6 @@
     private int mUnrestrictedContentHeight;
     private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
     private boolean mDark;
-    private final Paint mFadePaint = new Paint();
     private boolean mAnimate;
     private boolean mIsHeadsUp;
     private boolean mShowingLegacyBackground;
@@ -108,11 +110,12 @@
     private OnClickListener mExpandClickListener;
     private boolean mBeforeN;
     private boolean mExpandable;
+    private boolean mClipToActualHeight = true;
+    private ExpandableNotificationRow mContainingNotification;
 
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
-        mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
         mRoundRectRadius = getResources().getDimensionPixelSize(
                 R.dimen.notification_material_rounded_rect_radius);
         mRoundRectClippingEnabled = getResources().getBoolean(
@@ -241,10 +244,12 @@
     public void setContractedChild(View child) {
         if (mContractedChild != null) {
             mContractedChild.animate().cancel();
+            mContractedChild.removeOnLayoutChangeListener(mLayoutUpdater);
             removeView(mContractedChild);
         }
         addView(child);
         mContractedChild = child;
+        mContractedChild.addOnLayoutChangeListener(mLayoutUpdater);
         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child);
         selectLayout(false /* animate */, true /* force */);
         mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
@@ -254,10 +259,12 @@
     public void setExpandedChild(View child) {
         if (mExpandedChild != null) {
             mExpandedChild.animate().cancel();
+            mExpandedChild.removeOnLayoutChangeListener(mLayoutUpdater);
             removeView(mExpandedChild);
         }
         addView(child);
         mExpandedChild = child;
+        mExpandedChild.addOnLayoutChangeListener(mLayoutUpdater);
         mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child);
         selectLayout(false /* animate */, true /* force */);
         updateRoundRectClipping();
@@ -266,10 +273,12 @@
     public void setHeadsUpChild(View child) {
         if (mHeadsUpChild != null) {
             mHeadsUpChild.animate().cancel();
+            mHeadsUpChild.removeOnLayoutChangeListener(mLayoutUpdater);
             removeView(mHeadsUpChild);
         }
         addView(child);
         mHeadsUpChild = child;
+        mHeadsUpChild.addOnLayoutChangeListener(mLayoutUpdater);
         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child);
         selectLayout(false /* animate */, true /* force */);
         updateRoundRectClipping();
@@ -357,8 +366,17 @@
     }
 
     private void updateClipping() {
-        mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
-        setClipBounds(mClipBounds);
+        if (mClipToActualHeight) {
+            mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
+            setClipBounds(mClipBounds);
+        } else {
+            setClipBounds(null);
+        }
+    }
+
+    public void setClipToActualHeight(boolean clipToActualHeight) {
+        mClipToActualHeight = clipToActualHeight;
+        updateClipping();
     }
 
     private void selectLayout(boolean animate, boolean force) {
@@ -371,7 +389,7 @@
                     || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
                     || (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
                     || visibleType == VISIBLE_TYPE_CONTRACTED)) {
-                runSwitchAnimation(visibleType);
+                animateToVisibleType(visibleType);
             } else {
                 updateViewVisibilities(visibleType);
             }
@@ -381,59 +399,55 @@
 
     private void updateViewVisibilities(int visibleType) {
         boolean contractedVisible = visibleType == VISIBLE_TYPE_CONTRACTED;
-        mContractedChild.setVisibility(contractedVisible ? View.VISIBLE : View.INVISIBLE);
-        mContractedChild.setAlpha(contractedVisible ? 1f : 0f);
-        mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
+        mContractedWrapper.setVisible(contractedVisible);
         if (mExpandedChild != null) {
             boolean expandedVisible = visibleType == VISIBLE_TYPE_EXPANDED;
-            mExpandedChild.setVisibility(expandedVisible ? View.VISIBLE : View.INVISIBLE);
-            mExpandedChild.setAlpha(expandedVisible ? 1f : 0f);
-            mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
+            mExpandedWrapper.setVisible(expandedVisible);
         }
         if (mHeadsUpChild != null) {
             boolean headsUpVisible = visibleType == VISIBLE_TYPE_HEADSUP;
-            mHeadsUpChild.setVisibility(headsUpVisible ? View.VISIBLE : View.INVISIBLE);
-            mHeadsUpChild.setAlpha(headsUpVisible ? 1f : 0f);
-            mHeadsUpChild.setLayerType(LAYER_TYPE_NONE, null);
+            mHeadsUpWrapper.setVisible(headsUpVisible);
         }
         if (mSingleLineView != null) {
             boolean singleLineVisible = visibleType == VISIBLE_TYPE_SINGLELINE;
-            mSingleLineView.setVisibility(singleLineVisible ? View.VISIBLE : View.INVISIBLE);
-            mSingleLineView.setAlpha(singleLineVisible ? 1f : 0f);
-            mSingleLineView.setLayerType(LAYER_TYPE_NONE, null);
+            mSingleLineView.setVisible(singleLineVisible);
         }
-        setLayerType(LAYER_TYPE_NONE, null);
         updateRoundRectClipping();
     }
 
-    private void runSwitchAnimation(int visibleType) {
-        View shownView = getViewForVisibleType(visibleType);
-        View hiddenView = getViewForVisibleType(mVisibleType);
-        shownView.setVisibility(View.VISIBLE);
-        hiddenView.setVisibility(View.VISIBLE);
-        shownView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
-        hiddenView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
-        setLayerType(LAYER_TYPE_HARDWARE, null);
-        hiddenView.animate()
-                .alpha(0f)
-                .setDuration(ANIMATION_DURATION_LENGTH)
-                .setInterpolator(mLinearInterpolator)
-                .withEndAction(null); // In case we have multiple changes in one frame.
-        shownView.animate()
-                .alpha(1f)
-                .setDuration(ANIMATION_DURATION_LENGTH)
-                .setInterpolator(mLinearInterpolator)
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        updateViewVisibilities(mVisibleType);
-                    }
-                });
+    private void animateToVisibleType(int visibleType) {
+        final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
+        final TransformableView hiddenView = getTransformableViewForVisibleType(mVisibleType);
+        shownView.transformFrom(hiddenView);
+        getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
+        hiddenView.transformTo(shownView, new Runnable() {
+            @Override
+            public void run() {
+                hiddenView.setVisible(false);
+            }
+        });
         updateRoundRectClipping();
     }
 
     /**
      * @param visibleType one of the static enum types in this view
+     * @return the corresponding transformable view according to the given visible type
+     */
+    private TransformableView getTransformableViewForVisibleType(int visibleType) {
+        switch (visibleType) {
+            case VISIBLE_TYPE_EXPANDED:
+                return mExpandedWrapper;
+            case VISIBLE_TYPE_HEADSUP:
+                return mHeadsUpWrapper;
+            case VISIBLE_TYPE_SINGLELINE:
+                return mSingleLineView;
+            default:
+                return mContractedWrapper;
+        }
+    }
+
+    /**
+     * @param visibleType one of the static enum types in this view
      * @return the corresponding view according to the given visible type
      */
     private View getViewForVisibleType(int visibleType) {
@@ -455,7 +469,8 @@
     private int calculateVisibleType() {
         boolean noExpandedChild = mExpandedChild == null;
 
-        if (!noExpandedChild && mContentHeight == mExpandedChild.getHeight()) {
+        int viewHeight = Math.min(mContentHeight, mContainingNotification.getIntrinsicHeight());
+        if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) {
             return VISIBLE_TYPE_EXPANDED;
         }
         if (mIsChildInGroup && !isGroupExpanded()) {
@@ -463,13 +478,13 @@
         }
 
         if (mIsHeadsUp && mHeadsUpChild != null) {
-            if (mContentHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
+            if (viewHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
                 return VISIBLE_TYPE_HEADSUP;
             } else {
                 return VISIBLE_TYPE_EXPANDED;
             }
         } else {
-            if (mContentHeight <= mContractedChild.getHeight() || noExpandedChild) {
+            if (viewHeight <= mContractedChild.getHeight() || noExpandedChild) {
                 return VISIBLE_TYPE_CONTRACTED;
             } else {
                 return VISIBLE_TYPE_EXPANDED;
@@ -484,8 +499,17 @@
     public void setDark(boolean dark, boolean fade, long delay) {
         if (mDark == dark || mContractedChild == null) return;
         mDark = dark;
-        mContractedWrapper.setDark(dark && !mShowingLegacyBackground, fade, delay);
-        if (mSingleLineView != null) {
+        dark = dark && !mShowingLegacyBackground;
+        if (mVisibleType == VISIBLE_TYPE_CONTRACTED) {
+            mContractedWrapper.setDark(dark, fade, delay);
+        }
+        if (mVisibleType == VISIBLE_TYPE_EXPANDED) {
+            mExpandedWrapper.setDark(dark, fade, delay);
+        }
+        if (mVisibleType == VISIBLE_TYPE_HEADSUP) {
+            mHeadsUpWrapper.setDark(dark, fade, delay);
+        }
+        if (mSingleLineView != null && mVisibleType == VISIBLE_TYPE_SINGLELINE) {
             mSingleLineView.setDark(dark, fade, delay);
         }
     }
@@ -520,14 +544,14 @@
         applyRemoteInput(entry);
         selectLayout(false /* animate */, true /* force */);
         if (mContractedChild != null) {
-            mContractedWrapper.notifyContentUpdated();
+            mContractedWrapper.notifyContentUpdated(entry.notification);
             mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
         }
         if (mExpandedChild != null) {
-            mExpandedWrapper.notifyContentUpdated();
+            mExpandedWrapper.notifyContentUpdated(entry.notification);
         }
         if (mHeadsUpChild != null) {
-            mHeadsUpWrapper.notifyContentUpdated();
+            mHeadsUpWrapper.notifyContentUpdated(entry.notification);
         }
         updateRoundRectClipping();
     }
@@ -644,4 +668,12 @@
         }
         return header;
     }
+
+    public void setContainingNotification(ExpandableNotificationRow containingNotification) {
+        mContainingNotification = containingNotification;
+    }
+
+    public void requestSelectLayout(boolean needsAnimation) {
+        selectLayout(needsAnimation, false);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 52326e3..e4cd7d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -29,6 +29,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RadioButton;
 import android.widget.SeekBar;
@@ -133,6 +134,10 @@
         } catch (PackageManager.NameNotFoundException e) {
             // unlikely.
         }
+        if (systemApp) {
+            ((ImageView) row.findViewById(R.id.low_importance)).getDrawable().setTint(
+                    mContext.getColor(R.color.notification_guts_disabled_icon_tint));
+        }
         final int minProgress = systemApp ?
                 NotificationListenerService.Ranking.IMPORTANCE_LOW
                 : NotificationListenerService.Ranking.IMPORTANCE_NONE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 859a330..98a37f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -110,6 +110,25 @@
                 sIconExtractor,
                 sGreyComparator,
                 mGreyApplicator));
+        mComparators.add(new HeaderProcessor(mRow,
+                com.android.internal.R.id.profile_badge,
+                null /* Extractor */,
+                new ViewComparator() {
+                    @Override
+                    public boolean compare(View parent, View child, Object parentData,
+                            Object childData) {
+                        return parent.getVisibility() == View.VISIBLE;
+                    }
+
+                    @Override
+                    public boolean isEmpty(View view) {
+                        if (view instanceof ImageView) {
+                            return ((ImageView) view).getDrawable() == null;
+                        }
+                        return false;
+                    }
+                },
+                sVisibilityApplicator));
         mComparators.add(HeaderProcessor.forTextView(mRow,
                 com.android.internal.R.id.app_name_text));
         mComparators.add(HeaderProcessor.forTextView(mRow,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
deleted file mode 100644
index 77e8c55..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Color;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-
-/**
- * Wraps a notification view inflated from a template.
- */
-public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper {
-
-    private static final int mDarkProgressTint = 0xffffffff;
-
-    protected ImageView mPicture;
-    private ProgressBar mProgressBar;
-
-    protected NotificationTemplateViewWrapper(Context ctx, View view) {
-        super(ctx, view);
-        resolveTemplateViews();
-    }
-
-    private void resolveTemplateViews() {
-        View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
-        mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
-        final View progress = mView.findViewById(com.android.internal.R.id.progress);
-        if (progress instanceof ProgressBar) {
-            mProgressBar = (ProgressBar) progress;
-        } else {
-            // It's still a viewstub
-            mProgressBar = null;
-        }
-        if (mainColumn != null) {
-            mInvertHelper.addTarget(mainColumn);
-        }
-    }
-
-    @Override
-    public void notifyContentUpdated() {
-        super.notifyContentUpdated();
-
-        // Reinspect the notification.
-        resolveTemplateViews();
-    }
-
-    @Override
-    public void setDark(boolean dark, boolean fade, long delay) {
-        super.setDark(dark, fade, delay);
-        setPictureGrayscale(dark, fade, delay);
-        setProgressBarDark(dark, fade, delay);
-    }
-
-    private void setProgressBarDark(boolean dark, boolean fade, long delay) {
-        if (mProgressBar != null) {
-            if (fade) {
-                fadeProgressDark(mProgressBar, dark, delay);
-            } else {
-                updateProgressDark(mProgressBar, dark);
-            }
-        }
-    }
-
-    private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float t = (float) animation.getAnimatedValue();
-                updateProgressDark(target, t);
-            }
-        }, dark, delay, null /* listener */);
-    }
-
-    private void updateProgressDark(ProgressBar target, float intensity) {
-        int color = interpolateColor(mColor, mDarkProgressTint, intensity);
-        target.getIndeterminateDrawable().mutate().setTint(color);
-        target.getProgressDrawable().mutate().setTint(color);
-    }
-
-    private void updateProgressDark(ProgressBar target, boolean dark) {
-        updateProgressDark(target, dark ? 1f : 0f);
-    }
-
-    protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
-        if (mPicture != null) {
-            if (fade) {
-                fadeGrayscale(mPicture, grayscale, delay);
-            } else {
-                updateGrayscale(mPicture, grayscale);
-            }
-        }
-    }
-
-    private static int interpolateColor(int source, int target, float t) {
-        int aSource = Color.alpha(source);
-        int rSource = Color.red(source);
-        int gSource = Color.green(source);
-        int bSource = Color.blue(source);
-        int aTarget = Color.alpha(target);
-        int rTarget = Color.red(target);
-        int gTarget = Color.green(target);
-        int bTarget = Color.blue(target);
-        return Color.argb(
-                (int) (aSource * (1f - t) + aTarget * t),
-                (int) (rSource * (1f - t) + rTarget * t),
-                (int) (gSource * (1f - t) + gTarget * t),
-                (int) (bSource * (1f - t) + bTarget * t));
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScalingDrawableWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScalingDrawableWrapper.java
new file mode 100644
index 0000000..24277e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScalingDrawableWrapper.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableWrapper;
+
+/**
+ * An extension of {@link DrawableWrapper} that will take a given Drawable and scale it by
+ * the given factor.
+ */
+class ScalingDrawableWrapper extends DrawableWrapper {
+    private float mScaleFactor;
+
+    public ScalingDrawableWrapper(Drawable drawable, float scaleFactor) {
+        super(drawable);
+        mScaleFactor = scaleFactor;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return (int) (super.getIntrinsicWidth() * mScaleFactor);
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return (int) (super.getIntrinsicHeight() * mScaleFactor);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 68e483c..6801e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,16 +16,18 @@
 
 package com.android.systemui.statusbar;
 
+import android.annotation.DrawableRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
 import android.graphics.Color;
-import android.graphics.PorterDuff;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
 import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -86,10 +88,11 @@
     View mWifiSignalSpacer;
     LinearLayout mMobileSignalGroup;
 
-    private int mWideTypeIconStartPadding;
-    private int mSecondaryTelephonyPadding;
-    private int mEndPadding;
-    private int mEndPaddingNothingVisible;
+    private final int mWideTypeIconStartPadding;
+    private final int mSecondaryTelephonyPadding;
+    private final int mEndPadding;
+    private final int mEndPaddingNothingVisible;
+    private final float mIconScaleFactor;
 
     private boolean mBlockAirplane;
     private boolean mBlockMobile;
@@ -106,6 +109,17 @@
 
     public SignalClusterView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+
+        Resources res = getResources();
+        mWideTypeIconStartPadding = res.getDimensionPixelSize(R.dimen.wide_type_icon_start_padding);
+        mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding);
+        mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding);
+        mEndPaddingNothingVisible = res.getDimensionPixelSize(
+                R.dimen.no_signal_cluster_battery_padding);
+
+        TypedValue typedValue = new TypedValue();
+        res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
+        mIconScaleFactor = typedValue.getFloat();
     }
 
     @Override
@@ -146,19 +160,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mWideTypeIconStartPadding = getContext().getResources().getDimensionPixelSize(
-                R.dimen.wide_type_icon_start_padding);
-        mSecondaryTelephonyPadding = getContext().getResources().getDimensionPixelSize(
-                R.dimen.secondary_telephony_padding);
-        mEndPadding = getContext().getResources().getDimensionPixelSize(
-                R.dimen.signal_cluster_battery_padding);
-        mEndPaddingNothingVisible = getContext().getResources().getDimensionPixelSize(
-                R.dimen.no_signal_cluster_battery_padding);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
 
         mVpn            = (ImageView) findViewById(R.id.vpn);
         mEthernetGroup  = (ViewGroup) findViewById(R.id.ethernet_combo);
@@ -174,6 +175,32 @@
         mWifiAirplaneSpacer =         findViewById(R.id.wifi_airplane_spacer);
         mWifiSignalSpacer =           findViewById(R.id.wifi_signal_spacer);
         mMobileSignalGroup = (LinearLayout) findViewById(R.id.mobile_signal_group);
+
+        maybeScaleVpnAndNoSimsIcons();
+    }
+
+    /**
+     * Extracts the icon off of the VPN and no sims views and maybe scale them by
+     * {@link #mIconScaleFactor}. Note that the other icons are not scaled here because they are
+     * dynamic. As such, they need to be scaled each time the icon changes in {@link #apply()}.
+     */
+    private void maybeScaleVpnAndNoSimsIcons() {
+        if (mIconScaleFactor == 1.f) {
+            return;
+        }
+
+        mVpn.setImageDrawable(new ScalingDrawableWrapper(mVpn.getDrawable(), mIconScaleFactor));
+
+        mNoSims.setImageDrawable(
+                new ScalingDrawableWrapper(mNoSims.getDrawable(), mIconScaleFactor));
+        mNoSimsDark.setImageDrawable(
+                new ScalingDrawableWrapper(mNoSimsDark.getDrawable(), mIconScaleFactor));
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
         for (PhoneState state : mPhoneStates) {
             mMobileSignalGroup.addView(state.mMobileGroup);
         }
@@ -185,14 +212,7 @@
 
     @Override
     protected void onDetachedFromWindow() {
-        mVpn            = null;
-        mEthernetGroup  = null;
-        mEthernet       = null;
-        mWifiGroup      = null;
-        mWifi           = null;
-        mAirplane       = null;
         mMobileSignalGroup.removeAllViews();
-        mMobileSignalGroup = null;
         TunerService.get(mContext).removeTunable(this);
 
         super.onDetachedFromWindow();
@@ -383,8 +403,8 @@
 
         if (mEthernetVisible) {
             if (mLastEthernetIconId != mEthernetIconId) {
-                mEthernet.setImageResource(mEthernetIconId);
-                mEthernetDark.setImageResource(mEthernetIconId);
+                setIconForView(mEthernet, mEthernetIconId);
+                setIconForView(mEthernetDark, mEthernetIconId);
                 mLastEthernetIconId = mEthernetIconId;
             }
             mEthernetGroup.setContentDescription(mEthernetDescription);
@@ -397,11 +417,10 @@
                 String.format("ethernet: %s",
                     (mEthernetVisible ? "VISIBLE" : "GONE")));
 
-
         if (mWifiVisible) {
             if (mWifiStrengthId != mLastWifiStrengthId) {
-                mWifi.setImageResource(mWifiStrengthId);
-                mWifiDark.setImageResource(mWifiStrengthId);
+                setIconForView(mWifi, mWifiStrengthId);
+                setIconForView(mWifiDark, mWifiStrengthId);
                 mLastWifiStrengthId = mWifiStrengthId;
             }
             mWifiGroup.setContentDescription(mWifiDescription);
@@ -428,7 +447,7 @@
 
         if (mIsAirplaneMode) {
             if (mLastAirplaneIconId != mAirplaneIconId) {
-                mAirplane.setImageResource(mAirplaneIconId);
+                setIconForView(mAirplane, mAirplaneIconId);
                 mLastAirplaneIconId = mAirplaneIconId;
             }
             mAirplane.setContentDescription(mAirplaneContentDescription);
@@ -456,6 +475,21 @@
         setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
     }
 
+    /**
+     * Sets the given drawable id on the view. This method will also scale the icon by
+     * {@link #mIconScaleFactor} if appropriate.
+     */
+    private void setIconForView(ImageView imageView, @DrawableRes int iconId) {
+        // Using the imageView's context to retrieve the Drawable so that theme is preserved.
+        Drawable icon = imageView.getContext().getDrawable(iconId);
+
+        if (mIconScaleFactor == 1.f) {
+            imageView.setImageDrawable(icon);
+        } else {
+            imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor));
+        }
+    }
+
     public void setIconTint(int tint, float darkIntensity) {
         boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity;
         mIconTint = tint;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index de7a8db..5a7cf86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -24,10 +24,13 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.graphics.drawable.ScaleDrawable;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
 import android.view.ViewDebug;
 import android.view.accessibility.AccessibilityEvent;
 import com.android.internal.statusbar.StatusBarIcon;
@@ -189,12 +192,24 @@
      * @return Drawable for this item, or null if the package or item could not
      *         be found
      */
-    public static Drawable getIcon(Context context, StatusBarIcon icon) {
-        int userId = icon.user.getIdentifier();
+    public static Drawable getIcon(Context context, StatusBarIcon statusBarIcon) {
+        int userId = statusBarIcon.user.getIdentifier();
         if (userId == UserHandle.USER_ALL) {
             userId = UserHandle.USER_SYSTEM;
         }
-        return icon.icon.loadDrawableAsUser(context, userId);
+
+        Drawable icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
+
+        TypedValue typedValue = new TypedValue();
+        context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
+        float scaleFactor = typedValue.getFloat();
+
+        // No need to scale the icon, so return it as is.
+        if (scaleFactor == 1.f) {
+            return icon;
+        }
+
+        return new ScalingDrawableWrapper(icon, scaleFactor);
     }
 
     public StatusBarIcon getStatusBarIcon() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
new file mode 100644
index 0000000..38b6497
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import com.android.systemui.statusbar.notification.TransformState;
+
+/**
+ * A view that can be transformed to and from.
+ */
+public interface TransformableView {
+    int TRANSFORMING_VIEW_HEADER = 0;
+    int TRANSFORMING_VIEW_TITLE = 1;
+    int TRANSFORMING_VIEW_TEXT = 2;
+    int TRANSFORMING_VIEW_IMAGE = 3;
+    int TRANSFORMING_VIEW_PROGRESS = 4;
+
+    /**
+     * Get the current state of a view in a transform animation
+     * @param fadingView which view we are interested in
+     * @return the current transform state of this viewtype
+     */
+    TransformState getCurrentState(int fadingView);
+
+    /**
+     * Transform to the given view
+     * @param notification the view to transform to
+     */
+    void transformTo(TransformableView notification, Runnable endRunnable);
+
+    /**
+     * Transform to this view from the given view
+     * @param notification the view to transform from
+     */
+    void transformFrom(TransformableView notification);
+
+    /**
+     * Set this view to be fully visible or gone
+     * @param visible
+     */
+    void setVisible(boolean visible);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
new file mode 100644
index 0000000..63ff5aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -0,0 +1,194 @@
+/*
+ * 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;
+
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.TransformState;
+
+import java.util.Stack;
+
+/**
+ * A view that can be transformed to and from.
+ */
+public class ViewTransformationHelper implements TransformableView {
+
+    private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
+
+    private final Handler mHandler = new Handler();
+    private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
+    private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
+
+    public void addTransformedView(int key, View transformedView) {
+        mTransformedViews.put(key, transformedView);
+    }
+
+    public void reset() {
+        mTransformedViews.clear();
+    }
+
+    public void setCustomTransformation(CustomTransformation transformation, int viewType) {
+        mCustomTransformations.put(viewType, transformation);
+    }
+
+    @Override
+    public TransformState getCurrentState(int fadingView) {
+        View view = mTransformedViews.get(fadingView);
+        if (view != null && view.getVisibility() != View.GONE) {
+            return TransformState.createFrom(view);
+        }
+        return null;
+    }
+
+    @Override
+    public void transformTo(TransformableView notification, Runnable endRunnable) {
+        Runnable runnable = endRunnable;
+        for (Integer viewType : mTransformedViews.keySet()) {
+            TransformState ownState = getCurrentState(viewType);
+            if (ownState != null) {
+                CustomTransformation customTransformation = mCustomTransformations.get(viewType);
+                if (customTransformation != null && customTransformation.transformTo(
+                        ownState, notification, runnable)) {
+                    ownState.recycle();
+                    runnable = null;
+                    continue;
+                }
+                TransformState otherState = notification.getCurrentState(viewType);
+                if (otherState != null) {
+                    boolean run = ownState.transformViewTo(otherState, runnable);
+                    otherState.recycle();
+                    if (run) {
+                        runnable = null;
+                    }
+                } else {
+                    // there's no other view available
+                    CrossFadeHelper.fadeOut(mTransformedViews.get(viewType), runnable);
+                    runnable = null;
+                }
+                ownState.recycle();
+            }
+        }
+        if (runnable != null) {
+            // We need to post, since the visible type is only set after the transformation is
+            // started
+            mHandler.post(runnable);
+        }
+    }
+
+    @Override
+    public void transformFrom(TransformableView notification) {
+        for (Integer viewType : mTransformedViews.keySet()) {
+            TransformState ownState = getCurrentState(viewType);
+            if (ownState != null) {
+                CustomTransformation customTransformation = mCustomTransformations.get(viewType);
+                if (customTransformation != null && customTransformation.transformFrom(
+                        ownState, notification)) {
+                    ownState.recycle();
+                    continue;
+                }
+                TransformState otherState = notification.getCurrentState(viewType);
+                if (otherState != null) {
+                    ownState.transformViewFrom(otherState);
+                    otherState.recycle();
+                } else {
+                    // There's no other view, lets fade us in
+                    // Certain views need to prepare the fade in and make sure its children are
+                    // completely visible. An example is the notification header.
+                    ownState.prepareFadeIn();
+                    CrossFadeHelper.fadeIn(mTransformedViews.get(viewType));
+                }
+                ownState.recycle();
+            }
+        }
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        for (Integer viewType : mTransformedViews.keySet()) {
+            TransformState ownState = getCurrentState(viewType);
+            if (ownState != null) {
+                ownState.setVisible(visible);
+                ownState.recycle();
+            }
+        }
+    }
+
+    /**
+     * Add the remaining transformation views such that all views are being transformed correctly
+     * @param viewRoot the root below which all elements need to be transformed
+     */
+    public void addRemainingTransformTypes(View viewRoot) {
+        // lets now tag the right views
+        int numValues = mTransformedViews.size();
+        for (int i = 0; i < numValues; i++) {
+            View view = mTransformedViews.valueAt(i);
+            while (view != viewRoot.getParent()) {
+                view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
+                view = (View) view.getParent();
+            }
+        }
+        Stack<View> stack = new Stack<>();
+        // Add the right views now
+        stack.push(viewRoot);
+        while (!stack.isEmpty()) {
+            View child = stack.pop();
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+            Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
+            if (containsView == null) {
+                // This one is unhandled, let's add it to our list.
+                int id = child.getId();
+                if (id != View.NO_ID) {
+                    // We only fade views with an id
+                    addTransformedView(id, child);
+                    continue;
+                }
+            }
+            child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null);
+            if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){
+                ViewGroup group = (ViewGroup) child;
+                for (int i = 0; i < group.getChildCount(); i++) {
+                    stack.push(group.getChildAt(i));
+                }
+            }
+        }
+    }
+
+    public interface CustomTransformation {
+        /**
+         * Transform a state to the given view
+         * @param ownState the state to transform
+         * @param notification the view to transform to
+         * @return whether a custom transformation is performed
+         */
+        boolean transformTo(TransformState ownState, TransformableView notification,
+                Runnable endRunnable);
+
+        /**
+         * Transform to this state from the given view
+         * @param ownState the state to transform to
+         * @param notification the view to transform from
+         * @return whether a custom transformation is performed
+         */
+        boolean transformFrom(TransformState ownState, TransformableView notification);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
new file mode 100644
index 0000000..bf291d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
@@ -0,0 +1,149 @@
+/*
+ * 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.util.Pools;
+import android.view.NotificationHeaderView;
+import android.view.View;
+
+import com.android.systemui.statusbar.CrossFadeHelper;
+
+/**
+ * A transform state of a text view.
+*/
+public class HeaderTransformState extends TransformState {
+
+    private static Pools.SimplePool<HeaderTransformState> sInstancePool
+            = new Pools.SimplePool<>(40);
+    private View mExpandButton;
+
+    @Override
+    public void initFrom(View view) {
+        super.initFrom(view);
+        if (view instanceof NotificationHeaderView) {
+            NotificationHeaderView header = (NotificationHeaderView) view;
+            mExpandButton = header.getExpandButton();
+        }
+    }
+
+    @Override
+    public boolean transformViewTo(TransformState otherState, Runnable endRunnable) {
+        // if the transforming notification has a header, we have ensured that it looks the same
+        // but the expand button, so lets fade just that one.
+        if (!(mTransformedView instanceof NotificationHeaderView)) {
+            return false;
+        }
+        NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+        int childCount = header.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View headerChild = header.getChildAt(i);
+            if (headerChild.getVisibility() == View.GONE) {
+                continue;
+            }
+            if (headerChild != mExpandButton) {
+                headerChild.setVisibility(View.INVISIBLE);
+            } else {
+                CrossFadeHelper.fadeOut(mExpandButton, endRunnable);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void transformViewFrom(TransformState otherState) {
+        // if the transforming notification has a header, we have ensured that it looks the same
+        // but the expand button, so lets fade just that one.
+        if (!(mTransformedView instanceof NotificationHeaderView)) {
+            return;
+        }
+        NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+        header.setVisibility(View.VISIBLE);
+        header.setAlpha(1.0f);
+        int childCount = header.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View headerChild = header.getChildAt(i);
+            if (headerChild.getVisibility() == View.GONE) {
+                continue;
+            }
+            if (headerChild != mExpandButton) {
+                headerChild.setVisibility(View.VISIBLE);
+            } else {
+                CrossFadeHelper.fadeIn(mExpandButton);
+            }
+        }
+        return;
+    }
+
+    public static HeaderTransformState obtain() {
+        HeaderTransformState instance = sInstancePool.acquire();
+        if (instance != null) {
+            return instance;
+        }
+        return new HeaderTransformState();
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        sInstancePool.release(this);
+    }
+
+    @Override
+    protected void reset() {
+        super.reset();
+        mExpandButton = null;
+    }
+
+    public void setVisible(boolean visible) {
+        super.setVisible(visible);
+        if (!(mTransformedView instanceof NotificationHeaderView)) {
+            return;
+        }
+        NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+        int childCount = header.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View headerChild = header.getChildAt(i);
+            if (headerChild.getVisibility() == View.GONE) {
+                continue;
+            }
+            headerChild.animate().cancel();
+            headerChild.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+            if (headerChild == mExpandButton) {
+                headerChild.setAlpha(visible ? 1.0f : 0.0f);
+            }
+        }
+    }
+
+    @Override
+    public void prepareFadeIn() {
+        super.prepareFadeIn();
+        if (!(mTransformedView instanceof NotificationHeaderView)) {
+            return;
+        }
+        NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+        int childCount = header.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View headerChild = header.getChildAt(i);
+            if (headerChild.getVisibility() == View.GONE) {
+                continue;
+            }
+            headerChild.animate().cancel();
+            headerChild.setVisibility(View.VISIBLE);
+            headerChild.setAlpha(1.0f);
+        }
+    }
+}
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 5fb6fec..5eed5ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
@@ -18,18 +18,29 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.TextView;
 
 import com.android.keyguard.AlphaOptimizedLinearLayout;
 import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 
+import java.util.ArrayList;
+
 /**
  * A hybrid view which may contain information about one ore more notifications.
  */
-public class HybridNotificationView extends AlphaOptimizedLinearLayout {
+public class HybridNotificationView extends AlphaOptimizedLinearLayout
+        implements TransformableView {
+
+    private ViewTransformationHelper mTransformationHelper;
 
     protected TextView mTitleView;
     protected TextView mTextView;
@@ -58,6 +69,39 @@
         mTitleView = (TextView) findViewById(R.id.notification_title);
         mTextView = (TextView) findViewById(R.id.notification_text);
         mInvertHelper = new ViewInvertHelper(this, NotificationPanelView.DOZE_ANIMATION_DURATION);
+        mTransformationHelper = new ViewTransformationHelper();
+        mTransformationHelper.setCustomTransformation(
+                new ViewTransformationHelper.CustomTransformation() {
+                    @Override
+                    public boolean transformTo(TransformState ownState, TransformableView notification,
+                            Runnable endRunnable) {
+                        // We want to transform to the same y location as the title
+                        TransformState otherState = notification.getCurrentState(
+                                TRANSFORMING_VIEW_TITLE);
+                        CrossFadeHelper.fadeOut(mTextView, endRunnable);
+                        if (otherState != null) {
+                            ownState.animateViewVerticalTo(otherState, endRunnable);
+                            otherState.recycle();
+                        }
+                        return true;
+                    }
+
+                    @Override
+                    public boolean transformFrom(TransformState ownState,
+                            TransformableView notification) {
+                        // We want to transform from the same y location as the title
+                        TransformState otherState = notification.getCurrentState(
+                                TRANSFORMING_VIEW_TITLE);
+                        CrossFadeHelper.fadeIn(mTextView);
+                        if (otherState != null) {
+                            ownState.animateViewVerticalFrom(otherState);
+                            otherState.recycle();
+                        }
+                        return true;
+                    }
+                }, TRANSFORMING_VIEW_TEXT);
+        mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TITLE, mTitleView);
+        mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TEXT, mTextView);
     }
 
     public void bind(CharSequence title) {
@@ -66,11 +110,38 @@
 
     public void bind(CharSequence title, CharSequence text) {
         mTitleView.setText(title);
+        if (TextUtils.isEmpty(title)) {
+            mTitleView.setVisibility(GONE);
+        }
         mTextView.setText(text);
+        if (TextUtils.isEmpty(text)) {
+            mTextView.setVisibility(GONE);
+        }
         requestLayout();
     }
 
     public void setDark(boolean dark, boolean fade, long delay) {
         mInvertHelper.setInverted(dark, fade, delay);
     }
+
+    @Override
+    public TransformState getCurrentState(int fadingView) {
+        return mTransformationHelper.getCurrentState(fadingView);
+    }
+
+    @Override
+    public void transformTo(TransformableView notification, Runnable endRunnable) {
+        mTransformationHelper.transformTo(notification, endRunnable);
+    }
+
+    @Override
+    public void transformFrom(TransformableView notification) {
+        mTransformationHelper.transformFrom(notification);
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        mTransformationHelper.setVisible(visible);
+    }
 }
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 b8adf5b..285d53f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
@@ -52,16 +52,10 @@
 
     public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
             Notification notification) {
-        CharSequence titleText = resolveTitle(notification);
-        if (titleText == null) {
-            if (reusableView != null) {
-                mParent.removeView(reusableView);
-            }
-            return null;
-        }
         if (reusableView == null) {
             reusableView = inflateHybridView();
         }
+        CharSequence titleText = resolveTitle(notification);
         CharSequence contentText = resolveText(notification);
         reusableView.bind(titleText, contentText);
         return reusableView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
new file mode 100644
index 0000000..e891a97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.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 com.android.systemui.statusbar.notification;
+
+import android.graphics.drawable.Icon;
+import android.util.Pools;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+/**
+ * A transform state of a image view.
+*/
+public class ImageTransformState extends TransformState {
+
+    public static final int ICON_TAG = R.id.image_icon_tag;
+    private static Pools.SimplePool<ImageTransformState> sInstancePool
+            = new Pools.SimplePool<>(40);
+    private Icon mIcon;
+
+    @Override
+    public void initFrom(View view) {
+        super.initFrom(view);
+        if (view instanceof ImageView) {
+            mIcon = (Icon) view.getTag(ICON_TAG);
+        }
+    }
+
+    @Override
+    protected boolean sameAs(TransformState otherState) {
+        if (otherState instanceof ImageTransformState) {
+            return mIcon != null && mIcon.sameAs(((ImageTransformState) otherState).getIcon());
+        }
+        return super.sameAs(otherState);
+    }
+
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    public static ImageTransformState obtain() {
+        ImageTransformState instance = sInstancePool.acquire();
+        if (instance != null) {
+            return instance;
+        }
+        return new ImageTransformState();
+    }
+
+    @Override
+    protected boolean animateScale() {
+        return true;
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        sInstancePool.release(this);
+    }
+
+    @Override
+    protected void reset() {
+        super.reset();
+        mIcon = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
new file mode 100644
index 0000000..ce9540b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
@@ -0,0 +1,49 @@
+/*
+ * 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.app.Notification;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+/**
+ * Wraps a notification containing a big picture template
+ */
+public class NotificationBigPictureTemplateViewWrapper extends NotificationTemplateViewWrapper {
+
+    protected NotificationBigPictureTemplateViewWrapper(Context ctx, View view) {
+        super(ctx, view);
+    }
+
+    @Override
+    public void notifyContentUpdated(StatusBarNotification notification) {
+        super.notifyContentUpdated(notification);
+        updateImageTag(notification);
+    }
+
+    private void updateImageTag(StatusBarNotification notification) {
+        final Bundle extras = notification.getNotification().extras;
+        Icon overRiddenIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG);
+        if (overRiddenIcon != null) {
+            mPicture.setTag(ImageTransformState.ICON_TAG, overRiddenIcon);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index 6fd341b..fd65aac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification;
 
 import android.view.View;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index ddad2e0..4fd4cab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -27,17 +27,23 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
 import android.view.NotificationHeaderView;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.ImageView;
 
 import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 
-import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Stack;
 
 /**
  * Wraps a notification header view.
@@ -52,6 +58,8 @@
     protected final Interpolator mLinearOutSlowInInterpolator;
     protected final ViewInvertHelper mInvertHelper;
 
+    protected final ViewTransformationHelper mTransformationHelper;
+
     protected int mColor;
     private ImageView mIcon;
 
@@ -64,7 +72,9 @@
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
                 android.R.interpolator.linear_out_slow_in);
         mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
+        mTransformationHelper = new ViewTransformationHelper();
         resolveHeaderViews();
+        updateInvertHelper();
     }
 
     protected void resolveHeaderViews() {
@@ -73,12 +83,6 @@
         mColor = resolveColor(mExpandButton);
         mNotificationHeader = (NotificationHeaderView) mView.findViewById(
                 com.android.internal.R.id.notification_header);
-        for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
-            View child = mNotificationHeader.getChildAt(i);
-            if (child != mIcon) {
-                mInvertHelper.addTarget(child);
-            }
-        }
     }
 
     private int resolveColor(ImageView icon) {
@@ -92,10 +96,58 @@
     }
 
     @Override
-    public void notifyContentUpdated() {
-        mInvertHelper.clearTargets();
+    public void notifyContentUpdated(StatusBarNotification notification) {
         // Reinspect the notification.
         resolveHeaderViews();
+        updateInvertHelper();
+        updateTransformedTypes();
+        addRemainingTransformTypes();
+        updateCropToPaddingForImageViews();
+    }
+
+    /**
+     * Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
+     * child is faded automatically and doesn't have to be manually added.
+     * The keys used for the views are the ids.
+     */
+    private void addRemainingTransformTypes() {
+        mTransformationHelper.addRemainingTransformTypes(mView);
+    }
+
+    /**
+     * Since we are deactivating the clipping when transforming the ImageViews don't get clipped
+     * anymore during these transitions. We can avoid that by using
+     * {@link ImageView#setCropToPadding(boolean)} on all ImageViews.
+     */
+    private void updateCropToPaddingForImageViews() {
+        Stack<View> stack = new Stack<>();
+        stack.push(mView);
+        while (!stack.isEmpty()) {
+            View child = stack.pop();
+            if (child instanceof ImageView) {
+                ((ImageView) child).setCropToPadding(true);
+            } else if (child instanceof ViewGroup){
+                ViewGroup group = (ViewGroup) child;
+                for (int i = 0; i < group.getChildCount(); i++) {
+                    stack.push(group.getChildAt(i));
+                }
+            }
+        }
+    }
+
+    protected void updateInvertHelper() {
+        mInvertHelper.clearTargets();
+        for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+            View child = mNotificationHeader.getChildAt(i);
+            if (child != mIcon) {
+                mInvertHelper.addTarget(child);
+            }
+        }
+    }
+
+    protected void updateTransformedTypes() {
+        mTransformationHelper.reset();
+        mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER, mNotificationHeader);
     }
 
     @Override
@@ -236,4 +288,25 @@
     public NotificationHeaderView getNotificationHeader() {
         return mNotificationHeader;
     }
+
+    @Override
+    public TransformState getCurrentState(int fadingView) {
+        return mTransformationHelper.getCurrentState(fadingView);
+    }
+
+    @Override
+    public void transformTo(TransformableView notification, Runnable endRunnable) {
+        mTransformationHelper.transformTo(notification, endRunnable);
+    }
+
+    @Override
+    public void transformFrom(TransformableView notification) {
+        mTransformationHelper.transformFrom(notification);
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        super.setVisible(visible);
+        mTransformationHelper.setVisible(visible);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
new file mode 100644
index 0000000..a959e07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Color;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+/**
+ * Wraps a notification view inflated from a template.
+ */
+public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper {
+
+    private static final int mDarkProgressTint = 0xffffffff;
+
+    protected ImageView mPicture;
+    private ProgressBar mProgressBar;
+    private TextView mTitle;
+    private TextView mText;
+
+    protected NotificationTemplateViewWrapper(Context ctx, View view) {
+        super(ctx, view);
+        mTransformationHelper.setCustomTransformation(
+                new ViewTransformationHelper.CustomTransformation() {
+                    @Override
+                    public boolean transformTo(TransformState ownState,
+                            TransformableView notification, final Runnable endRunnable) {
+                        if (!(notification instanceof HybridNotificationView)) {
+                            return false;
+                        }
+                        TransformState otherState = notification.getCurrentState(
+                                TRANSFORMING_VIEW_TITLE);
+                        CrossFadeHelper.fadeOut(mText, endRunnable);
+                        if (otherState != null) {
+                            int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
+                            int[] ownPosition = ownState.getLaidOutLocationOnScreen();
+                            mText.animate()
+                                    .translationY((otherStablePosition[1]
+                                            + otherState.getTransformedView().getHeight()
+                                            - ownPosition[1]) * 0.33f)
+                                    .setDuration(
+                                            StackStateAnimator.ANIMATION_DURATION_STANDARD)
+                                    .setInterpolator(TransformState.FAST_OUT_SLOW_IN)
+                                    .withEndAction(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            if (endRunnable != null) {
+                                                endRunnable.run();
+                                            }
+                                            TransformState.setClippingDeactivated(mText,
+                                                    false);
+                                        }
+                                    });
+                            TransformState.setClippingDeactivated(mText, true);
+                            otherState.recycle();
+                        }
+                        return true;
+                    }
+
+                    @Override
+                    public boolean transformFrom(TransformState ownState,
+                            TransformableView notification) {
+                        if (!(notification instanceof HybridNotificationView)) {
+                            return false;
+                        }
+                        TransformState otherState = notification.getCurrentState(
+                                TRANSFORMING_VIEW_TITLE);
+                        boolean isVisible = mText.getVisibility() == View.VISIBLE;
+                        CrossFadeHelper.fadeIn(mText);
+                        if (otherState != null) {
+                            int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
+                            int[] ownStablePosition = ownState.getLaidOutLocationOnScreen();
+                            if (!isVisible) {
+                                mText.setTranslationY((otherStablePosition[1]
+                                        + otherState.getTransformedView().getHeight()
+                                        - ownStablePosition[1]) * 0.33f);
+                            }
+                            mText.animate()
+                                    .translationY(0)
+                                    .setDuration(
+                                            StackStateAnimator.ANIMATION_DURATION_STANDARD)
+                                    .setInterpolator(TransformState.FAST_OUT_SLOW_IN)
+                                    .withEndAction(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            TransformState.setClippingDeactivated(mText,
+                                                    false);
+                                        }
+                                    });
+                            TransformState.setClippingDeactivated(mText, true);
+                            otherState.recycle();
+                        }
+                        return true;
+                    }
+                }, TRANSFORMING_VIEW_TEXT);
+    }
+
+    private void resolveTemplateViews(StatusBarNotification notification) {
+        mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
+        mPicture.setTag(ImageTransformState.ICON_TAG,
+                notification.getNotification().getLargeIcon());
+        mTitle = (TextView) mView.findViewById(com.android.internal.R.id.title);
+        mText = (TextView) mView.findViewById(com.android.internal.R.id.text);
+        final View progress = mView.findViewById(com.android.internal.R.id.progress);
+        if (progress instanceof ProgressBar) {
+            mProgressBar = (ProgressBar) progress;
+        } else {
+            // It's still a viewstub
+            mProgressBar = null;
+        }
+    }
+
+    @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.
+        resolveTemplateViews(notification);
+        super.notifyContentUpdated(notification);
+    }
+
+    @Override
+    protected void updateInvertHelper() {
+        super.updateInvertHelper();
+        View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
+        if (mainColumn != null) {
+            mInvertHelper.addTarget(mainColumn);
+        }
+    }
+
+    @Override
+    protected void updateTransformedTypes() {
+        // This also clears the existing types
+        super.updateTransformedTypes();
+        if (mTitle != null) {
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mTitle);
+        }
+        if (mText != null) {
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText);
+        }
+        if (mPicture != null) {
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, mPicture);
+        }
+        if (mProgressBar != null) {
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar);
+        }
+    }
+
+    @Override
+    public void setDark(boolean dark, boolean fade, long delay) {
+        super.setDark(dark, fade, delay);
+        setPictureGrayscale(dark, fade, delay);
+        setProgressBarDark(dark, fade, delay);
+    }
+
+    private void setProgressBarDark(boolean dark, boolean fade, long delay) {
+        if (mProgressBar != null) {
+            if (fade) {
+                fadeProgressDark(mProgressBar, dark, delay);
+            } else {
+                updateProgressDark(mProgressBar, dark);
+            }
+        }
+    }
+
+    private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = (float) animation.getAnimatedValue();
+                updateProgressDark(target, t);
+            }
+        }, dark, delay, null /* listener */);
+    }
+
+    private void updateProgressDark(ProgressBar target, float intensity) {
+        int color = interpolateColor(mColor, mDarkProgressTint, intensity);
+        target.getIndeterminateDrawable().mutate().setTint(color);
+        target.getProgressDrawable().mutate().setTint(color);
+    }
+
+    private void updateProgressDark(ProgressBar target, boolean dark) {
+        updateProgressDark(target, dark ? 1f : 0f);
+    }
+
+    protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
+        if (mPicture != null) {
+            if (fade) {
+                fadeGrayscale(mPicture, grayscale, delay);
+            } else {
+                updateGrayscale(mPicture, grayscale);
+            }
+        }
+    }
+
+    private static int interpolateColor(int source, int target, float t) {
+        int aSource = Color.alpha(source);
+        int rSource = Color.red(source);
+        int gSource = Color.green(source);
+        int bSource = Color.blue(source);
+        int aTarget = Color.alpha(target);
+        int rTarget = Color.red(target);
+        int gTarget = Color.green(target);
+        int bTarget = Color.blue(target);
+        return Color.argb(
+                (int) (aSource * (1f - t) + aTarget * t),
+                (int) (rSource * (1f - t) + rTarget * t),
+                (int) (gSource * (1f - t) + gTarget * t),
+                (int) (bSource * (1f - t) + bTarget * t));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 61499de..0ceba78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -14,22 +14,29 @@
  * limitations under the License
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification;
 
 import android.content.Context;
+import android.service.notification.StatusBarNotification;
 import android.view.NotificationHeaderView;
 import android.view.View;
 
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+
 /**
  * Wraps the actual notification content view; used to implement behaviors which are different for
  * the individual templates and custom views.
  */
-public abstract class NotificationViewWrapper {
+public abstract class NotificationViewWrapper implements TransformableView {
 
     protected final View mView;
 
     public static NotificationViewWrapper wrap(Context ctx, View v) {
         if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
+            if ("bigPicture".equals(v.getTag())) {
+                return new NotificationBigPictureTemplateViewWrapper(ctx, v);
+            }
             return new NotificationTemplateViewWrapper(ctx, v);
         } else if (v instanceof NotificationHeaderView) {
             return new NotificationHeaderViewWrapper(ctx, v);
@@ -53,8 +60,9 @@
 
     /**
      * Notifies this wrapper that the content of the view might have changed.
+     * @param notification
      */
-    public void notifyContentUpdated() {};
+    public void notifyContentUpdated(StatusBarNotification notification) {};
 
     /**
      * @return true if this template might need to be clipped with a round rect to make it look
@@ -78,4 +86,26 @@
     public NotificationHeaderView getNotificationHeader() {
         return null;
     }
+
+    @Override
+    public TransformState getCurrentState(int fadingView) {
+        return null;
+    }
+
+    @Override
+    public void transformTo(TransformableView notification, Runnable endRunnable) {
+        // By default we are fading out completely
+        CrossFadeHelper.fadeOut(mView, endRunnable);
+    }
+
+    @Override
+    public void transformFrom(TransformableView notification) {
+        // By default we are fading in completely
+        CrossFadeHelper.fadeIn(mView);
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ProgressTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ProgressTransformState.java
new file mode 100644
index 0000000..bf78194
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ProgressTransformState.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util.Pools;
+
+/**
+ * A transform state of a progress view.
+*/
+public class ProgressTransformState extends TransformState {
+
+    private static Pools.SimplePool<ProgressTransformState> sInstancePool
+            = new Pools.SimplePool<>(40);
+
+    @Override
+    protected boolean sameAs(TransformState otherState) {
+        if (otherState instanceof ProgressTransformState) {
+            return true;
+        }
+        return super.sameAs(otherState);
+    }
+
+    public static ProgressTransformState obtain() {
+        ProgressTransformState instance = sInstancePool.acquire();
+        if (instance != null) {
+            return instance;
+        }
+        return new ProgressTransformState();
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        sInstancePool.release(this);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
new file mode 100644
index 0000000..5ab441d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -0,0 +1,70 @@
+/*
+ * 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.text.TextUtils;
+import android.util.Pools;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * A transform state of a mText view.
+*/
+public class TextViewTransformState extends TransformState {
+
+    private static Pools.SimplePool<TextViewTransformState> sInstancePool
+            = new Pools.SimplePool<>(40);
+    private CharSequence mText;
+
+    @Override
+    public void initFrom(View view) {
+        super.initFrom(view);
+        if (view instanceof TextView) {
+            TextView txt = (TextView) view;
+            mText = txt.getText();
+        }
+    }
+
+    @Override
+    protected boolean sameAs(TransformState otherState) {
+        if (otherState instanceof TextViewTransformState) {
+            TextViewTransformState otherTvs = (TextViewTransformState) otherState;
+            return TextUtils.equals(otherTvs.mText, mText);
+        }
+        return super.sameAs(otherState);
+    }
+
+    public static TextViewTransformState obtain() {
+        TextViewTransformState instance = sInstancePool.acquire();
+        if (instance != null) {
+            return instance;
+        }
+        return new TextViewTransformState();
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        sInstancePool.release(this);
+    }
+
+    @Override
+    protected void reset() {
+        super.reset();
+        mText = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
new file mode 100644
index 0000000..388ba0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -0,0 +1,316 @@
+/*
+ * 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.util.ArraySet;
+import android.util.Pools;
+import android.view.NotificationHeaderView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+/**
+ * A transform state of a view.
+*/
+public class TransformState {
+
+    private static final int ANIMATE_X = 0x1;
+    private static final int ANIMATE_Y = 0x10;
+    private static final int ANIMATE_ALL = ANIMATE_X | ANIMATE_Y;
+    private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
+    private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
+    private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+    public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
+
+    protected View mTransformedView;
+    private int[] mOwnPosition = new int[2];
+
+    public void initFrom(View view) {
+        mTransformedView = view;
+    }
+
+    /**
+     * Transforms the {@link #mTransformedView} from the given transformviewstate
+     * @param otherState the state to transform from
+     */
+    public void transformViewFrom(TransformState otherState) {
+        mTransformedView.animate().cancel();
+        if (sameAs(otherState)) {
+            // We have the same content, lets show ourselves
+            mTransformedView.setAlpha(1.0f);
+            mTransformedView.setVisibility(View.VISIBLE);
+        } else {
+            CrossFadeHelper.fadeIn(mTransformedView);
+        }
+        animateViewFrom(otherState);
+    }
+
+    public void animateViewFrom(TransformState otherState) {
+        animateViewFrom(otherState, ANIMATE_ALL);
+    }
+
+    public void animateViewVerticalFrom(TransformState otherState) {
+        animateViewFrom(otherState, ANIMATE_Y);
+    }
+
+    private void animateViewFrom(TransformState otherState, int animationFlags) {
+        final View transformedView = mTransformedView;
+        // lets animate the positions correctly
+        int[] otherPosition = otherState.getLocationOnScreen();
+        int[] ownStablePosition = getLaidOutLocationOnScreen();
+        if ((animationFlags & ANIMATE_X) != 0) {
+            transformedView.setTranslationX(otherPosition[0] - ownStablePosition[0]);
+            transformedView.animate().translationX(0);
+        }
+        if ((animationFlags & ANIMATE_Y) != 0) {
+            transformedView.setTranslationY(otherPosition[1] - ownStablePosition[1]);
+            transformedView.animate().translationY(0);
+        }
+        if (animateScale()) {
+            // we also want to animate the scale if we're the same
+            View otherView = otherState.getTransformedView();
+            if (otherView.getWidth() != transformedView.getWidth()) {
+                float scaleX = (otherView.getWidth() * otherView.getScaleX()
+                        / (float) transformedView.getWidth());
+                transformedView.setScaleX(scaleX);
+                transformedView.setPivotX(0);
+                transformedView.animate().scaleX(1.0f);
+            }
+            if (otherView.getHeight() != transformedView.getHeight()) {
+                float scaleY = (otherView.getHeight() * otherView.getScaleY()
+                        / (float) transformedView.getHeight());
+                transformedView.setScaleY(scaleY);
+                transformedView.setPivotY(0);
+                transformedView.animate().scaleY(1.0f);
+            }
+        }
+        transformedView.animate()
+                .setInterpolator(TransformState.FAST_OUT_SLOW_IN)
+                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        setClippingDeactivated(transformedView, false);
+                    }
+                });
+        setClippingDeactivated(transformedView, true);
+    }
+
+    protected boolean animateScale() {
+        return false;
+    }
+
+    /**
+     * Transforms the {@link #mTransformedView} to the given transformviewstate
+     * @param otherState the state to transform from
+     * @param endRunnable a runnable to run at the end of the animation
+     * @return whether an animation was started
+     */
+    public boolean transformViewTo(TransformState otherState, final Runnable endRunnable) {
+        mTransformedView.animate().cancel();
+        if (sameAs(otherState)) {
+            // We have the same text, lets show ourselfs
+            mTransformedView.setAlpha(0.0f);
+            mTransformedView.setVisibility(View.INVISIBLE);
+            return false;
+        } else {
+            CrossFadeHelper.fadeOut(mTransformedView, endRunnable);
+        }
+        animateViewTo(otherState, endRunnable);
+        return true;
+    }
+
+    public void animateViewTo(TransformState otherState, Runnable endRunnable) {
+        animateViewTo(otherState, endRunnable, ANIMATE_ALL);
+    }
+
+    public void animateViewVerticalTo(TransformState otherState, Runnable endRunnable) {
+        animateViewTo(otherState, endRunnable, ANIMATE_Y);
+    }
+
+    private void animateViewTo(TransformState otherState, final Runnable endRunnable,
+            int animationFlags) {
+        // lets animate the positions correctly
+        int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
+        int[] ownPosition = getLaidOutLocationOnScreen();
+        final View transformedView = mTransformedView;
+        if ((animationFlags & ANIMATE_X) != 0) {
+            transformedView.animate()
+                    .translationX(otherStablePosition[0] - ownPosition[0]);
+        }
+        if ((animationFlags & ANIMATE_Y) != 0) {
+            transformedView.animate()
+                    .translationY(otherStablePosition[1] - ownPosition[1]);
+        }
+        transformedView.animate()
+                .setInterpolator(TransformState.FAST_OUT_SLOW_IN)
+                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (endRunnable != null) {
+                            endRunnable.run();
+                        }
+                        setClippingDeactivated(transformedView, false);
+                    }
+                });
+        setClippingDeactivated(transformedView, true);
+    }
+
+    public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
+        ViewGroup view = (ViewGroup) transformedView.getParent();
+        while (true) {
+            ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
+            if (clipSet == null) {
+                clipSet = new ArraySet<>();
+                view.setTag(CLIP_CLIPPING_SET, clipSet);
+            }
+            Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
+            if (clipChildren == null) {
+                clipChildren = view.getClipChildren();
+                view.setTag(CLIP_CHILDREN_TAG, clipChildren);
+            }
+            Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
+            if (clipToPadding == null) {
+                clipToPadding = view.getClipToPadding();
+                view.setTag(CLIP_TO_PADDING, clipToPadding);
+            }
+            ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
+                    ? (ExpandableNotificationRow) view
+                    : null;
+            if (!deactivated) {
+                clipSet.remove(transformedView);
+                if (clipSet.isEmpty()) {
+                    view.setClipChildren(clipChildren);
+                    view.setClipToPadding(clipToPadding);
+                    view.setTag(CLIP_CLIPPING_SET, null);
+                    if (row != null) {
+                        row.setClipToActualHeight(true);
+                    }
+                }
+            } else {
+                clipSet.add(transformedView);
+                view.setClipChildren(false);
+                view.setClipToPadding(false);
+                if (row != null && row.isChildInGroup()) {
+                    // We still want to clip to the parent's height
+                    row.setClipToActualHeight(false);
+                }
+            }
+            if (row != null && !row.isChildInGroup()) {
+                return;
+            }
+            final ViewParent parent = view.getParent();
+            if (parent instanceof ViewGroup) {
+                view = (ViewGroup) parent;
+            } else {
+                return;
+            }
+        }
+    }
+
+    public int[] getLaidOutLocationOnScreen() {
+        int[] location = getLocationOnScreen();
+        location[0] -= mTransformedView.getTranslationX();
+        location[1] -= mTransformedView.getTranslationY();
+        return location;
+    }
+
+    public int[] getLocationOnScreen() {
+        mTransformedView.getLocationOnScreen(mOwnPosition);
+        return mOwnPosition;
+    }
+
+    protected boolean sameAs(TransformState otherState) {
+        return false;
+    }
+
+    public static TransformState createFrom(View view) {
+        if (view instanceof TextView) {
+            TextViewTransformState result = TextViewTransformState.obtain();
+            result.initFrom(view);
+            return result;
+        }
+        if (view instanceof NotificationHeaderView) {
+            HeaderTransformState result = HeaderTransformState.obtain();
+            result.initFrom(view);
+            return result;
+        }
+        if (view instanceof ImageView) {
+            ImageTransformState result = ImageTransformState.obtain();
+            result.initFrom(view);
+            return result;
+        }
+        if (view instanceof ProgressBar) {
+            ProgressTransformState result = ProgressTransformState.obtain();
+            result.initFrom(view);
+            return result;
+        }
+        TransformState result = obtain();
+        result.initFrom(view);
+        return result;
+    }
+
+    public void recycle() {
+        reset();
+        if (getClass() == TransformState.class) {
+            sInstancePool.release(this);
+        }
+    }
+
+    protected void reset() {
+        mTransformedView = null;
+    }
+
+    public void setVisible(boolean visible) {
+        mTransformedView.animate().cancel();
+        mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        mTransformedView.setAlpha(visible ? 1.0f : 0.0f);
+        if (visible) {
+            mTransformedView.setTranslationX(0);
+            mTransformedView.setTranslationY(0);
+            mTransformedView.setScaleX(1.0f);
+            mTransformedView.setScaleY(1.0f);
+        }
+    }
+
+    public void prepareFadeIn() {
+    }
+
+    public static TransformState obtain() {
+        TransformState instance = sInstancePool.acquire();
+        if (instance != null) {
+            return instance;
+        }
+        return new TransformState();
+    }
+
+    public View getTransformedView() {
+        return mTransformedView;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
new file mode 100644
index 0000000..8e3886b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -0,0 +1,168 @@
+/*
+ * 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.phone;
+
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+
+/**
+ * Dispatches common view calls to multiple views.  This is used to handle
+ * multiples of the same nav bar icon appearing.
+ */
+public class ButtonDispatcher {
+
+    private final ArrayList<View> mViews = new ArrayList<>();
+
+    private final int mId;
+
+    private View.OnClickListener mClickListener;
+    private View.OnTouchListener mTouchListener;
+    private View.OnLongClickListener mLongClickListener;
+    private Boolean mLongClickable;
+    private Integer mAlpha;
+    private Integer mVisibility = -1;
+    private int mImageResource = -1;
+    private Drawable mImageDrawable;
+    private View mCurrentView;
+
+    public ButtonDispatcher(int id) {
+        mId = id;
+    }
+
+    void clear() {
+        mViews.clear();
+    }
+
+    void addView(View view) {
+        mViews.add(view);
+        view.setOnClickListener(mClickListener);
+        view.setOnTouchListener(mTouchListener);
+        view.setOnLongClickListener(mLongClickListener);
+        if (mLongClickable != null) {
+            view.setLongClickable(mLongClickable);
+        }
+        if (mAlpha != null) {
+            view.setAlpha(mAlpha);
+        }
+        if (mVisibility != null) {
+            view.setVisibility(mVisibility);
+        }
+        if (mImageResource > 0) {
+            ((ImageView) view).setImageResource(mImageResource);
+        } else if (mImageDrawable != null) {
+            ((ImageView) view).setImageDrawable(mImageDrawable);
+        }
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public int getVisibility() {
+        return mVisibility;
+    }
+
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    public void setImageDrawable(Drawable drawable) {
+        mImageDrawable = drawable;
+        mImageResource = -1;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            ((ImageView) mViews.get(i)).setImageDrawable(mImageDrawable);
+        }
+    }
+
+    public void setImageResource(int resource) {
+        mImageResource = resource;
+        mImageDrawable = null;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            ((ImageView) mViews.get(i)).setImageResource(mImageResource);
+        }
+    }
+
+    public void setVisibility(int visibility) {
+        if (mVisibility == visibility) return;
+        mVisibility = visibility;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setVisibility(mVisibility);
+        }
+    }
+
+    public void abortCurrentGesture() {
+        // This seems to be an instantaneous thing, so not going to persist it.
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            ((KeyButtonView) mViews.get(i)).abortCurrentGesture();
+        }
+    }
+
+    public void setAlpha(int alpha) {
+        mAlpha = alpha;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setAlpha(alpha);
+        }
+    }
+
+    public void setOnClickListener(View.OnClickListener clickListener) {
+        mClickListener = clickListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnClickListener(mClickListener);
+        }
+    }
+
+    public void setOnTouchListener(View.OnTouchListener touchListener) {
+        mTouchListener = touchListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnTouchListener(mTouchListener);
+        }
+    }
+
+    public void setLongClickable(boolean isLongClickable) {
+        mLongClickable = isLongClickable;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setLongClickable(mLongClickable);
+        }
+    }
+
+    public void setOnLongClickListener(View.OnLongClickListener longClickListener) {
+        mLongClickListener = longClickListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnLongClickListener(mLongClickListener);
+        }
+    }
+
+    public View getCurrentView() {
+        return mCurrentView;
+    }
+
+    public void setCurrentView(View currentView) {
+        mCurrentView = currentView.findViewById(mId);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index df8c7fa0..26abc48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -110,12 +110,12 @@
                     mInitialTouchX = x;
                     mInitialTouchY = y;
                     int expandedHeight = mPickedChild.getActualHeight();
+                    mHeadsUpManager.unpinAll();
                     mPanel.setPanelScrimMinFraction((float) expandedHeight
                             / mPanel.getMaxPanelHeight());
                     mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight
                             + mNotificationsTopPadding);
                     mPanel.clearNotificattonEffects();
-                    mHeadsUpManager.unpinAll();
                     return true;
                 }
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 2db0804..abe357a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -162,11 +162,18 @@
         mDockWindowTouchSlopExceeded = false;
         mTouchDownX = (int) event.getX();
         mTouchDownY = (int) event.getY();
-        View recentsButton = mNavigationBarView.getRecentsButton();
-        mDownOnRecents = mTouchDownX >= recentsButton.getLeft()
-                && mTouchDownX <= recentsButton.getRight()
-                && mTouchDownY >= recentsButton.getTop()
-                && mTouchDownY <= recentsButton.getBottom();
+
+        if (mNavigationBarView != null) {
+            View recentsButton = mNavigationBarView.getRecentsButton().getCurrentView();
+            if (recentsButton != null) {
+                mDownOnRecents = mTouchDownX >= recentsButton.getLeft()
+                        && mTouchDownX <= recentsButton.getRight()
+                        && mTouchDownY >= recentsButton.getTop()
+                        && mTouchDownY <= recentsButton.getBottom();
+            } else {
+                mDownOnRecents = false;
+            }
+        }
     }
 
     private boolean handleDragActionMoveEvent(MotionEvent event) {
@@ -218,7 +225,7 @@
             if (mDragMode == DRAG_MODE_DIVIDER) {
                 int position = !mIsVertical ? (int) event.getRawY() : (int) event.getRawX();
                 SnapTarget snapTarget = mDivider.getView().getSnapAlgorithm()
-                        .calculateSnapTarget(position, 0f /* velocity */);
+                        .calculateSnapTarget(position, 0f /* velocity */, false /* hardDismiss */);
                 mDivider.getView().resizeStack(position, snapTarget.position, snapTarget);
             } else if (mDragMode == DRAG_MODE_RECENTS) {
                 mRecentsComponent.onDraggingInRecents(event.getRawY());
@@ -237,7 +244,8 @@
                                 : (int) event.getRawY(),
                         mIsVertical
                                 ? mVelocityTracker.getXVelocity()
-                                : mVelocityTracker.getYVelocity());
+                                : mVelocityTracker.getYVelocity(),
+                        true /* avoidDismissStart */);
             } else if (mDragMode == DRAG_MODE_RECENTS) {
                 mRecentsComponent.onDraggingInRecentsEnded(mVelocityTracker.getYVelocity());
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
new file mode 100644
index 0000000..b8ae81f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -0,0 +1,266 @@
+/*
+ * 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.phone;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.Space;
+import com.android.systemui.R;
+import com.android.systemui.tuner.TunerService;
+
+import java.util.Objects;
+
+public class NavigationBarInflaterView extends FrameLayout implements TunerService.Tunable {
+
+    private static final String TAG = "NavBarInflater";
+
+    public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
+
+    private static final String MENU_IME = "menu_ime";
+    private static final String BACK = "back";
+    private static final String HOME = "home";
+    private static final String RECENT = "recent";
+    private static final String NAVSPACE = "space";
+
+    public static final String GRAVITY_SEPARATOR = ";";
+    public static final String BUTTON_SEPARATOR = ",";
+
+    private final LayoutInflater mLayoutInflater;
+    private final LayoutInflater mLandscapeInflater;
+
+    private FrameLayout mRot0;
+    private FrameLayout mRot90;
+    private SparseArray<ButtonDispatcher> mButtonDispatchers;
+    private String mCurrentLayout;
+
+    public NavigationBarInflaterView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mLayoutInflater = LayoutInflater.from(context);
+        Configuration landscape = new Configuration();
+        landscape.setTo(context.getResources().getConfiguration());
+        landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
+        mLandscapeInflater = LayoutInflater.from(context.createConfigurationContext(landscape));
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mRot0 = (FrameLayout) findViewById(R.id.rot0);
+        mRot90 = (FrameLayout) findViewById(R.id.rot90);
+        clearViews();
+        inflateLayout(getDefaultLayout());
+    }
+
+    private String getDefaultLayout() {
+        return mContext.getString(R.string.config_navBarLayout);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        TunerService.get(getContext()).removeTunable(this);
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (NAV_BAR_VIEWS.equals(key)) {
+            if (newValue == null) {
+                newValue = getDefaultLayout();
+            }
+            if (!Objects.equals(mCurrentLayout, newValue)) {
+                clearViews();
+                inflateLayout(newValue);
+            }
+        }
+    }
+
+    public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDisatchers) {
+        mButtonDispatchers = buttonDisatchers;
+        for (int i = 0; i < buttonDisatchers.size(); i++) {
+            initiallyFill(buttonDisatchers.valueAt(i));
+        }
+    }
+
+    private void initiallyFill(ButtonDispatcher buttonDispatcher) {
+        addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.start_group));
+        addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.center_group));
+        addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.end_group));
+        addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.start_group));
+        addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.center_group));
+        addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.end_group));
+    }
+
+    private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) {
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            // Need to manually search for each id, just in case each group has more than one
+            // of a single id.  It probably mostly a waste of time, but shouldn't take long
+            // and will only happen once.
+            if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) {
+                buttonDispatcher.addView(parent.getChildAt(i));
+            } else if (parent.getChildAt(i) instanceof ViewGroup) {
+                addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i));
+            }
+        }
+    }
+
+    private void inflateLayout(String newLayout) {
+        mCurrentLayout = newLayout;
+        String[] sets = newLayout.split(GRAVITY_SEPARATOR);
+        String[] start = sets[0].split(BUTTON_SEPARATOR);
+        String[] center = sets[1].split(BUTTON_SEPARATOR);
+        String[] end = sets[2].split(BUTTON_SEPARATOR);
+        inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.start_group),
+                (ViewGroup) mRot0.findViewById(R.id.start_group_lightsout), false);
+        inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.start_group),
+                (ViewGroup) mRot90.findViewById(R.id.start_group_lightsout), true);
+        inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group),
+                (ViewGroup) mRot0.findViewById(R.id.start_group_lightsout), false);
+        inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group),
+                (ViewGroup) mRot90.findViewById(R.id.start_group_lightsout), true);
+        inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.end_group),
+                (ViewGroup) mRot0.findViewById(R.id.start_group_lightsout), false);
+        inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.end_group),
+                (ViewGroup) mRot90.findViewById(R.id.start_group_lightsout), true);
+    }
+
+    private void inflateButtons(String[] buttons, ViewGroup parent, ViewGroup lightsOutParent,
+            boolean landscape) {
+        for (int i = 0; i < buttons.length; i++) {
+            copyToLightsout(inflateButton(buttons[i], parent, landscape), lightsOutParent);
+        }
+    }
+
+    private void copyToLightsout(View view, ViewGroup lightsOutParent) {
+        if (view instanceof FrameLayout) {
+            // The only ViewGroup we support in here is a FrameLayout, so copy those manually.
+            FrameLayout original = (FrameLayout) view;
+            FrameLayout layout = new FrameLayout(view.getContext());
+            for (int i = 0; i < original.getChildCount(); i++) {
+                copyToLightsout(original.getChildAt(i), layout);
+            }
+            lightsOutParent.addView(layout, copy(view.getLayoutParams()));
+        } else if (view instanceof Space) {
+            lightsOutParent.addView(new Space(view.getContext()), copy(view.getLayoutParams()));
+        } else {
+            lightsOutParent.addView(generateLightsOutView(view), copy(view.getLayoutParams()));
+        }
+    }
+
+    private View generateLightsOutView(View view) {
+        ImageView imageView = new ImageView(view.getContext());
+        // Copy everything we can about the original view.
+        imageView.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(),
+                view.getPaddingBottom());
+        imageView.setContentDescription(view.getContentDescription());
+        imageView.setId(view.getId());
+        // Only home gets a big dot, everything else will be little.
+        imageView.setImageResource(view.getId() == R.id.home
+                ? R.drawable.ic_sysbar_lights_out_dot_large
+                : R.drawable.ic_sysbar_lights_out_dot_small);
+        return imageView;
+    }
+
+    private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
+        return new LayoutParams(layoutParams.width, layoutParams.height);
+    }
+
+    private View inflateButton(String button, ViewGroup parent, boolean landscape) {
+        View v = null;
+        if (HOME.equals(button)) {
+            v = (landscape ? mLandscapeInflater : mLayoutInflater)
+                    .inflate(R.layout.home, parent, false);
+            if (landscape && isSw600Dp()) {
+                setupLandButton(v);
+            }
+        } else if (BACK.equals(button)) {
+            v = (landscape ? mLandscapeInflater : mLayoutInflater)
+                    .inflate(R.layout.back, parent, false);
+            if (landscape && isSw600Dp()) {
+                setupLandButton(v);
+            }
+        } else if (RECENT.equals(button)) {
+            v = (landscape ? mLandscapeInflater : mLayoutInflater)
+                    .inflate(R.layout.recent_apps, parent, false);
+            if (landscape && isSw600Dp()) {
+                setupLandButton(v);
+            }
+        } else if (MENU_IME.equals(button)) {
+            v = (landscape ? mLandscapeInflater : mLayoutInflater)
+                    .inflate(R.layout.menu_ime, parent, false);
+        } else if (NAVSPACE.equals(button)) {
+            v = (landscape ? mLandscapeInflater : mLayoutInflater)
+                    .inflate(R.layout.nav_key_space, parent, false);
+        } else {
+            throw new IllegalArgumentException("Unknown button " + button);
+        }
+        parent.addView(v);
+        if (mButtonDispatchers != null) {
+            final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId());
+            if (indexOfKey >= 0) {
+                mButtonDispatchers.valueAt(indexOfKey).addView(v);
+            }
+        }
+        return v;
+    }
+
+    private boolean isSw600Dp() {
+        Configuration configuration = mContext.getResources().getConfiguration();
+        return (configuration.smallestScreenWidthDp >= 600);
+    }
+
+    /**
+     * This manually sets the width of sw600dp landscape buttons because despite
+     * overriding the configuration from the overridden resources aren't loaded currently.
+     */
+    private void setupLandButton(View v) {
+        Resources res = mContext.getResources();
+        v.getLayoutParams().width = res.getDimensionPixelOffset(
+                R.dimen.navigation_key_width_sw600dp_land);
+        int padding = res.getDimensionPixelOffset(R.dimen.navigation_key_padding_sw600dp_land);
+        v.setPadding(padding, v.getPaddingTop(), padding, v.getPaddingBottom());
+    }
+
+    private void clearViews() {
+        if (mButtonDispatchers != null) {
+            for (int i = 0; i < mButtonDispatchers.size(); i++) {
+                mButtonDispatchers.valueAt(i).clear();
+            }
+        }
+        clearAllChildren((ViewGroup) mRot0.findViewById(R.id.nav_buttons));
+        clearAllChildren((ViewGroup) mRot0.findViewById(R.id.lights_out));
+        clearAllChildren((ViewGroup) mRot90.findViewById(R.id.nav_buttons));
+        clearAllChildren((ViewGroup) mRot90.findViewById(R.id.lights_out));
+    }
+
+    private void clearAllChildren(ViewGroup group) {
+        for (int i = 0; i < group.getChildCount(); i++) {
+            ((ViewGroup) group.getChildAt(i)).removeAllViews();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index efa8f5b..839b579 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -21,6 +21,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.app.ActivityManagerNative;
 import android.app.StatusBarManager;
 import android.content.Context;
@@ -34,6 +35,7 @@
 import android.os.RemoteException;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.Gravity;
 import android.view.IDockedStackListener.Stub;
@@ -44,15 +46,11 @@
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
-
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.policy.DeadZone;
-import com.android.systemui.statusbar.policy.KeyButtonView;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -100,6 +98,8 @@
     private boolean mWakeAndUnlocking;
     private boolean mCarMode = false;
 
+    private final SparseArray<ButtonDispatcher> mButtonDisatchers = new SparseArray<>();
+
     private class NavTransitionListener implements TransitionListener {
         private boolean mBackTransitioning;
         private boolean mHomeAppearing;
@@ -131,12 +131,14 @@
         }
 
         public void onBackAltCleared() {
+            ButtonDispatcher backButton = getBackButton();
+
             // When dismissing ime during unlock, force the back button to run the same appearance
             // animation as home (if we catch this condition early enough).
-            if (!mBackTransitioning && getBackButton().getVisibility() == VISIBLE
+            if (!mBackTransitioning && backButton.getVisibility() == VISIBLE
                     && mHomeAppearing && getHomeButton().getAlpha() == 0) {
                 getBackButton().setAlpha(0);
-                ValueAnimator a = ObjectAnimator.ofFloat(getBackButton(), "alpha", 0, 1);
+                ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1);
                 a.setStartDelay(mStartDelay);
                 a.setDuration(mDuration);
                 a.setInterpolator(mInterpolator);
@@ -190,6 +192,12 @@
         getIcons(context);
 
         mBarTransitions = new NavigationBarTransitions(this);
+
+        mButtonDisatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
+        mButtonDisatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
+        mButtonDisatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
+        mButtonDisatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
+        mButtonDisatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
     }
 
     public BarTransitions getBarTransitions() {
@@ -235,26 +243,27 @@
         return mRotatedViews;
     }
 
-    public KeyButtonView getRecentsButton() {
-        return (KeyButtonView) getCurrentView().findViewById(R.id.recent_apps);
+    public ButtonDispatcher getRecentsButton() {
+        return mButtonDisatchers.get(R.id.recent_apps);
     }
 
-    public View getMenuButton() {
-        return getCurrentView().findViewById(R.id.menu);
+    public ButtonDispatcher getMenuButton() {
+        return mButtonDisatchers.get(R.id.menu);
     }
 
-    public View getBackButton() {
-        return getCurrentView().findViewById(R.id.back);
+    public ButtonDispatcher getBackButton() {
+        return mButtonDisatchers.get(R.id.back);
     }
 
-    public KeyButtonView getHomeButton() {
-        return (KeyButtonView) getCurrentView().findViewById(R.id.home);
+    public ButtonDispatcher getHomeButton() {
+        return mButtonDisatchers.get(R.id.home);
     }
 
-    public View getImeSwitchButton() {
-        return getCurrentView().findViewById(R.id.ime_switcher);
+    public ButtonDispatcher getImeSwitchButton() {
+        return mButtonDisatchers.get(R.id.ime_switcher);
     }
 
+    @Nullable
     public View getAppShelf() {
         return getCurrentView().findViewById(R.id.app_shelf);
     }
@@ -329,19 +338,20 @@
                 ? getBackIconWithAlt(mCarMode, mVertical)
                 : getBackIcon(mCarMode, mVertical);
 
-        ((ImageView) getBackButton()).setImageDrawable(backIcon);
+        getBackButton().setImageDrawable(backIcon);
 
-        ((ImageView) getRecentsButton()).setImageDrawable(
+        getRecentsButton().setImageDrawable(
                 mVertical ? mRecentLandIcon : mRecentIcon);
 
         if (mCarMode) {
-            ((ImageView) getHomeButton()).setImageDrawable(mHomeCarModeIcon);
+            getHomeButton().setImageDrawable(mHomeCarModeIcon);
         } else {
-            ((ImageView) getHomeButton()).setImageDrawable(mHomeDefaultIcon);
+            getHomeButton().setImageDrawable(mHomeDefaultIcon);
         }
 
         final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
         getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
+
         // Update menu button in case the IME state has changed.
         setMenuVisibility(mShowMenu, true);
 
@@ -382,9 +392,9 @@
             disableRecent = false;
         }
 
-        getBackButton()   .setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
-        getHomeButton()   .setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
-        getRecentsButton().setVisibility(disableRecent     ? View.INVISIBLE : View.VISIBLE);
+        getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
+        getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
+        getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
 
         // The app shelf, if it exists, follows the visibility of the home button.
         View appShelf = getAppShelf();
@@ -475,6 +485,7 @@
         // Only show Menu if IME switcher not shown.
         final boolean shouldShow = mShowMenu &&
                 ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
+
         getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
     }
 
@@ -488,6 +499,11 @@
         mRotatedViews[Surface.ROTATION_270] = mRotatedViews[Surface.ROTATION_90];
 
         mCurrentView = mRotatedViews[Surface.ROTATION_0];
+        for (int i = 0; i < mButtonDisatchers.size(); i++) {
+            mButtonDisatchers.valueAt(i).setCurrentView(mCurrentView);
+        }
+        ((NavigationBarInflaterView) findViewById(R.id.navigation_inflater)).setButtonDispatchers(
+                mButtonDisatchers);
 
         getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
 
@@ -531,6 +547,9 @@
         }
         mCurrentView = mRotatedViews[rot];
         mCurrentView.setVisibility(View.VISIBLE);
+        for (int i = 0; i < mButtonDisatchers.size(); i++) {
+            mButtonDisatchers.valueAt(i).setCurrentView(mCurrentView);
+        }
         updateLayoutTransitionsEnabled();
 
         getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
@@ -621,33 +640,16 @@
 
             // We swap all children of the 90 and 270 degree layouts, since they are vertical
             View rotation90 = mRotatedViews[Surface.ROTATION_90];
-            swapChildrenOrderIfVertical(rotation90.findViewById(R.id.nav_buttons));
-            adjustExtraKeyGravity(rotation90, isLayoutRtl);
+            swapChildrenOrderIfVertical(rotation90);
 
             View rotation270 = mRotatedViews[Surface.ROTATION_270];
             if (rotation90 != rotation270) {
-                swapChildrenOrderIfVertical(rotation270.findViewById(R.id.nav_buttons));
-                adjustExtraKeyGravity(rotation270, isLayoutRtl);
+                swapChildrenOrderIfVertical(rotation270);
             }
             mIsLayoutRtl = isLayoutRtl;
         }
     }
 
-    private void adjustExtraKeyGravity(View navBar, boolean isLayoutRtl) {
-        View menu = navBar.findViewById(R.id.menu);
-        View imeSwitcher = navBar.findViewById(R.id.ime_switcher);
-        if (menu != null) {
-            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) menu.getLayoutParams();
-            lp.gravity = isLayoutRtl ? Gravity.BOTTOM : Gravity.TOP;
-            menu.setLayoutParams(lp);
-        }
-        if (imeSwitcher != null) {
-            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) imeSwitcher.getLayoutParams();
-            lp.gravity = isLayoutRtl ? Gravity.BOTTOM : Gravity.TOP;
-            imeSwitcher.setLayoutParams(lp);
-        }
-    }
-
     /**
      * Swaps the children order of a LinearLayout if it's orientation is Vertical
      *
@@ -657,6 +659,11 @@
         if (group instanceof LinearLayout) {
             LinearLayout linearLayout = (LinearLayout) group;
             if (linearLayout.getOrientation() == VERTICAL) {
+                if ((linearLayout.getGravity() & Gravity.TOP) != 0) {
+                    linearLayout.setGravity(Gravity.BOTTOM);
+                } else if ((linearLayout.getGravity() & Gravity.BOTTOM) != 0) {
+                    linearLayout.setGravity(Gravity.TOP);
+                }
                 int childCount = linearLayout.getChildCount();
                 ArrayList<View> childList = new ArrayList<>(childCount);
                 for (int i = 0; i < childCount; i++) {
@@ -667,6 +674,11 @@
                     linearLayout.addView(childList.get(i));
                 }
             }
+        } else if (group instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) group;
+            for (int i = 0; i < viewGroup.getChildCount(); i++) {
+                swapChildrenOrderIfVertical(viewGroup.getChildAt(i));
+            }
         }
     }
 
@@ -754,13 +766,12 @@
         pw.println("    }");
     }
 
-    private static void dumpButton(PrintWriter pw, String caption, View button) {
+    private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
         pw.print("      " + caption + ": ");
         if (button == null) {
             pw.print("null");
         } else {
-            pw.print(PhoneStatusBar.viewInfo(button)
-                    + " " + visibilityToString(button.getVisibility())
+            pw.print(visibilityToString(button.getVisibility())
                     + " alpha=" + button.getAlpha()
                     );
         }
@@ -770,4 +781,5 @@
     public interface OnVerticalChangedListener {
         void onVerticalChanged(boolean isVertical);
     }
+
 }
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 f6fc259..3130eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -16,12 +16,13 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.app.Notification;
 import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
 
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -29,18 +30,19 @@
 /**
  * A class to handle notifications and their corresponding groups.
  */
-public class NotificationGroupManager {
+public class NotificationGroupManager implements HeadsUpManager.OnHeadsUpChangedListener {
 
     private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
     private OnGroupChangeListener mListener;
     private int mBarState = -1;
+    private ArraySet<String> mHeadsUpedEntries = new ArraySet<>();
 
     public void setOnGroupChangeListener(OnGroupChangeListener listener) {
         mListener = listener;
     }
 
     public boolean isGroupExpanded(StatusBarNotification sbn) {
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
         if (group == null) {
             return false;
         }
@@ -48,7 +50,7 @@
     }
 
     public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
         if (group == null) {
             return;
         }
@@ -75,8 +77,7 @@
      */
     private void onEntryRemovedInternal(NotificationData.Entry removed,
             final StatusBarNotification sbn) {
-        Notification notif = sbn.getNotification();
-        String groupKey = sbn.getGroupKey();
+        String groupKey = getGroupKey(sbn);
         final NotificationGroup group = mGroupMap.get(groupKey);
         if (group == null) {
             // When an app posts 2 different notifications as summary of the same group, then a
@@ -85,7 +86,7 @@
             // the close future. See b/23676310 for reference.
             return;
         }
-        if (notif.isGroupChild()) {
+        if (isGroupChild(sbn)) {
             group.children.remove(removed);
         } else {
             group.summary = null;
@@ -97,16 +98,16 @@
         }
     }
 
-    public void onEntryAdded(NotificationData.Entry added) {
-        StatusBarNotification sbn = added.notification;
-        Notification notif = sbn.getNotification();
-        String groupKey = sbn.getGroupKey();
+    public void onEntryAdded(final NotificationData.Entry added) {
+        final StatusBarNotification sbn = added.notification;
+        boolean isGroupChild = isGroupChild(sbn);
+        String groupKey = getGroupKey(sbn);
         NotificationGroup group = mGroupMap.get(groupKey);
         if (group == null) {
             group = new NotificationGroup();
             mGroupMap.put(groupKey, group);
         }
-        if (notif.isGroupChild()) {
+        if (isGroupChild) {
             group.children.add(added);
         } else {
             group.summary = added;
@@ -119,17 +120,17 @@
 
     public void onEntryUpdated(NotificationData.Entry entry,
             StatusBarNotification oldNotification) {
-        if (mGroupMap.get(oldNotification.getGroupKey()) != null) {
+        if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
             onEntryRemovedInternal(entry, oldNotification);
         }
         onEntryAdded(entry);
     }
 
     public boolean isVisible(StatusBarNotification sbn) {
-        if (!sbn.getNotification().isGroupChild()) {
+        if (!isGroupChild(sbn)) {
             return true;
         }
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
         if (group != null && (group.expanded || group.summary == null)) {
             return true;
         }
@@ -137,10 +138,10 @@
     }
 
     public boolean hasGroupChildren(StatusBarNotification sbn) {
-        if (!sbn.getNotification().isGroupSummary()) {
+        if (!isGroupSummary(sbn)) {
             return false;
         }
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
         if (group == null) {
             return false;
         }
@@ -165,10 +166,10 @@
      * @return whether a given notification is a child in a group which has a summary
      */
     public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
-        if (!sbn.getNotification().isGroupChild()) {
+        if (!isGroupChild(sbn)) {
             return false;
         }
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
         if (group == null || group.summary == null) {
             return false;
         }
@@ -179,10 +180,10 @@
      * @return whether a given notification is a summary in a group which has children
      */
     public boolean isSummaryOfGroup(StatusBarNotification sbn) {
-        if (!sbn.getNotification().isGroupSummary()) {
+        if (!isGroupSummary(sbn)) {
             return false;
         }
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
         if (group == null) {
             return false;
         }
@@ -190,24 +191,88 @@
     }
 
     public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
         return group == null ? null
                 : group.summary == null ? null
                 : group.summary.row;
     }
 
-    public void onEntryHeadsUped(NotificationData.Entry headsUp) {
-        // TODO: handle this nicely
-    }
-
     public void toggleGroupExpansion(StatusBarNotification sbn) {
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
         if (group == null) {
             return;
         }
         setGroupExpanded(group, !group.expanded);
     }
 
+    private boolean isIsolated(StatusBarNotification sbn) {
+        return mHeadsUpedEntries.contains(sbn.getKey()) && sbn.getNotification().isGroupChild();
+    }
+
+    private boolean isGroupSummary(StatusBarNotification sbn) {
+        if (isIsolated(sbn)) {
+            return true;
+        }
+        return sbn.getNotification().isGroupSummary();
+    }
+    private boolean isGroupChild(StatusBarNotification sbn) {
+        if (isIsolated(sbn)) {
+            return false;
+        }
+        return sbn.getNotification().isGroupChild();
+    }
+
+    private String getGroupKey(StatusBarNotification sbn) {
+        if (isIsolated(sbn)) {
+            return sbn.getKey();
+        }
+        return sbn.getGroupKey();
+    }
+
+    @Override
+    public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
+    }
+
+    @Override
+    public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
+    }
+
+    @Override
+    public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
+    }
+
+    @Override
+    public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+        final StatusBarNotification sbn = entry.notification;
+        if (entry.row.isHeadsUp()) {
+            if (!mHeadsUpedEntries.contains(sbn.getKey())) {
+                final boolean groupChild = sbn.getNotification().isGroupChild();
+                if (groupChild) {
+                    // We will be isolated now, so lets update the groups
+                    onEntryRemovedInternal(entry, entry.notification);
+                }
+                mHeadsUpedEntries.add(sbn.getKey());
+                if (groupChild) {
+                    onEntryAdded(entry);
+                    mListener.onChildIsolationChanged();
+                }
+            }
+        } else {
+            if (mHeadsUpedEntries.contains(sbn.getKey())) {
+                boolean isolatedBefore = isIsolated(sbn);
+                if (isolatedBefore) {
+                    // not isolated anymore, we need to update the groups
+                    onEntryRemovedInternal(entry, entry.notification);
+                }
+                mHeadsUpedEntries.remove(sbn.getKey());
+                if (isolatedBefore) {
+                    onEntryAdded(entry);
+                    mListener.onChildIsolationChanged();
+                }
+            }
+        }
+    }
+
     public static class NotificationGroup {
         public final HashSet<NotificationData.Entry> children = new HashSet<>();
         public NotificationData.Entry summary;
@@ -230,5 +295,10 @@
          * @param group the group created
          */
         void onGroupCreatedFromChildren(NotificationGroup group);
+
+        /**
+         * The isolation of a child has changed i.e it's group changes.
+         */
+        void onChildIsolationChanged();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index aa01bf2..f0b7894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -81,7 +81,6 @@
     private boolean mMotionAborted;
     private boolean mUpwardsWhenTresholdReached;
     private boolean mAnimatingOnDown;
-    private int mLayoutHeight = 0;
 
     private ValueAnimator mHeightAnimator;
     private ObjectAnimator mPeekAnimator;
@@ -715,10 +714,7 @@
     @Override
     protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        if (mLayoutHeight != getHeight()) {
-            mLayoutHeight = getHeight();
-            mStatusBar.onPanelHeightChanged();
-        }
+        mStatusBar.onPanelLaidOut();
         requestPanelHeightUpdate();
         mHasLayoutedSinceDown = true;
         if (mUpdateFlingOnLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 8a5cf26..ffc836c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -293,7 +293,7 @@
     Display mDisplay;
     Point mCurrentDisplaySize = new Point();
 
-    StatusBarWindowView mStatusBarWindow;
+    protected StatusBarWindowView mStatusBarWindow;
     PhoneStatusBarView mStatusBarView;
     private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     protected StatusBarWindowManager mStatusBarWindowManager;
@@ -673,8 +673,7 @@
         updateDisplaySize(); // populates mDisplayMetrics
         updateResources();
 
-        mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
-                R.layout.super_status_bar, null);
+        inflateStatusBarWindow(context);
         mStatusBarWindow.setService(this);
         mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {
             @Override
@@ -707,6 +706,7 @@
         mHeadsUpManager.setBar(this);
         mHeadsUpManager.addListener(this);
         mHeadsUpManager.addListener(mNotificationPanel);
+        mHeadsUpManager.addListener(mGroupManager);
         mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
         mNotificationData.setHeadsUpManager(mHeadsUpManager);
 
@@ -934,6 +934,11 @@
         return mStatusBarView;
     }
 
+    protected void inflateStatusBarWindow(Context context) {
+        mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
+                R.layout.super_status_bar, null);
+    }
+
     protected void createNavigationBarView(Context context) {
         inflateNavigationBarView(context);
         mNavigationBarView.setDisabledFlags(mDisabled1);
@@ -1184,14 +1189,20 @@
     private void prepareNavigationBarView() {
         mNavigationBarView.reorient();
 
-        mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
-        mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
-        mNavigationBarView.getRecentsButton().setLongClickable(true);
-        mNavigationBarView.getRecentsButton().setOnLongClickListener(mRecentsLongClickListener);
-        mNavigationBarView.getBackButton().setLongClickable(true);
-        mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackListener);
-        mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
-        mNavigationBarView.getHomeButton().setOnLongClickListener(mLongPressHomeListener);
+        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
+        recentsButton.setOnClickListener(mRecentsClickListener);
+        recentsButton.setOnTouchListener(mRecentsPreloadOnTouchListener);
+        recentsButton.setLongClickable(true);
+        recentsButton.setOnLongClickListener(mRecentsLongClickListener);
+
+        ButtonDispatcher backButton = mNavigationBarView.getBackButton();
+        backButton.setLongClickable(true);
+        backButton.setOnLongClickListener(mLongPressBackListener);
+
+        ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+        homeButton.setOnTouchListener(mHomeActionListener);
+        homeButton.setOnLongClickListener(mLongPressHomeListener);
+
         mAssistManager.onConfigurationChanged();
     }
 
@@ -1396,16 +1407,21 @@
 
         }
 
-        ArrayList<View> toRemove = new ArrayList<>();
+        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
         for (int i=0; i< mStackScroller.getChildCount(); i++) {
             View child = mStackScroller.getChildAt(i);
             if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
-                toRemove.add(child);
+                toRemove.add((ExpandableNotificationRow) child);
             }
         }
 
-        for (View remove : toRemove) {
+        for (ExpandableNotificationRow remove : toRemove) {
+            if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
+                // we are only transfering this notification to its parent, don't generate an animation
+                mStackScroller.setChildTransferInProgress(true);
+            }
             mStackScroller.removeView(remove);
+            mStackScroller.setChildTransferInProgress(false);
         }
         for (int i=0; i<toShow.size(); i++) {
             View v = toShow.get(i);
@@ -1561,6 +1577,10 @@
         mIconController.updateNotificationIcons(mNotificationData);
     }
 
+    public void requestNotificationUpdate() {
+        updateNotifications();
+    }
+
     @Override
     protected void updateRowStates() {
         super.updateRowStates();
@@ -4023,9 +4043,10 @@
     }
 
     @Override
-    public void onExpandClicked(View clickedView, boolean nowExpanded) {
+    public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
+        mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
         if (mState == StatusBarState.KEYGUARD && nowExpanded) {
-            goToLockedShade(clickedView);
+            goToLockedShade(clickedEntry.row);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 813a167..902fd3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -164,6 +164,9 @@
     public void panelScrimMinFractionChanged(float minFraction) {
         if (mMinFraction != minFraction) {
             mMinFraction = minFraction;
+            if (minFraction != 0.0f) {
+                mScrimController.animateNextChange();
+            }
             updateScrimFraction();
         }
     }
@@ -176,7 +179,7 @@
     }
 
     private void updateScrimFraction() {
-        float scrimFraction = Math.max(mPanelFraction - mMinFraction / (1.0f - mMinFraction), 0);
+        float scrimFraction = Math.max(mPanelFraction, mMinFraction);
         mScrimController.setPanelExpansion(scrimFraction);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
new file mode 100644
index 0000000..da16954
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
@@ -0,0 +1,55 @@
+/*
+ * 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.phone;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * Automatically reverses the order of children as they are added.
+ * Also reverse the width and height values of layout params
+ */
+public class ReverseLinearLayout extends LinearLayout {
+
+    public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void addView(View child) {
+        reversParams(child.getLayoutParams());
+        super.addView(child, 0);
+    }
+
+    @Override
+    public void addView(View child, ViewGroup.LayoutParams params) {
+        reversParams(params);
+        super.addView(child, 0, params);
+    }
+
+    private void reversParams(ViewGroup.LayoutParams params) {
+        if (params == null) {
+            return;
+        }
+        int width = params.width;
+        params.width = params.height;
+        params.height = width;
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index b9e9292..403199a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -52,8 +52,8 @@
     private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
     private static final int TAG_KEY_ANIM = R.id.scrim;
     private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
-    private static final int TAG_HUN_START_ALPHA = R.id.hun_scrim_alpha_start;
-    private static final int TAG_HUN_END_ALPHA = R.id.hun_scrim_alpha_end;
+    private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
+    private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
 
     private final ScrimView mScrimBehind;
     private final ScrimView mScrimInFront;
@@ -164,6 +164,10 @@
         scheduleUpdate();
     }
 
+    public void animateNextChange() {
+        mAnimateChange = true;
+    }
+
     public void setDozing(boolean dozing) {
         if (mDozing != dozing) {
             mDozing = dozing;
@@ -271,21 +275,7 @@
     }
 
     private void setScrimColor(View scrim, float alpha) {
-        ValueAnimator runningAnim = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
-        Float target = (Float) scrim.getTag(TAG_KEY_ANIM_TARGET);
-        if (runningAnim != null && target != null) {
-            if (alpha != target) {
-                runningAnim.cancel();
-            } else {
-                return;
-            }
-        }
-        if (mAnimateChange) {
-            startScrimAnimation(scrim, alpha);
-        } else {
-            setCurrentScrimAlpha(scrim, alpha);
-            updateScrimColor(scrim);
-        }
+        updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
     }
 
     private float getDozeAlpha(View scrim) {
@@ -428,41 +418,44 @@
     }
 
     private void updateHeadsUpScrim(boolean animate) {
-        float alpha = calculateHeadsUpAlpha();
-        ValueAnimator previousAnimator = StackStateAnimator.getChildTag(mHeadsUpScrim,
+        updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+    }
+
+    private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
+        ValueAnimator previousAnimator = StackStateAnimator.getChildTag(scrim,
                 TAG_KEY_ANIM);
         float animEndValue = -1;
         if (previousAnimator != null) {
-            if (animate || alpha == mCurrentHeadsUpAlpha) {
+            if (animate || alpha == currentAlpha) {
                 previousAnimator.cancel();
             } else {
-                animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, TAG_HUN_END_ALPHA);
+                animEndValue = StackStateAnimator.getChildTag(scrim, TAG_END_ALPHA);
             }
         }
-        if (alpha != mCurrentHeadsUpAlpha && alpha != animEndValue) {
+        if (alpha != currentAlpha && alpha != animEndValue) {
             if (animate) {
-                startScrimAnimation(mHeadsUpScrim, alpha);
-                mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, mCurrentHeadsUpAlpha);
-                mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
+                startScrimAnimation(scrim, alpha);
+                scrim.setTag(TAG_START_ALPHA, currentAlpha);
+                scrim.setTag(TAG_END_ALPHA, alpha);
             } else {
                 if (previousAnimator != null) {
-                    float previousStartValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
-                            TAG_HUN_START_ALPHA);
-                    float previousEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
-                           TAG_HUN_END_ALPHA);
+                    float previousStartValue = StackStateAnimator.getChildTag(scrim,
+                            TAG_START_ALPHA);
+                    float previousEndValue = StackStateAnimator.getChildTag(scrim,
+                            TAG_END_ALPHA);
                     // we need to increase all animation keyframes of the previous animator by the
                     // relative change to the end value
                     PropertyValuesHolder[] values = previousAnimator.getValues();
                     float relativeDiff = alpha - previousEndValue;
                     float newStartValue = previousStartValue + relativeDiff;
                     values[0].setFloatValues(newStartValue, alpha);
-                    mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, newStartValue);
-                    mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
+                    scrim.setTag(TAG_START_ALPHA, newStartValue);
+                    scrim.setTag(TAG_END_ALPHA, alpha);
                     previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                 } else {
                     // update the alpha directly
-                    setCurrentScrimAlpha(mHeadsUpScrim, alpha);
-                    updateScrimColor(mHeadsUpScrim);
+                    setCurrentScrimAlpha(scrim, alpha);
+                    updateScrimColor(scrim);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index b16c98e..64fb066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -20,6 +20,7 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
@@ -28,6 +29,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
@@ -122,7 +124,10 @@
         notificationIconArea.addView(mNotificationIconAreaInner);
 
         mStatusIconsKeyguard = (LinearLayout) keyguardStatusBar.findViewById(R.id.statusIcons);
+
         mBatteryMeterView = (BatteryMeterView) statusBar.findViewById(R.id.battery);
+        maybeScaleBatteryMeterView(context);
+
         mClock = (TextView) statusBar.findViewById(R.id.clock);
         mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext,
                 android.R.interpolator.linear_out_slow_in);
@@ -136,6 +141,30 @@
         TunerService.get(mContext).addTunable(this, ICON_BLACKLIST);
     }
 
+    /**
+     * Looks up the scale factor for status bar icons and scales the battery view by that amount
+     * if appropriate.
+     */
+    private void maybeScaleBatteryMeterView(Context context) {
+        Resources res = context.getResources();
+        TypedValue typedValue = new TypedValue();
+
+        res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
+        float iconScaleFactor = typedValue.getFloat();
+
+        if (iconScaleFactor == 1.f) {
+            return;
+        }
+
+        float batteryHeight = res.getDimension(R.dimen.status_bar_battery_icon_height);
+        float batteryWidth = res.getDimension(R.dimen.status_bar_battery_icon_width);
+
+        LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
+                (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
+
+        mBatteryMeterView.setLayoutParams(scaledLayoutParams);
+    }
+
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (!ICON_BLACKLIST.equals(key)) {
@@ -174,8 +203,12 @@
         boolean blocked = mIconBlacklist.contains(slot);
         StatusBarIconView view = new StatusBarIconView(mContext, slot, null, blocked);
         view.set(icon);
-        mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
+
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
+        lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
+        mStatusIcons.addView(view, viewIndex, lp);
+
         view = new StatusBarIconView(mContext, slot, null, blocked);
         view.set(icon);
         mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index b38c3fe..dd7bd56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -190,7 +190,6 @@
         if (alert) {
             HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
             headsUpEntry.updateEntry();
-            mGroupManager.onEntryHeadsUped(headsUp);
             setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
         }
     }
@@ -384,10 +383,17 @@
             for (HeadsUpEntry entry : mSortedEntries) {
                 ExpandableNotificationRow row = entry.entry.row;
                 if (row.isPinned()) {
+                    if (row.isChildInGroup()) {
+                        final ExpandableNotificationRow groupSummary
+                                = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+                        if (groupSummary != null) {
+                            row = groupSummary;
+                        }
+                    }
                     row.getLocationOnScreen(mTmpTwoArray);
                     minX = mTmpTwoArray[0];
                     maxX = mTmpTwoArray[0] + row.getWidth();
-                    maxY = row.getHeadsUpHeight();
+                    maxY = row.getIntrinsicHeight();
                     break;
                 }
             }
@@ -448,6 +454,8 @@
         for (String key : mHeadsUpEntries.keySet()) {
             HeadsUpEntry entry = mHeadsUpEntries.get(key);
             setEntryPinned(entry, false /* isPinned */);
+            // maybe it got un sticky
+            entry.updateEntry(false /* updatePostTime */);
         }
     }
 
@@ -470,6 +478,10 @@
         mTrackingHeadsUp = trackingHeadsUp;
     }
 
+    public boolean isTrackingHeadsUp() {
+        return mTrackingHeadsUp;
+    }
+
     public void setIsExpanded(boolean isExpanded) {
         if (isExpanded != mIsExpanded) {
             mIsExpanded = isExpanded;
@@ -482,9 +494,25 @@
         }
     }
 
-    public int getTopHeadsUpHeight() {
+    /**
+     * @return the height of the top heads up notification when pinned. This is different from the
+     *         intrinsic height, which also includes whether the notification is system expanded and
+     *         is mainly used when dragging down from a heads up notification.
+     */
+    public int getTopHeadsUpPinnedHeight() {
         HeadsUpEntry topEntry = getTopEntry();
-        return topEntry != null ? topEntry.entry.row.getHeadsUpHeight() : 0;
+        if (topEntry == null || topEntry.entry == null) {
+            return 0;
+        }
+        ExpandableNotificationRow row = topEntry.entry.row;
+        if (row.isChildInGroup()) {
+            final ExpandableNotificationRow groupSummary
+                    = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+            if (groupSummary != null) {
+                row = groupSummary;
+            }
+        }
+        return row.getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
     }
 
     /**
@@ -558,6 +586,22 @@
     }
 
     /**
+     * Set an entry to be expanded and therefore stick in the heads up area if it's pinned
+     * until it's collapsed again.
+     */
+    public void setExpanded(NotificationData.Entry entry, boolean expanded) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+        if (headsUpEntry != null && headsUpEntry.expanded != expanded) {
+            headsUpEntry.expanded = expanded;
+            if (expanded) {
+                headsUpEntry.removeAutoRemovalCallbacks();
+            } else {
+                headsUpEntry.updateEntry(false /* updatePostTime */);
+            }
+        }
+    }
+
+    /**
      * This represents a notification and how long it is in a heads up mode. It also manages its
      * lifecycle automatically when created.
      */
@@ -567,6 +611,7 @@
         public long earliestRemovaltime;
         private Runnable mRemoveHeadsUpRunnable;
         public boolean remoteInputActive;
+        public boolean expanded;
 
         public void setEntry(final NotificationData.Entry entry) {
             this.entry = entry;
@@ -601,7 +646,7 @@
             if (mEntriesToRemoveAfterExpand.contains(entry)) {
                 mEntriesToRemoveAfterExpand.remove(entry);
             }
-            if (!hasFullScreenIntent(entry) && !mRemoteInputActive) {
+            if (!isSticky()) {
                 long finishTime = postTime + mHeadsUpNotificationDecay;
                 long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
                 mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
@@ -609,6 +654,11 @@
             mSortedEntries.add(HeadsUpEntry.this);
         }
 
+        private boolean isSticky() {
+            return (entry.row.isPinned() && expanded)
+                    || remoteInputActive || hasFullScreenIntent(entry);
+        }
+
         @Override
         public int compareTo(HeadsUpEntry o) {
             boolean selfFullscreen = hasFullScreenIntent(entry);
@@ -641,6 +691,8 @@
             removeAutoRemovalCallbacks();
             entry = null;
             mRemoveHeadsUpRunnable = null;
+            expanded = false;
+            remoteInputActive = false;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 3a97be6..a3f571e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -155,13 +155,7 @@
     }
 
     public int getInnerHeight() {
-        return mLayoutHeight - mTopPadding - getTopHeadsUpPushIn();
-    }
-
-    private int getTopHeadsUpPushIn() {
-        ExpandableNotificationRow topHeadsUpEntry = getTopHeadsUpEntry();
-        return topHeadsUpEntry != null ? topHeadsUpEntry.getHeadsUpHeight()
-                - topHeadsUpEntry.getMinHeight(): 0;
+        return mLayoutHeight - mTopPadding;
     }
 
     public boolean isShadeExpanded() {
@@ -180,11 +174,6 @@
         return mMaxHeadsUpTranslation;
     }
 
-    public ExpandableNotificationRow getTopHeadsUpEntry() {
-        HeadsUpManager.HeadsUpEntry topEntry = mHeadsUpManager.getTopEntry();
-        return topEntry == null ? null : topEntry.entry.row;
-    }
-
     public void setDismissAllInProgress(boolean dismissAllInProgress) {
         mDismissAllInProgress = dismissAllInProgress;
     }
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 baccd2c..dc40fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -24,6 +24,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
@@ -170,8 +171,15 @@
         mChildren.remove(row);
         removeView(row);
 
-        View divider = mDividers.remove(childIndex);
+        final View divider = mDividers.remove(childIndex);
         removeView(divider);
+        getOverlay().add(divider);
+        CrossFadeHelper.fadeOut(divider, new Runnable() {
+            @Override
+            public void run() {
+                getOverlay().remove(divider);
+            }
+        });
 
         row.setSystemChildExpanded(false);
         updateGroupOverflow();
@@ -403,31 +411,20 @@
     }
 
     public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator,
-            boolean withDelays, long baseDelay, long duration) {
+            long baseDelay, long duration) {
         int childCount = mChildren.size();
         ViewState tmpState = new ViewState();
-        int delayIndex = 0;
-        int maxAllowChildCount = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
         for (int i = childCount - 1; i >= 0; i--) {
             ExpandableNotificationRow child = mChildren.get(i);
             StackViewState viewState = state.getViewStateForView(child);
-            int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN,
-                    delayIndex);
-            long delay = withDelays
-                    ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN
-                    : 0;
-            delay = (long) (delay * (mChildrenExpanded ? 1.0f : 0.5f) + baseDelay);
-            stateAnimator.startStackAnimations(child, viewState, state, -1, delay);
+            stateAnimator.startStackAnimations(child, viewState, state, -1, baseDelay);
 
             // layout the divider
             View divider = mDividers.get(i);
             tmpState.initFrom(divider);
             tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
             tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
-            stateAnimator.startViewAnimations(divider, tmpState, delay, duration);
-            if (i < maxAllowChildCount) {
-                delayIndex++;
-            }
+            stateAnimator.startViewAnimations(divider, tmpState, baseDelay, duration);
         }
         if (mGroupOverflowContainer != null) {
             stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState,
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 0ed1527..36b2810 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -138,6 +138,7 @@
     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
     private boolean mAnimationsEnabled;
     private boolean mChangePositionInProgress;
+    private boolean mChildTransferInProgress;
 
     /**
      * The raw amount of the overScroll on the top, which is not rubber-banded.
@@ -363,14 +364,14 @@
         updateContentHeight();
         clampScrollPosition();
         if (mRequestViewResizeAnimationOnLayout) {
-            requestAnimationOnViewResize();
+            requestAnimationOnViewResize(null);
             mRequestViewResizeAnimationOnLayout = false;
         }
         requestChildrenUpdate();
     }
 
-    private void requestAnimationOnViewResize() {
-        if (mIsExpanded && mAnimationsEnabled) {
+    private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
+        if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
             mNeedViewResizeAnimation = true;
             mNeedsAnimation = true;
         }
@@ -495,27 +496,28 @@
         int stackHeight;
         float paddingOffset;
         boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
-        int normalUnfoldPositionStart = trackingHeadsUp ? mHeadsUpManager.getTopHeadsUpHeight()
+        int normalUnfoldPositionStart = trackingHeadsUp
+                ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
                 : minStackHeight;
         if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
                 || getNotGoneChildCount() == 0) {
             paddingOffset = mTopPaddingOverflow;
             stackHeight = newStackHeight;
         } else {
-
-            // We did not reach the position yet where we actually start growing,
-            // so we translate the stack upwards.
-            int translationY = (newStackHeight - minStackHeight);
-            // A slight parallax effect is introduced in order for the stack to catch up with
-            // the top card.
-            float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
-                    / minStackHeight;
-            partiallyThere = Math.max(0, partiallyThere);
+            int translationY;
             if (!trackingHeadsUp) {
+                // We did not reach the position yet where we actually start growing,
+                // so we translate the stack upwards.
+                translationY = (newStackHeight - minStackHeight);
+                // A slight parallax effect is introduced in order for the stack to catch up with
+                // the top card.
+                float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
+                        / minStackHeight;
+                partiallyThere = Math.max(0, partiallyThere);
                 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
                         mCollapseSecondCardPadding);
             } else {
-                translationY = (int) (height - mHeadsUpManager.getTopHeadsUpHeight());
+                translationY = (int) (height - normalUnfoldPositionStart);
             }
             paddingOffset = translationY - mTopPadding;
             stackHeight = (int) (height - (translationY - mTopPadding));
@@ -729,7 +731,10 @@
                 if (slidingChild instanceof ExpandableNotificationRow) {
                     ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
-                            && mHeadsUpManager.getTopEntry().entry.row != row) {
+                            && mHeadsUpManager.getTopEntry().entry.row != row
+                            && mGroupManager.getGroupSummary(
+                                mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
+                                != row) {
                         continue;
                     }
                     return row.getViewAtPosition(touchY - childTop);
@@ -1506,7 +1511,7 @@
 
     public int getMinStackHeight() {
         final ExpandableView firstChild = getFirstChildNotGone();
-        final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
+        final int firstChildMinHeight = firstChild != null ? firstChild.getMinHeight()
                 : mCollapsedSize;
         return firstChildMinHeight + mBottomStackPeekSize + mCollapseSecondCardPadding;
     }
@@ -1628,12 +1633,16 @@
         }
     }
 
+    public void setChildTransferInProgress(boolean childTransferInProgress) {
+        mChildTransferInProgress = childTransferInProgress;
+    }
+
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         // we only call our internal methods if this is actually a removal and not just a
         // notification which becomes a child notification
-        if (!isChildInGroup(child)) {
+        if (!mChildTransferInProgress) {
             onViewRemovedInternal(child);
         }
     }
@@ -2337,7 +2346,10 @@
         clampScrollPosition();
         notifyHeightChangeListener(view);
         if (needsAnimation) {
-            requestAnimationOnViewResize();
+            ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
+                    ? (ExpandableNotificationRow) view
+                    : null;
+            requestAnimationOnViewResize(row);
         }
         requestChildrenUpdate();
     }
@@ -2801,7 +2813,7 @@
 
     @Override
     public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
-        boolean animated = mAnimationsEnabled && mIsExpanded;
+        boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
         if (animated) {
             mExpandedGroupView = changedRow;
             mNeedsAnimation = true;
@@ -2812,13 +2824,12 @@
 
     @Override
     public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
-        for (NotificationData.Entry entry : group.children) {
-            ExpandableNotificationRow row = entry.row;
-            if (indexOfChild(row) != -1) {
-                removeView(row);
-                group.summary.row.addChildNotification(row);
-            }
-        }
+        mPhoneStatusBar.requestNotificationUpdate();
+    }
+
+    @Override
+    public void onChildIsolationChanged() {
+        mPhoneStatusBar.requestNotificationUpdate();
     }
 
     public void generateChildOrderChangedEvent() {
@@ -3113,7 +3124,7 @@
                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
 
                 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
-                StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED,
+                StackStateAnimator.ANIMATION_DURATION_STANDARD,
 
                 // ANIMATION_TYPE_HEADS_UP_APPEAR
                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 5496963..822012d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -410,10 +410,6 @@
         // How far in is the element currently transitioning into the bottom stack.
         float yPositionInScrollView = 0.0f;
 
-        // If we have a heads-up higher than the collapsed height we need to add the difference to
-        // the padding of all other elements, i.e push in the top stack slightly.
-        ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry();
-
         int childCount = algorithmState.visibleChildren.size();
         int numberOfElementsCompletelyIn = algorithmState.partialInTop == 1.0f
                 ? algorithmState.lastTopStackIndex
@@ -422,7 +418,7 @@
             ExpandableView child = algorithmState.visibleChildren.get(i);
             StackViewState childViewState = resultState.getViewStateForView(child);
             childViewState.location = StackViewState.LOCATION_UNKNOWN;
-            int childHeight = getMaxAllowedChildHeight(child, ambientState);
+            int childHeight = getMaxAllowedChildHeight(child);
             int minHeight = child.getMinHeight();
             float yPositionInScrollViewAfterElement = yPositionInScrollView
                     + childHeight
@@ -504,11 +500,6 @@
             currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
             yPositionInScrollView = yPositionInScrollViewAfterElement;
 
-            if (ambientState.isShadeExpanded() && topHeadsUpEntry != null
-                    && child != topHeadsUpEntry) {
-                childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() -
-                        mFirstChildMinHeight;
-            }
             childViewState.yTranslation += ambientState.getTopPadding()
                     + ambientState.getStackTranslation();
         }
@@ -533,10 +524,6 @@
             StackViewState childState = resultState.getViewStateForView(row);
             boolean isTopEntry = topHeadsUpEntry == row;
             if (mIsExpanded) {
-                if (isTopEntry) {
-                    childState.height += row.getHeadsUpHeight() - mFirstChildMinHeight;
-                }
-                childState.height = Math.max(childState.height, row.getHeadsUpHeight());
                 // Ensure that the heads up is always visible even when scrolled off from the bottom
                 float bottomPosition = ambientState.getMaxHeadsUpTranslation() - childState.height;
                 childState.yTranslation = Math.min(childState.yTranslation,
@@ -545,12 +532,12 @@
             if (row.isPinned()) {
                 childState.yTranslation = Math.max(childState.yTranslation,
                         mNotificationsTopPadding);
-                childState.height = Math.max(row.getHeadsUpHeight(), childState.height);
+                childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
                 if (!isTopEntry) {
                     // Ensure that a headsUp doesn't vertically extend further than the heads-up at
                     // the top most z-position
                     StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
-                    childState.height = row.getHeadsUpHeight();
+                    childState.height = row.getIntrinsicHeight();
                     childState.yTranslation = topState.yTranslation + topState.height
                             - childState.height;
                 }
@@ -608,16 +595,8 @@
                 mFirstChildMinHeight - childHeight);
     }
 
-    private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
-        if (child instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            if (ambientState == null && row.isHeadsUp()
-                    || ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
-                int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight();
-                return mFirstChildMinHeight + extraSize;
-            }
-            return row.getIntrinsicHeight();
-        } else if (child instanceof ExpandableView) {
+    private int getMaxAllowedChildHeight(View child) {
+        if (child instanceof ExpandableView) {
             ExpandableView expandableView = (ExpandableView) child;
             return expandableView.getIntrinsicHeight();
         }
@@ -744,7 +723,7 @@
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
             StackViewState childViewState = resultState.getViewStateForView(child);
-            int childHeight = getMaxAllowedChildHeight(child, ambientState);
+            int childHeight = getMaxAllowedChildHeight(child);
             float yPositionInScrollViewAfterElement = yPositionInScrollView
                     + childHeight
                     + mPaddingBetweenElements;
@@ -865,13 +844,6 @@
                 // current height or the end value of the animation.
                 mFirstChildMaxHeight = StackStateAnimator.getFinalActualHeight(
                         mFirstChildWhileExpanding);
-                if (mFirstChildWhileExpanding instanceof ExpandableNotificationRow) {
-                    ExpandableNotificationRow row =
-                            (ExpandableNotificationRow) mFirstChildWhileExpanding;
-                    if (row.isHeadsUp()) {
-                        mFirstChildMaxHeight += mFirstChildMinHeight - row.getHeadsUpHeight();
-                    }
-                }
             } else {
                 updateFirstChildMaxSizeToMaxHeight();
             }
@@ -893,7 +865,7 @@
                                 int oldBottom) {
                             if (mFirstChildWhileExpanding != null) {
                                 mFirstChildMaxHeight = getMaxAllowedChildHeight(
-                                        mFirstChildWhileExpanding, null);
+                                        mFirstChildWhileExpanding);
                             } else {
                                 mFirstChildMaxHeight = 0;
                             }
@@ -901,7 +873,7 @@
                         }
                     });
         } else {
-            mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding, null);
+            mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index b4ab48a..9d5e072 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -43,17 +43,14 @@
     public static final int ANIMATION_DURATION_STANDARD = 360;
     public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
     public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
-    public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360;
     public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
     public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650;
     public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
     public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
-    public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
     public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
     public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
     public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
     public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
-    public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3;
     public static final int ANIMATION_DELAY_HEADS_UP = 120;
 
     private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
@@ -263,8 +260,7 @@
                     delay + duration);
         } else if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            row.startChildAnimation(finalState, this, child == mChildExpandingView, delay,
-                    duration);
+            row.startChildAnimation(finalState, this, delay, duration);
         }
     }
 
@@ -898,7 +894,7 @@
                     event.animationType == NotificationStackScrollLayout
                             .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
                 mHeadsUpDisappearChildren.add(changingView);
-                if (mHostLayout.indexOfChild(changingView) == -1) {
+                if (changingView.getParent() == null) {
                     // This notification was actually removed, so we need to add it to the overlay
                     mHostLayout.getOverlay().add(changingView);
                     mTmpState.initFrom(changingView);
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index 3d91d62..bed1e9e 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -84,9 +84,8 @@
                 return;
             }
             if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
-            mState = STATE_PIP_OVERLAY;
             mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
-            launchPipOverlayActivity();
+            showPipOverlay(false);
         }
     };
     private final Runnable mOnTaskStackChanged = new Runnable() {
@@ -208,17 +207,17 @@
      * stack to the default PIP bound {@link com.android.internal.R.string
      * .config_defaultPictureInPictureBounds}.
      */
-    public void showPipOverlay() {
+    public void showPipOverlay(boolean resizeStack) {
         if (DEBUG) Log.d(TAG, "showPipOverlay()");
-        try {
-            mActivityManager.resizeStack(PINNED_STACK_ID, mPipBound, false);
-        } catch (Exception e) {
-            Log.e(TAG, "resizeStack failed", e);
-            closePip();
-            return;
-        }
         mState = STATE_PIP_OVERLAY;
-        launchPipOverlayActivity();
+        Intent intent = new Intent(mContext, PipOverlayActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchStackId(PINNED_STACK_ID);
+        if (resizeStack) {
+            options.setLaunchBounds(mPipBound);
+        }
+        mContext.startActivity(intent, options.toBundle());
     }
 
     /**
@@ -228,13 +227,6 @@
      */
     public void showPipMenu() {
         if (DEBUG) Log.d(TAG, "showPipMenu()");
-        try {
-            mActivityManager.resizeStack(PINNED_STACK_ID, mMenuModePipBound, false);
-        } catch (Exception e) {
-            Log.e(TAG, "resizeStack failed", e);
-            closePip();
-            return;
-        }
         mState = STATE_PIP_MENU;
         for (int i = mListeners.size() - 1; i >= 0; --i) {
             mListeners.get(i).onShowPipMenu();
@@ -243,6 +235,7 @@
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchStackId(PINNED_STACK_ID);
+        options.setLaunchBounds(mMenuModePipBound);
         mContext.startActivity(intent, options.toBundle());
     }
 
@@ -260,14 +253,6 @@
         mListeners.remove(listener);
     }
 
-    private void launchPipOverlayActivity() {
-        Intent intent = new Intent(mContext, PipOverlayActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchStackId(PINNED_STACK_ID);
-        mContext.startActivity(intent, options.toBundle());
-    }
-
     private boolean hasPipTasks() {
         try {
             StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
index 1248321..97c70ed 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -55,7 +55,7 @@
         findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                mPipManager.showPipOverlay();
+                mPipManager.showPipOverlay(true);
                 finish();
             }
         });
@@ -69,7 +69,7 @@
 
     @Override
     public void onBackPressed() {
-        mPipManager.showPipOverlay();
+        mPipManager.showPipOverlay(true);
         finish();
     }
 
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index 7eb8005..3668ddd 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -751,6 +751,14 @@
         rsnScriptReduce(mContext, id, slot, ain, aout, limits);
     }
 
+    native void rsnScriptReduceNew(long con, long id, int slot, long[] ains,
+                                   long aout, int[] limits);
+    synchronized void nScriptReduceNew(long id, int slot, long ains[], long aout,
+                                       int[] limits) {
+        validate();
+        rsnScriptReduceNew(mContext, id, slot, ains, aout, limits);
+    }
+
     native void rsnScriptInvokeV(long con, long id, int slot, byte[] params);
     synchronized void nScriptInvokeV(long id, int slot, byte[] params) {
         validate();
diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java
index ed4c6c7..84f980d 100644
--- a/rs/java/android/renderscript/Script.java
+++ b/rs/java/android/renderscript/Script.java
@@ -284,7 +284,7 @@
     }
 
     /**
-     * Only intended for use by generated reflected code.
+     * Only intended for use by generated reflected code.  (Simple reduction)
      *
      * @hide
      */
@@ -312,6 +312,46 @@
         mRS.nScriptReduce(getID(mRS), slot, in_id, out_id, limits);
     }
 
+    /**
+     * Only intended for use by generated reflected code.  (General reduction)
+     *
+     * @hide
+     */
+    protected void reduce(int slot, Allocation[] ains, Allocation aout, LaunchOptions sc) {
+        mRS.validate();
+        if (ains == null || ains.length < 1) {
+            throw new RSIllegalArgumentException(
+                "At least one input is required.");
+        }
+        if (aout == null) {
+            throw new RSIllegalArgumentException(
+                "aout is required to be non-null.");
+        }
+        for (Allocation ain : ains) {
+            mRS.validateObject(ain);
+        }
+
+        long[] in_ids = new long[ains.length];
+        for (int index = 0; index < ains.length; ++index) {
+            in_ids[index] = ains[index].getID(mRS);
+        }
+        long out_id = aout.getID(mRS);
+
+        int[] limits = null;
+        if (sc != null) {
+            limits = new int[6];
+
+            limits[0] = sc.xstart;
+            limits[1] = sc.xend;
+            limits[2] = sc.ystart;
+            limits[3] = sc.yend;
+            limits[4] = sc.zstart;
+            limits[5] = sc.zend;
+        }
+
+        mRS.nScriptReduceNew(getID(mRS), slot, in_ids, out_id, limits);
+    }
+
     long[] mInIdsBuffer;
 
     Script(long id, RenderScript rs) {
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 113241d..3954070 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -1988,7 +1988,6 @@
 
         if (sizeof(RsAllocation) == sizeof(jlong)) {
             in_allocs = (RsAllocation*)in_ptr;
-
         } else {
             // Convert from 64-bit jlong types to the native pointer type.
 
@@ -2127,6 +2126,101 @@
     }
 }
 
+static void
+nScriptReduceNew(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot,
+                 jlongArray ains, jlong aout, jintArray limits)
+{
+    if (kLogApi) {
+        ALOGD("nScriptReduceNew, con(%p), s(%p), slot(%i) ains(%p) aout(%" PRId64 ")", (RsContext)con, (void *)script, slot, ains, aout);
+    }
+
+    if (ains == nullptr) {
+        ALOGE("At least one input required.");
+        // TODO (b/20758983): Report back to Java and throw an exception
+        return;
+    }
+    jint in_len = _env->GetArrayLength(ains);
+    if (in_len > (jint)RS_KERNEL_MAX_ARGUMENTS) {
+        ALOGE("Too many arguments in kernel launch.");
+        // TODO (b/20758983): Report back to Java and throw an exception
+        return;
+    }
+
+    jlong *in_ptr = _env->GetLongArrayElements(ains, nullptr);
+    if (in_ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        // TODO (b/20758983): Report back to Java and throw an exception
+        return;
+    }
+
+    RsAllocation *in_allocs = nullptr;
+    if (sizeof(RsAllocation) == sizeof(jlong)) {
+        in_allocs = (RsAllocation*)in_ptr;
+    } else {
+        // Convert from 64-bit jlong types to the native pointer type.
+
+        in_allocs = (RsAllocation*)alloca(in_len * sizeof(RsAllocation));
+        if (in_allocs == nullptr) {
+            ALOGE("Failed launching kernel for lack of memory.");
+            // TODO (b/20758983): Report back to Java and throw an exception
+            _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+            return;
+        }
+
+        for (int index = in_len; --index >= 0;) {
+            in_allocs[index] = (RsAllocation)in_ptr[index];
+        }
+    }
+
+    RsScriptCall sc, *sca = nullptr;
+    uint32_t sc_size = 0;
+
+    jint  limit_len = 0;
+    jint *limit_ptr = nullptr;
+
+    if (limits != nullptr) {
+        limit_len = _env->GetArrayLength(limits);
+        limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+        if (limit_ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            // TODO (b/20758983): Report back to Java and throw an exception
+            return;
+        }
+
+        assert(limit_len == 6);
+        UNUSED(limit_len);  // As the assert might not be compiled.
+
+        sc.xStart     = limit_ptr[0];
+        sc.xEnd       = limit_ptr[1];
+        sc.yStart     = limit_ptr[2];
+        sc.yEnd       = limit_ptr[3];
+        sc.zStart     = limit_ptr[4];
+        sc.zEnd       = limit_ptr[5];
+        sc.strategy   = RS_FOR_EACH_STRATEGY_DONT_CARE;
+        sc.arrayStart = 0;
+        sc.arrayEnd = 0;
+        sc.array2Start = 0;
+        sc.array2End = 0;
+        sc.array3Start = 0;
+        sc.array3End = 0;
+        sc.array4Start = 0;
+        sc.array4End = 0;
+
+        sca = &sc;
+        sc_size = sizeof(sc);
+    }
+
+    rsScriptReduceNew((RsContext)con, (RsScript)script, slot,
+                      in_allocs, in_len, (RsAllocation)aout,
+                      sca, sc_size);
+
+    _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+
+    if (limits != nullptr) {
+        _env->ReleaseIntArrayElements(limits, limit_ptr, JNI_ABORT);
+    }
+}
+
 // -----------------------------------
 
 static jlong
@@ -2755,6 +2849,7 @@
 
 {"rsnScriptForEach",                 "(JJI[JJ[B[I)V",                         (void*)nScriptForEach },
 {"rsnScriptReduce",                  "(JJIJJ[I)V",                            (void*)nScriptReduce },
+{"rsnScriptReduceNew",               "(JJI[JJ[I)V",                           (void*)nScriptReduceNew },
 
 {"rsnScriptSetVarI",                 "(JJII)V",                               (void*)nScriptSetVarI },
 {"rsnScriptGetVarI",                 "(JJI)I",                                (void*)nScriptGetVarI },
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 234c94e..d63dd0c 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -676,8 +676,13 @@
         return true;
     }
 
+    // Checks if the app is in a stopped state, that means it won't receive broadcasts.
+    private static boolean appIsStopped(ApplicationInfo app) {
+        return ((app.flags & ApplicationInfo.FLAG_STOPPED) != 0);
+    }
+
     /* does *not* check overall backup eligibility policy! */
-    public static boolean appGetsFullBackup(PackageInfo pkg) {
+    private static boolean appGetsFullBackup(PackageInfo pkg) {
         if (pkg.applicationInfo.backupAgentName != null) {
             // If it has an agent, it gets full backups only if it says so
             return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0;
@@ -2758,7 +2763,7 @@
                     return;
                 }
 
-                if ((mCurrentPackage.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+                if (appIsStopped(mCurrentPackage.applicationInfo)) {
                     // The app has been force-stopped or cleared or just installed,
                     // and not yet launched out of that state, so just as it won't
                     // receive broadcasts, we won't run it for backup.
@@ -4179,28 +4184,18 @@
                 try {
                     PackageInfo info = mPackageManager.getPackageInfo(pkg,
                             PackageManager.GET_SIGNATURES);
-                    if ((info.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0
-                            || pkg.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
+                    if (!appIsEligibleForBackup(info.applicationInfo)) {
                         // Cull any packages that have indicated that backups are not permitted,
+                        // that run as system-domain uids but do not define their own backup agents,
                         // as well as any explicit mention of the 'special' shared-storage agent
                         // package (we handle that one at the end).
                         if (MORE_DEBUG) {
-                            Slog.d(TAG, "Ignoring opted-out package " + pkg);
+                            Slog.d(TAG, "Ignoring not eligible package " + pkg);
                         }
                         sendBackupOnResult(mBackupObserver, pkg,
-                                BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+                            BackupManager.ERROR_BACKUP_NOT_ALLOWED);
                         continue;
-                    } else if ((info.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
-                            && (info.applicationInfo.backupAgentName == null)) {
-                        // Cull any packages that run as system-domain uids but do not define their
-                        // own backup agents
-                        if (MORE_DEBUG) {
-                            Slog.d(TAG, "Ignoring non-agent system package " + pkg);
-                        }
-                        sendBackupOnResult(mBackupObserver, pkg,
-                                BackupManager.ERROR_BACKUP_NOT_ALLOWED);
-                        continue;
-                    } else if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+                    } else if (appIsStopped(info.applicationInfo)) {
                         // Cull any packages in the 'stopped' state: they've either just been
                         // installed or have explicitly been force-stopped by the user.  In both
                         // cases we do not want to launch them for backup.
@@ -9538,6 +9533,32 @@
         }
     }
 
+    public boolean isAppEligibleForBackup(String packageName) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+                "isAppEligibleForBackup");
+        try {
+            PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
+                    PackageManager.GET_SIGNATURES);
+            if (!appIsEligibleForBackup(packageInfo.applicationInfo) ||
+                    appIsStopped(packageInfo.applicationInfo)) {
+                return false;
+            }
+            IBackupTransport transport = getTransport(mCurrentTransport);
+            if (transport != null) {
+                try {
+                    return transport.isAppEligibleForBackup(packageInfo,
+                        appGetsFullBackup(packageInfo));
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to contact transport");
+                }
+            }
+            // If transport is not present we couldn't tell that the package is not eligible.
+            return true;
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
     // ----- Restore session -----
 
     class ActiveRestoreSession extends IRestoreSession.Stub {
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 505a1a5..bbf881b 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -326,6 +326,12 @@
     }
 
     @Override
+    public boolean isAppEligibleForBackup(String packageName) {
+        BackupManagerService svc = mService;
+        return (svc != null) ? svc.isAppEligibleForBackup(packageName) : false;
+    }
+
+    @Override
     public int requestBackup(String[] packages, IBackupObserver observer) throws RemoteException {
         BackupManagerService svc = mService;
         return (svc != null) ? svc.requestBackup(packages, observer) : null;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index df20704..9927fd6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -171,6 +171,7 @@
     private static final boolean VDBG = false;
 
     private static final boolean LOGD_RULES = false;
+    private static final boolean LOGD_BLOCKED_NETWORKINFO = true;
 
     // TODO: create better separation between radio types and network types
 
@@ -955,6 +956,21 @@
         }
     }
 
+    private void maybeLogBlockedNetworkInfo(NetworkInfo ni, int uid) {
+        if (ni == null || !LOGD_BLOCKED_NETWORKINFO) return;
+        boolean removed = false;
+        boolean added = false;
+        synchronized (mBlockedAppUids) {
+            if (ni.getDetailedState() == DetailedState.BLOCKED && mBlockedAppUids.add(uid)) {
+                added = true;
+            } else if (ni.isConnected() && mBlockedAppUids.remove(uid)) {
+                removed = true;
+            }
+        }
+        if (added) log("Returning blocked NetworkInfo to uid=" + uid);
+        else if (removed) log("Returning unblocked NetworkInfo to uid=" + uid);
+    }
+
     /**
      * Return a filtered {@link NetworkInfo}, potentially marked
      * {@link DetailedState#BLOCKED} based on
@@ -965,10 +981,6 @@
             // network is blocked; clone and override state
             info = new NetworkInfo(info);
             info.setDetailedState(DetailedState.BLOCKED, null, null);
-            if (VDBG) {
-                log("returning Blocked NetworkInfo for ifname=" +
-                        lp.getInterfaceName() + ", uid=" + uid);
-            }
         }
         if (info != null && mLockdownTracker != null) {
             info = mLockdownTracker.augmentNetworkInfo(info);
@@ -989,7 +1001,9 @@
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
         NetworkState state = getUnfilteredActiveNetworkState(uid);
-        return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
+        NetworkInfo ni = getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
+        maybeLogBlockedNetworkInfo(ni, uid);
+        return ni;
     }
 
     @Override
@@ -3974,6 +3988,9 @@
     private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
             new HashMap<Messenger, NetworkAgentInfo>();
 
+    @GuardedBy("mBlockedAppUids")
+    private final HashSet<Integer> mBlockedAppUids = new HashSet();
+
     // Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated.
     private final NetworkRequest mDefaultRequest;
 
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 798a04a..4a123df 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -742,6 +742,31 @@
         }
     }
 
+    public static final class Lifecycle extends SystemService {
+        private InputMethodManagerService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+            mService = new InputMethodManagerService(context);
+        }
+
+        @Override
+        public void onStart() {
+            LocalServices.addService(InputMethodManagerInternal.class,
+                    new LocalServiceImpl(mService.mHandler));
+            publishBinderService(Context.INPUT_METHOD_SERVICE, mService);
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+                StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager
+                        .getService(Context.STATUS_BAR_SERVICE);
+                mService.systemRunning(statusBarService);
+            }
+        }
+    }
+
     public InputMethodManagerService(Context context) {
         mIPackageManager = AppGlobals.getPackageManager();
         mContext = context;
@@ -894,7 +919,6 @@
                         }
                     }
                 }, filter);
-        LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl(mHandler));
     }
 
     private void resetDefaultImeLocked(Context context) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e3f4999..10b4ba7 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -4243,6 +4243,14 @@
 
     private int getTaskConfigurationChanges(ActivityRecord record, Configuration taskConfig,
             Configuration oldTaskOverride) {
+
+        // If we went from full-screen to non-full-screen, make sure to use the correct
+        // configuration task diff, so the diff stays as small as possible.
+        if (Configuration.EMPTY.equals(oldTaskOverride)
+                && !Configuration.EMPTY.equals(taskConfig)) {
+            oldTaskOverride = record.task.extractOverrideConfig(record.configuration);
+        }
+
         // Determine what has changed.  May be nothing, if this is a config
         // that has come back from the app after going idle.  In that case
         // we just want to leave the official config object now in the
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d9dd77d..1fc674b 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2251,7 +2251,10 @@
             // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
             // window), we need to clear the replace window settings. Otherwise, we schedule a
             // timeout to remove the old window if the replacing window is not coming in time.
-            mWindowManager.scheduleClearReplacingWindowIfNeeded(topActivity.appToken, !kept);
+            // In case of the pinned stack we don't resize the task during the move, but we will
+            // resize the stack soon after so we want to retain the replacing window.
+            mWindowManager.scheduleClearReplacingWindowIfNeeded(topActivity.appToken,
+                    !kept || stackId == PINNED_STACK_ID);
         }
 
         // The task might have already been running and its visibility needs to be synchronized with
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index cc86a62..4ce8b2f 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1291,24 +1291,7 @@
             if (stack == null || StackId.persistTaskBounds(stack.mStackId)) {
                 mLastNonFullscreenBounds = mBounds;
             }
-
-            final Configuration serviceConfig = mService.mConfiguration;
-            mOverrideConfig = new Configuration(Configuration.EMPTY);
-            // TODO(multidisplay): Update Dp to that of display stack is on.
-            final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
-            mOverrideConfig.screenWidthDp =
-                    Math.min((int)(mBounds.width() / density), serviceConfig.screenWidthDp);
-            mOverrideConfig.screenHeightDp =
-                    Math.min((int)(mBounds.height() / density), serviceConfig.screenHeightDp);
-            mOverrideConfig.smallestScreenWidthDp =
-                    Math.min(mOverrideConfig.screenWidthDp, mOverrideConfig.screenHeightDp);
-            mOverrideConfig.orientation =
-                    (mOverrideConfig.screenWidthDp <= mOverrideConfig.screenHeightDp)
-                            ? Configuration.ORIENTATION_PORTRAIT
-                            : Configuration.ORIENTATION_LANDSCAPE;
-            final int sl = Configuration.resetScreenLayout(serviceConfig.screenLayout);
-            mOverrideConfig.screenLayout = Configuration.reduceScreenLayout(
-                    sl, mOverrideConfig.screenWidthDp, mOverrideConfig.screenHeightDp);
+            mOverrideConfig = calculateOverrideConfig(mBounds);
         }
 
         if (mFullscreen != oldFullscreen) {
@@ -1318,6 +1301,41 @@
         return !mOverrideConfig.equals(oldConfig) ? mOverrideConfig : null;
     }
 
+    Configuration calculateOverrideConfig(Rect bounds) {
+        final Configuration serviceConfig = mService.mConfiguration;
+        final Configuration config = new Configuration(Configuration.EMPTY);
+        // TODO(multidisplay): Update Dp to that of display stack is on.
+        final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        config.screenWidthDp =
+                Math.min((int)(bounds.width() / density), serviceConfig.screenWidthDp);
+        config.screenHeightDp =
+                Math.min((int)(bounds.height() / density), serviceConfig.screenHeightDp);
+        config.smallestScreenWidthDp =
+                Math.min(config.screenWidthDp, config.screenHeightDp);
+        config.orientation = (config.screenWidthDp <= config.screenHeightDp)
+                ? Configuration.ORIENTATION_PORTRAIT
+                : Configuration.ORIENTATION_LANDSCAPE;
+        final int sl = Configuration.resetScreenLayout(serviceConfig.screenLayout);
+        config.screenLayout = Configuration.reduceScreenLayout(
+                sl, config.screenWidthDp, config.screenHeightDp);
+        return config;
+    }
+
+    /**
+     * Using the existing configuration {@param config}, creates a new task override config such
+     * that all the fields that are usually set in an override config are set to the ones in
+     * {@param config}.
+     */
+    Configuration extractOverrideConfig(Configuration config) {
+        final Configuration extracted = new Configuration(Configuration.EMPTY);
+        extracted.screenWidthDp = config.screenWidthDp;
+        extracted.screenHeightDp = config.screenHeightDp;
+        extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
+        extracted.orientation = config.orientation;
+        extracted.screenLayout = config.screenLayout;
+        return extracted;
+    }
+
     Rect updateOverrideConfigurationFromLaunchBounds() {
         final Rect bounds = validateBounds(getLaunchBounds());
         updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index ee91b63..573afd6 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.view.Display;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.R;
 import com.android.internal.util.XmlUtils;
@@ -67,6 +68,8 @@
 import android.os.MessageQueue;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -97,6 +100,7 @@
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -157,8 +161,7 @@
     private final ArrayList<InputDevice>
             mTempFullKeyboards = new ArrayList<InputDevice>(); // handler thread only
     private boolean mKeyboardLayoutNotificationShown;
-    private PendingIntent mKeyboardLayoutIntent;
-    private Toast mSwitchedKeyboardLayoutToast;
+    private InputMethodSubtypeHandle mCurrentImeHandle;
 
     // State for vibrator tokens.
     private Object mVibratorLock = new Object();
@@ -1297,6 +1300,82 @@
     }
 
     @Override // Binder call
+    @Nullable
+    public KeyboardLayout getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            InputMethodInfo imeInfo, InputMethodSubtype imeSubtype) {
+        InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(imeInfo, imeSubtype);
+        String key = getLayoutDescriptor(identifier);
+        final String keyboardLayoutDescriptor;
+        synchronized (mDataStore) {
+            keyboardLayoutDescriptor = mDataStore.getKeyboardLayout(key, handle);
+        }
+
+        if (keyboardLayoutDescriptor == null) {
+            return null;
+        }
+
+        final KeyboardLayout[] result = new KeyboardLayout[1];
+        visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    int keyboardLayoutResId, KeyboardLayout layout) {
+                result[0] = layout;
+            }
+        });
+        if (result[0] == null) {
+            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
+                    + keyboardLayoutDescriptor + "'.");
+        }
+        return result[0];
+    }
+
+    @Override
+    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            InputMethodInfo imeInfo, InputMethodSubtype imeSubtype,
+            String keyboardLayoutDescriptor) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                "setKeyboardLayoutForInputDevice()")) {
+            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+        if (imeInfo == null || imeSubtype == null) {
+            throw new IllegalArgumentException("imeInfo and imeSubtype must not be null");
+        }
+        InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(imeInfo, imeSubtype);
+        setKeyboardLayoutForInputDeviceInner(identifier, handle, keyboardLayoutDescriptor);
+    }
+
+    private void setKeyboardLayoutForInputDeviceInner(InputDeviceIdentifier identifier,
+            InputMethodSubtypeHandle imeHandle, String keyboardLayoutDescriptor) {
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            try {
+                if (mDataStore.setKeyboardLayout(key, imeHandle, keyboardLayoutDescriptor)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Set keyboard layout " + keyboardLayoutDescriptor +
+                                " for subtype " + imeHandle + " and device " + identifier +
+                                " using key " + key);
+                    }
+                    if (imeHandle.equals(mCurrentImeHandle)) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Layout for current subtype changed, switching layout");
+                        }
+                        SomeArgs args = SomeArgs.obtain();
+                        args.arg1 = identifier;
+                        args.arg2 = imeHandle;
+                        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, args).sendToTarget();
+                    }
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    @Override // Binder call
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
         if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
@@ -1315,8 +1394,7 @@
                     oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
                 }
                 if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
-                        && !Objects.equal(oldLayout,
-                                mDataStore.getCurrentKeyboardLayout(key))) {
+                        && !Objects.equal(oldLayout, mDataStore.getCurrentKeyboardLayout(key))) {
                     mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
                 }
             } finally {
@@ -1366,45 +1444,44 @@
             Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
                     + " ime=" + inputMethodInfo + " subtype=" + subtype);
         }
-    }
-
-    public void switchKeyboardLayout(int deviceId, int direction) {
-        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
+        if (inputMethodInfo == null) {
+            Slog.d(TAG, "No InputMethod is running, ignoring change");
+            return;
+        }
+        if (subtype != null && !"keyboard".equals(subtype.getMode())) {
+            Slog.d(TAG, "InputMethodSubtype changed to non-keyboard subtype, ignoring change");
+            return;
+        }
+        InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(inputMethodInfo, subtype);
+        if (!handle.equals(mCurrentImeHandle)) {
+            mCurrentImeHandle = handle;
+            handleSwitchKeyboardLayout(null, handle);
+        }
     }
 
     // Must be called on handler.
-    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
-        final InputDevice device = getInputDevice(deviceId);
-        if (device != null) {
-            final boolean changed;
-            final String keyboardLayoutDescriptor;
-
-            String key = getLayoutDescriptor(device.getIdentifier());
-            synchronized (mDataStore) {
-                try {
-                    changed = mDataStore.switchKeyboardLayout(key, direction);
-                    keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
-                            key);
-                } finally {
-                    mDataStore.saveIfNeeded();
+    private void handleSwitchKeyboardLayout(@Nullable InputDeviceIdentifier identifier,
+            InputMethodSubtypeHandle handle) {
+        synchronized (mInputDevicesLock) {
+            for (InputDevice device : mInputDevices) {
+                if (identifier != null && !device.getIdentifier().equals(identifier) ||
+                        !device.isFullKeyboard()) {
+                    continue;
                 }
-            }
-
-            if (changed) {
-                if (mSwitchedKeyboardLayoutToast != null) {
-                    mSwitchedKeyboardLayoutToast.cancel();
-                    mSwitchedKeyboardLayoutToast = null;
-                }
-                if (keyboardLayoutDescriptor != null) {
-                    KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
-                    if (keyboardLayout != null) {
-                        mSwitchedKeyboardLayoutToast = Toast.makeText(
-                                mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
-                        mSwitchedKeyboardLayoutToast.show();
+                String key = getLayoutDescriptor(device.getIdentifier());
+                boolean changed = false;
+                synchronized (mDataStore) {
+                    try {
+                        if (mDataStore.switchKeyboardLayout(key, handle)) {
+                            changed = true;
+                        }
+                    } finally {
+                        mDataStore.saveIfNeeded();
                     }
                 }
-
-                reloadKeyboardLayouts();
+                if (changed) {
+                    reloadKeyboardLayouts();
+                }
             }
         }
     }
@@ -1616,7 +1693,7 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
             pw.println("Permission Denial: can't dump InputManager from from pid="
@@ -1630,8 +1707,48 @@
         if (dumpStr != null) {
             pw.println(dumpStr);
         }
+        pw.println("  Keyboard Layouts:");
+        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    int keyboardLayoutResId, KeyboardLayout layout) {
+                pw.println("    \"" + layout + "\": " + layout.getDescriptor());
+            }
+        });
+        pw.println();
+        synchronized(mDataStore) {
+            mDataStore.dump(pw, "  ");
+        }
     }
 
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+        (new Shell()).exec(this, in, out, err, args, resultReceiver);
+    }
+
+    public int onShellCommand(Shell shell, String cmd) {
+        if (TextUtils.isEmpty(cmd)) {
+            shell.onHelp();
+            return 1;
+        }
+        if (cmd.equals("setlayout")) {
+            if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                    "onShellCommand()")) {
+                throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+            }
+            InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(
+                    shell.getNextArgRequired(), Integer.parseInt(shell.getNextArgRequired()));
+            String descriptor = shell.getNextArgRequired();
+            int vid = Integer.decode(shell.getNextArgRequired());
+            int pid = Integer.decode(shell.getNextArgRequired());
+            InputDeviceIdentifier id = new InputDeviceIdentifier(descriptor, vid, pid);
+            setKeyboardLayoutForInputDeviceInner(id, handle, shell.getNextArgRequired());
+        }
+        return 0;
+    }
+
+
     private boolean checkCallingPermission(String permission, String func) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == Process.myPid()) {
@@ -1937,9 +2054,12 @@
                 case MSG_DELIVER_INPUT_DEVICES_CHANGED:
                     deliverInputDevicesChanged((InputDevice[])msg.obj);
                     break;
-                case MSG_SWITCH_KEYBOARD_LAYOUT:
-                    handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+                case MSG_SWITCH_KEYBOARD_LAYOUT: {
+                    SomeArgs args = (SomeArgs)msg.obj;
+                    handleSwitchKeyboardLayout((InputDeviceIdentifier)args.arg1,
+                            (InputMethodSubtypeHandle)args.arg2);
                     break;
+                }
                 case MSG_RELOAD_KEYBOARD_LAYOUTS:
                     reloadKeyboardLayouts();
                     break;
@@ -2106,6 +2226,25 @@
         }
     }
 
+    private class Shell extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            return onShellCommand(this, cmd);
+        }
+
+        @Override
+        public void onHelp() {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.println("Input manager commands:");
+            pw.println("  help");
+            pw.println("    Print this help text.");
+            pw.println("");
+            pw.println("  setlayout IME_ID IME_SUPTYPE_HASH_CODE"
+                    + " DEVICE_DESCRIPTOR VENDOR_ID PRODUCT_ID KEYBOARD_DESCRIPTOR");
+            pw.println("    Sets a keyboard layout for a given IME subtype and input device pair");
+        }
+    }
+
     private final class LocalService extends InputManagerInternal {
         @Override
         public void setDisplayViewports(
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index f6d7244..e97aca8 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -16,6 +16,7 @@
 
 package com.android.server.input;
 
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
@@ -26,6 +27,8 @@
 
 import android.view.Surface;
 import android.hardware.input.TouchCalibration;
+import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
@@ -37,10 +40,13 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -131,9 +137,26 @@
         }
         return state.getKeyboardLayouts();
     }
+    public String getKeyboardLayout(String inputDeviceDescriptor,
+            InputMethodSubtypeHandle imeHandle) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        if (state == null) {
+            return null;
+        }
+        return state.getKeyboardLayout(imeHandle);
+    }
 
-    public boolean addKeyboardLayout(String inputDeviceDescriptor,
-            String keyboardLayoutDescriptor) {
+    public boolean setKeyboardLayout(String inputDeviceDescriptor,
+            InputMethodSubtypeHandle imeHandle, String keyboardLayoutDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        if (state.setKeyboardLayout(imeHandle, keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean addKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
         if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
             setDirty();
@@ -152,9 +175,10 @@
         return false;
     }
 
-    public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
+    public boolean switchKeyboardLayout(String inputDeviceDescriptor,
+            InputMethodSubtypeHandle imeHandle) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
-        if (state != null && state.switchKeyboardLayout(direction)) {
+        if (state != null && state.switchKeyboardLayout(imeHandle)) {
             setDirty();
             return true;
         }
@@ -301,13 +325,26 @@
         serializer.endDocument();
     }
 
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "PersistentDataStore");
+        pw.println(prefix + "  mLoaded=" + mLoaded);
+        pw.println(prefix + "  mDirty=" + mDirty);
+        pw.println(prefix + "  InputDeviceStates:");
+        int i = 0;
+        for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
+            pw.println(prefix + "    " + i++ + ": " + entry.getKey());
+            entry.getValue().dump(pw, prefix + "      ");
+        }
+    }
+
     private static final class InputDeviceState {
         private static final String[] CALIBRATION_NAME = { "x_scale",
                 "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
 
         private TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
         private String mCurrentKeyboardLayout;
-        private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+        private List<String> mUnassociatedKeyboardLayouts = new ArrayList<>();
+        private ArrayMap<InputMethodSubtypeHandle, String> mKeyboardLayouts = new ArrayMap<>();
 
         public TouchCalibration getTouchCalibration(int surfaceRotation) {
             try {
@@ -345,18 +382,34 @@
         }
 
         public String[] getKeyboardLayouts() {
-            if (mKeyboardLayouts.isEmpty()) {
+            if (mUnassociatedKeyboardLayouts.isEmpty()) {
                 return (String[])ArrayUtils.emptyArray(String.class);
             }
-            return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
+            return mUnassociatedKeyboardLayouts.toArray(
+                    new String[mUnassociatedKeyboardLayouts.size()]);
+        }
+
+        public String getKeyboardLayout(InputMethodSubtypeHandle handle) {
+            return mKeyboardLayouts.get(handle);
+        }
+
+        public boolean setKeyboardLayout(InputMethodSubtypeHandle imeHandle,
+                String keyboardLayout) {
+            String existingLayout = mKeyboardLayouts.get(imeHandle);
+            if (TextUtils.equals(existingLayout, keyboardLayout)) {
+                return false;
+            }
+            mKeyboardLayouts.put(imeHandle, keyboardLayout);
+            return true;
         }
 
         public boolean addKeyboardLayout(String keyboardLayout) {
-            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+            int index = Collections.binarySearch(
+                    mUnassociatedKeyboardLayouts, keyboardLayout);
             if (index >= 0) {
                 return false;
             }
-            mKeyboardLayouts.add(-index - 1, keyboardLayout);
+            mUnassociatedKeyboardLayouts.add(-index - 1, keyboardLayout);
             if (mCurrentKeyboardLayout == null) {
                 mCurrentKeyboardLayout = keyboardLayout;
             }
@@ -364,11 +417,11 @@
         }
 
         public boolean removeKeyboardLayout(String keyboardLayout) {
-            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+            int index = Collections.binarySearch(mUnassociatedKeyboardLayouts, keyboardLayout);
             if (index < 0) {
                 return false;
             }
-            mKeyboardLayouts.remove(index);
+            mUnassociatedKeyboardLayouts.remove(index);
             updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
             return true;
         }
@@ -376,41 +429,34 @@
         private void updateCurrentKeyboardLayoutIfRemoved(
                 String removedKeyboardLayout, int removedIndex) {
             if (Objects.equal(mCurrentKeyboardLayout, removedKeyboardLayout)) {
-                if (!mKeyboardLayouts.isEmpty()) {
+                if (!mUnassociatedKeyboardLayouts.isEmpty()) {
                     int index = removedIndex;
-                    if (index == mKeyboardLayouts.size()) {
+                    if (index == mUnassociatedKeyboardLayouts.size()) {
                         index = 0;
                     }
-                    mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+                    mCurrentKeyboardLayout = mUnassociatedKeyboardLayouts.get(index);
                 } else {
                     mCurrentKeyboardLayout = null;
                 }
             }
         }
 
-        public boolean switchKeyboardLayout(int direction) {
-            final int size = mKeyboardLayouts.size();
-            if (size < 2) {
-                return false;
+        public boolean switchKeyboardLayout(InputMethodSubtypeHandle imeHandle) {
+            final String layout = mKeyboardLayouts.get(imeHandle);
+            if (layout != null && !TextUtils.equals(mCurrentKeyboardLayout, layout)) {
+                mCurrentKeyboardLayout = layout;
+                return true;
             }
-            int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
-            assert index >= 0;
-            if (direction > 0) {
-                index = (index + 1) % size;
-            } else {
-                index = (index + size - 1) % size;
-            }
-            mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
-            return true;
+            return false;
         }
 
         public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
             boolean changed = false;
-            for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
-                String keyboardLayout = mKeyboardLayouts.get(i);
+            for (int i = mUnassociatedKeyboardLayouts.size(); i-- > 0; ) {
+                String keyboardLayout = mUnassociatedKeyboardLayouts.get(i);
                 if (!availableKeyboardLayouts.contains(keyboardLayout)) {
                     Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
-                    mKeyboardLayouts.remove(i);
+                    mUnassociatedKeyboardLayouts.remove(i);
                     updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
                     changed = true;
                 }
@@ -428,13 +474,8 @@
                         throw new XmlPullParserException(
                                 "Missing descriptor attribute on keyboard-layout.");
                     }
-                    String current = parser.getAttributeValue(null, "current");
-                    if (mKeyboardLayouts.contains(descriptor)) {
-                        throw new XmlPullParserException(
-                                "Found duplicate keyboard layout.");
-                    }
 
-                    mKeyboardLayouts.add(descriptor);
+                    String current = parser.getAttributeValue(null, "current");
                     if (current != null && current.equals("true")) {
                         if (mCurrentKeyboardLayout != null) {
                             throw new XmlPullParserException(
@@ -442,6 +483,32 @@
                         }
                         mCurrentKeyboardLayout = descriptor;
                     }
+
+                    String inputMethodId = parser.getAttributeValue(null, "input-method-id");
+                    String inputMethodSubtypeId =
+                        parser.getAttributeValue(null, "input-method-subtype-id");
+                    if (inputMethodId == null && inputMethodSubtypeId != null
+                            || inputMethodId != null && inputMethodSubtypeId == null) {
+                        throw new XmlPullParserException(
+                                "Found an incomplete input method description");
+                    }
+
+                    if (inputMethodSubtypeId != null) {
+                        InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(
+                                inputMethodId, Integer.parseInt(inputMethodSubtypeId));
+                        if (mKeyboardLayouts.containsKey(handle)) {
+                            throw new XmlPullParserException(
+                                    "Found duplicate subtype to keyboard layout mapping: "
+                                    + handle);
+                        }
+                        mKeyboardLayouts.put(handle, descriptor);
+                    } else {
+                        if (mUnassociatedKeyboardLayouts.contains(descriptor)) {
+                            throw new XmlPullParserException(
+                                    "Found duplicate unassociated keyboard layout: " + descriptor);
+                        }
+                        mUnassociatedKeyboardLayouts.add(descriptor);
+                    }
                 } else if (parser.getName().equals("calibration")) {
                     String format = parser.getAttributeValue(null, "format");
                     String rotation = parser.getAttributeValue(null, "rotation");
@@ -492,19 +559,31 @@
             }
 
             // Maintain invariant that layouts are sorted.
-            Collections.sort(mKeyboardLayouts);
+            Collections.sort(mUnassociatedKeyboardLayouts);
 
             // Maintain invariant that there is always a current keyboard layout unless
             // there are none installed.
-            if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
-                mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
+            if (mCurrentKeyboardLayout == null && !mUnassociatedKeyboardLayouts.isEmpty()) {
+                mCurrentKeyboardLayout = mUnassociatedKeyboardLayouts.get(0);
             }
         }
 
         public void saveToXml(XmlSerializer serializer) throws IOException {
-            for (String layout : mKeyboardLayouts) {
+            for (String layout : mUnassociatedKeyboardLayouts) {
                 serializer.startTag(null, "keyboard-layout");
                 serializer.attribute(null, "descriptor", layout);
+                serializer.endTag(null, "keyboard-layout");
+            }
+
+            final int N = mKeyboardLayouts.size();
+            for (int i = 0; i < N; i++) {
+                final InputMethodSubtypeHandle handle = mKeyboardLayouts.keyAt(i);
+                final String layout = mKeyboardLayouts.valueAt(i);
+                serializer.startTag(null, "keyboard-layout");
+                serializer.attribute(null, "descriptor", layout);
+                serializer.attribute(null, "input-method-id", handle.getInputMethodId());
+                serializer.attribute(null, "input-method-subtype-id",
+                        Integer.toString(handle.getSubtypeId()));
                 if (layout.equals(mCurrentKeyboardLayout)) {
                     serializer.attribute(null, "current", "true");
                 }
@@ -529,6 +608,22 @@
             }
         }
 
+        private void dump(final PrintWriter pw, final String prefix) {
+            pw.println(prefix + "CurrentKeyboardLayout=" + mCurrentKeyboardLayout);
+            pw.println(prefix + "UnassociatedKeyboardLayouts=" + mUnassociatedKeyboardLayouts);
+            pw.println(prefix + "TouchCalibration=" + Arrays.toString(mTouchCalibration));
+            pw.println(prefix + "Subtype to Layout Mappings:");
+            final int N = mKeyboardLayouts.size();
+            if (N != 0) {
+                for (int i = 0; i < N; i++) {
+                    pw.println(prefix + "  " + mKeyboardLayouts.keyAt(i) + ": "
+                            + mKeyboardLayouts.valueAt(i));
+                }
+            } else {
+                pw.println(prefix + "  <none>");
+            }
+        }
+
         private static String surfaceRotationToString(int surfaceRotation) {
             switch (surfaceRotation) {
                 case Surface.ROTATION_0:   return "0";
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 38ebc80..1d7d24b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -31,6 +31,9 @@
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
 import static android.net.ConnectivityManager.isNetworkTypeMobile;
 import static android.net.NetworkPolicy.CYCLE_NONE;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
@@ -124,6 +127,7 @@
 import android.os.INetworkManagementService;
 import android.os.IPowerManager;
 import android.os.Message;
+import android.os.ResultReceiver;
 import android.os.MessageQueue.IdleHandler;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
@@ -1873,6 +1877,20 @@
     }
 
     @Override
+    public int getRestrictBackgroundByCaller() {
+        mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
+        final int uid = Binder.getCallingUid();
+        synchronized (mRulesLock) {
+            if (!mRestrictBackground) {
+                return RESTRICT_BACKGROUND_STATUS_DISABLED;
+            }
+            return mRestrictBackgroundWhitelistUids.get(uid)
+                    ? RESTRICT_BACKGROUND_STATUS_WHITELISTED
+                    : RESTRICT_BACKGROUND_STATUS_ENABLED;
+        }
+    }
+
+    @Override
     public boolean getRestrictBackground() {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
@@ -2102,6 +2120,13 @@
     }
 
     @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) throws RemoteException {
+        (new NetworkPolicyManagerShellCommand(this)).exec(
+                this, in, out, err, args, resultReceiver);
+    }
+
+    @Override
     public boolean isUidForeground(int uid) {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
new file mode 100644
index 0000000..7b1acca
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
@@ -0,0 +1,230 @@
+/*
+ * 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.server.net;
+
+import java.io.PrintWriter;
+
+import android.content.Intent;
+import android.net.INetworkPolicyManager;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+public class NetworkPolicyManagerShellCommand extends ShellCommand {
+
+    final INetworkPolicyManager mInterface;
+
+    NetworkPolicyManagerShellCommand(NetworkPolicyManagerService service) {
+        mInterface = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch(cmd) {
+                case "get":
+                    return runGet();
+                case "set":
+                    return runSet();
+                case "list":
+                    return runList();
+                case "add":
+                    return runAdd();
+                case "remove":
+                    return runRemove();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("Network policy manager (netpolicy) commands:");
+        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("  remove restrict-background-whitelist UID");
+        pw.println("    Removes a UID from the whitelist for restrict background usage.");
+    }
+
+    private int runGet() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final String type = getNextArg();
+        if (type == null) {
+            pw.println("Error: didn't specify type of data to get");
+            return -1;
+        }
+        switch(type) {
+            case "restrict-background":
+                return getRestrictBackgroundWhitelist();
+        }
+        pw.println("Error: unknown get type '" + type + "'");
+        return -1;
+    }
+
+    private int runSet() throws RemoteException  {
+        final PrintWriter pw = getOutPrintWriter();
+        final String type = getNextArg();
+        if (type == null) {
+            pw.println("Error: didn't specify type of data to set");
+            return -1;
+        }
+        switch(type) {
+            case "restrict-background":
+                return setRestrictBackgroundWhitelist();
+        }
+        pw.println("Error: unknown set type '" + type + "'");
+        return -1;
+    }
+
+    private int runList() throws RemoteException  {
+        final PrintWriter pw = getOutPrintWriter();
+        final String type = getNextArg();
+        if (type == null) {
+            pw.println("Error: didn't specify type of data to list");
+            return -1;
+        }
+        switch(type) {
+            case "restrict-background-whitelist":
+                return runListRestrictBackgroundWhitelist();
+        }
+        pw.println("Error: unknown list type '" + type + "'");
+        return -1;
+    }
+
+    private int runAdd() throws RemoteException  {
+        final PrintWriter pw = getOutPrintWriter();
+        final String type = getNextArg();
+        if (type == null) {
+            pw.println("Error: didn't specify type of data to add");
+            return -1;
+        }
+        switch(type) {
+            case "restrict-background-whitelist":
+                return addRestrictBackgroundWhitelist();
+        }
+        pw.println("Error: unknown add type '" + type + "'");
+        return -1;
+    }
+
+    private int runRemove() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final String type = getNextArg();
+        if (type == null) {
+            pw.println("Error: didn't specify type of data to remove");
+            return -1;
+        }
+        switch(type) {
+            case "restrict-background-whitelist":
+                return removeRestrictBackgroundWhitelist();
+        }
+        pw.println("Error: unknown remove type '" + type + "'");
+        return -1;
+    }
+
+    private int runListRestrictBackgroundWhitelist() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final int[] uids = mInterface.getRestrictBackgroundWhitelistedUids();
+        pw.print("Restrict background whitelisted UIDs: ");
+        if (uids.length == 0) {
+            pw.println("none");
+        } else {
+            for (int i = 0; i < uids.length; i++) {
+                int uid = uids[i];
+                pw.print(uid);
+                pw.print(' ');
+            }
+        }
+        pw.println();
+        return 0;
+    }
+
+    private int getRestrictBackgroundWhitelist() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.print("Restrict background status: ");
+        pw.println(mInterface.getRestrictBackground() ? "enabled" : "disabled");
+        return 0;
+    }
+
+    private int setRestrictBackgroundWhitelist() throws RemoteException {
+        final int enabled = getNextBooleanArg();
+        if (enabled < 0) {
+            return enabled;
+        }
+        mInterface.setRestrictBackground(enabled > 0);
+        return 0;
+    }
+
+    private int addRestrictBackgroundWhitelist() throws RemoteException {
+      final int uid = getUidFromNextArg();
+      if (uid < 0) {
+          return uid;
+      }
+      mInterface.addRestrictBackgroundWhitelistedUid(uid);
+      return 0;
+    }
+
+    private int removeRestrictBackgroundWhitelist() throws RemoteException {
+        final int uid = getUidFromNextArg();
+        if (uid < 0) {
+            return uid;
+        }
+        mInterface.removeRestrictBackgroundWhitelistedUid(uid);
+        return 0;
+    }
+
+    private int getNextBooleanArg() {
+        final PrintWriter pw = getOutPrintWriter();
+        final String arg = getNextArg();
+        if (arg == null) {
+            pw.println("Error: didn't specify BOOLEAN");
+            return -1;
+        }
+        return Boolean.valueOf(arg) ? 1 : 0;
+    }
+
+    private int getUidFromNextArg() {
+        final PrintWriter pw = getOutPrintWriter();
+        final String arg = getNextArg();
+        if (arg == null) {
+            pw.println("Error: didn't specify UID");
+            return -1;
+        }
+        try {
+            return Integer.parseInt(arg);
+        } catch (NumberFormatException e) {
+            pw.println("Error: UID (" + arg + ") should be a number");
+            return -2;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 482217c..bce7223 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -82,7 +82,7 @@
      * {@link PackageManagerService#mInstallLock}.
      */
     int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
-            boolean inclDependencies, String volumeUuid, boolean useProfiles, boolean extractOnly) {
+            boolean inclDependencies, boolean useProfiles, boolean extractOnly) {
         ArraySet<String> done;
         if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
             done = new ArraySet<String>();
@@ -97,7 +97,7 @@
                 mDexoptWakeLock.acquire();
             }
             try {
-                return performDexOptLI(pkg, instructionSets, done, volumeUuid, useProfiles,
+                return performDexOptLI(pkg, instructionSets, done, useProfiles,
                         extractOnly);
             } finally {
                 if (useLock) {
@@ -108,7 +108,7 @@
     }
 
     private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
-            ArraySet<String> done, String volumeUuid, boolean useProfiles, boolean extractOnly) {
+            ArraySet<String> done, boolean useProfiles, boolean extractOnly) {
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
 
@@ -185,7 +185,7 @@
                 try {
                     mPackageManagerService.mInstaller.dexopt(path, sharedGid,
                             pkg.packageName, dexCodeInstructionSet, dexoptNeeded, oatDir,
-                            dexFlags, volumeUuid, useProfiles);
+                            dexFlags, pkg.volumeUuid, useProfiles);
                     performedDexOpt = true;
                 } catch (InstallerException e) {
                     Slog.w(TAG, "Failed to dexopt", e);
@@ -252,8 +252,7 @@
             if (libPkg != null && !done.contains(libName)) {
                 // TODO: Analyze and investigate if we (should) profile libraries.
                 // Currently this will do a full compilation of the library.
-                performDexOptLI(libPkg, instructionSets, done,
-                        StorageManager.UUID_PRIVATE_INTERNAL, /*useProfiles*/ false,
+                performDexOptLI(libPkg, instructionSets, done, /*useProfiles*/ false,
                         /* extractOnly */ false);
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9d17f8f..f05e45f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6717,7 +6717,7 @@
             synchronized (mInstallLock) {
                 final String[] instructionSets = new String[] { targetInstructionSet };
                 int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
-                        true /* inclDependencies */, p.volumeUuid, useProfiles, extractOnly);
+                        true /* inclDependencies */, useProfiles, extractOnly);
                 return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
             }
         } finally {
@@ -6762,7 +6762,7 @@
             // Whoever is calling forceDexOpt wants a fully compiled package.
             // Don't use profiles since that may cause compilation to be skipped.
             final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets,
-                    true /* inclDependencies */, pkg.volumeUuid, false /* useProfiles */,
+                    true /* inclDependencies */, false /* useProfiles */,
                     false /* extractOnly */);
 
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -13024,7 +13024,7 @@
                 // Do not run PackageDexOptimizer through the local performDexOpt
                 // method because `pkg` is not in `mPackages` yet.
                 int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */,
-                        false /* inclDependencies */, volumeUuid, false /* useProfiles */,
+                        false /* inclDependencies */, false /* useProfiles */,
                         true /* extractOnly */);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1e057aa..5b9f0b3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -109,7 +109,7 @@
  */
 public class UserManagerService extends IUserManager.Stub {
     private static final String LOG_TAG = "UserManagerService";
-    static final boolean DBG = true; // DO NOT SUBMIT WITH TRUE
+    static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
     private static final boolean DBG_WITH_STACKTRACE = false; // DO NOT SUBMIT WITH TRUE
 
     private static final String TAG_NAME = "name";
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3b61817..0b1354a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3136,15 +3136,6 @@
             hideRecentApps(true, false);
         }
 
-        // Handle keyboard layout switching.
-        // TODO: Deprecate this behavior when we fully migrate to IME subtype-based layout rotation.
-        if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_SPACE
-                && ((metaState & KeyEvent.META_CTRL_MASK) != 0)) {
-            int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-            mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
-            return -1;
-        }
-
         // Handle input method switching.
         if (down && repeatCount == 0
                 && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 9a55e7f..2deb0d5 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -34,7 +34,6 @@
  */
 public class VrManagerService extends SystemService {
 
-    public static final boolean DEBUG = false;
     public static final String TAG = "VrManagerService";
 
     private final Object mLock = new Object();
@@ -88,7 +87,8 @@
         synchronized (mLock) {
             if (mVrModeEnabled != enabled) {
                 mVrModeEnabled = enabled;
-                if (DEBUG) Slog.d(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
+                // Log mode change event.
+                Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
                 onVrModeChangedLocked();
             }
         }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 0622fad..552af03 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -732,7 +732,8 @@
         float scaleW = appWidth / thumbWidth;
         float unscaledHeight = thumbHeight * scaleW;
         getNextAppTransitionStartRect(taskId, mTmpRect);
-        float unscaledStartY = mTmpRect.top - (unscaledHeight - thumbHeight) / 2f;
+        final float unscaledStartY = mTmpRect.top - (unscaledHeight - thumbHeight) / 2f;
+        final float toY = appRect.top + mNextAppTransitionInsets.top + -unscaledStartY;
         if (mNextAppTransitionScaleUp) {
             // Animation up from the thumbnail to the full screen
             Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
@@ -744,7 +745,6 @@
             alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
             final float toX = appRect.left + appRect.width() / 2 -
                     (mTmpRect.left + thumbWidth / 2);
-            final float toY = appRect.top + mNextAppTransitionInsets.top + -unscaledStartY;
             Animation translate = new TranslateAnimation(0, toX, 0, toY);
             translate.setInterpolator(mTouchResponseInterpolator);
             translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
@@ -764,8 +764,9 @@
             Animation alpha = new AlphaAnimation(0f, 1f);
             alpha.setInterpolator(mThumbnailFadeInInterpolator);
             alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
-            Animation translate = new TranslateAnimation(0, 0, -unscaledStartY +
-                    mNextAppTransitionInsets.top, 0);
+            final float toX = appRect.left + appRect.width() / 2 -
+                    (mTmpRect.left + thumbWidth / 2);
+            Animation translate = new TranslateAnimation(toX, 0, toY, 0);
             translate.setInterpolator(mTouchResponseInterpolator);
             translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
 
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 18798e5..65b91f7 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -402,7 +402,7 @@
                 }
                 // force DRAG_EXITED_EVENT if appropriate
                 DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED,
-                        x, y, null, null, null, null, false);
+                        0, 0, null, null, null, null, false);
                 mTargetWindow.mClient.dispatchDragEvent(evt);
                 if (myPid != mTargetWindow.mSession.mPid) {
                     evt.recycle();
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index a75f2c9..2833b35 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -299,8 +299,7 @@
         final int orientation = mService.mCurConfiguration.orientation;
         mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
         final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
-                mService.mContext.getResources(),
-                0 /* minFlingVelocityPxPerSecond */, displayWidth, displayHeight,
+                mService.mContext.getResources(), displayWidth, displayHeight,
                 dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds);
         final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
 
@@ -552,7 +551,6 @@
             mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
                     mTmpRect2);
             final int position = new DividerSnapAlgorithm(mService.mContext.getResources(),
-                    0 /* minFlingVelocityPxPerSecond */,
                     di.logicalWidth,
                     di.logicalHeight,
                     dockDividerWidth,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d2e2639..1021411 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5308,12 +5308,6 @@
 
     // Called by window manager policy.  Not exposed externally.
     @Override
-    public void switchKeyboardLayout(int deviceId, int direction) {
-        mInputManager.switchKeyboardLayout(deviceId, direction);
-    }
-
-    // Called by window manager policy.  Not exposed externally.
-    @Override
     public void switchInputMethod(boolean forwardDirection) {
         final InputMethodManagerInternal inputMethodManagerInternal =
                 LocalServices.getService(InputMethodManagerInternal.class);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 9012b98..761d6e9 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -27,6 +27,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerService.H.*;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
 import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
@@ -67,6 +68,7 @@
  * surfaces according to these frames. Z layer is still assigned withing WindowManagerService.
  */
 class WindowSurfacePlacer {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfacePlacer" : TAG_WM;
     private final WindowManagerService mService;
     private final WallpaperController mWallpaperControllerLocked;
 
@@ -160,7 +162,7 @@
             if (DEBUG) {
                 throw new RuntimeException("Recursive call!");
             }
-            Slog.w(TAG_WM, "performLayoutAndPlaceSurfacesLocked called while in layout. Callers="
+            Slog.w(TAG, "performLayoutAndPlaceSurfacesLocked called while in layout. Callers="
                     + Debug.getCallers(3));
             return;
         }
@@ -186,11 +188,10 @@
             // Wait a little bit for things to settle down, and off we go.
             while (!mService.mForceRemoves.isEmpty()) {
                 WindowState ws = mService.mForceRemoves.remove(0);
-                Slog.i(TAG_WM, "Force removing: " + ws);
+                Slog.i(TAG, "Force removing: " + ws);
                 mService.removeWindowInnerLocked(ws);
             }
-            Slog.w(TAG_WM,
-                    "Due to memory failure, waiting a bit for next layout");
+            Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
             Object tmp = new Object();
             synchronized (tmp) {
                 try {
@@ -209,7 +210,7 @@
                 if (++mLayoutRepeatCount < 6) {
                     requestTraversal();
                 } else {
-                    Slog.e(TAG_WM, "Performed 6 layouts in a row. Skipping");
+                    Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
                     mLayoutRepeatCount = 0;
                 }
             } else {
@@ -222,7 +223,7 @@
             }
         } catch (RuntimeException e) {
             mInLayout = false;
-            Slog.wtf(TAG_WM, "Unhandled exception while laying out windows", e);
+            Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
         }
 
         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
@@ -230,18 +231,15 @@
 
     void debugLayoutRepeats(final String msg, int pendingLayoutChanges) {
         if (mLayoutRepeatCount >= LAYOUT_REPEAT_THRESHOLD) {
-            Slog.v(TAG_WM, "Layouts looping: " + msg +
+            Slog.v(TAG, "Layouts looping: " + msg +
                     ", mPendingLayoutChanges = 0x" + Integer.toHexString(pendingLayoutChanges));
         }
     }
 
     // "Something has changed!  Let's make it correct now."
     private void performSurfacePlacementInner(boolean recoveringMemory) {
-        if (DEBUG_WINDOW_TRACE) {
-            Slog.v(TAG_WM,
-                    "performSurfacePlacementInner: entry. Called by "
-                    + Debug.getCallers(3));
-        }
+        if (DEBUG_WINDOW_TRACE) Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
+                + Debug.getCallers(3));
 
         int i;
         boolean updateInputWindowsNeeded = false;
@@ -283,16 +281,16 @@
         final int defaultDw = defaultInfo.logicalWidth;
         final int defaultDh = defaultInfo.logicalHeight;
 
-        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                 ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
         SurfaceControl.openTransaction();
         try {
             applySurfaceChangesTransaction(recoveringMemory, numDisplays, defaultDw, defaultDh);
         } catch (RuntimeException e) {
-            Slog.wtf(TAG_WM, "Unhandled exception in Window Manager", e);
+            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
             SurfaceControl.closeTransaction();
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
         }
 
@@ -339,7 +337,7 @@
 
         if (mWallpaperMayChange) {
             if (DEBUG_WALLPAPER_LIGHT)
-                Slog.v(TAG_WM, "Wallpaper may change!  Adjusting");
+                Slog.v(TAG, "Wallpaper may change!  Adjusting");
             defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
             if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("WallpaperMayChange",
                     defaultDisplay.pendingLayoutChanges);
@@ -374,10 +372,8 @@
             mService.mResizingWindows.remove(i);
         }
 
-        if (DEBUG_ORIENTATION && mService.mDisplayFrozen)
-            Slog.v(TAG_WM,
-                "With display frozen, orientationChangeComplete="
-                + mOrientationChangeComplete);
+        if (DEBUG_ORIENTATION && mService.mDisplayFrozen) Slog.v(TAG,
+                "With display frozen, orientationChangeComplete=" + mOrientationChangeComplete);
         if (mOrientationChangeComplete) {
             if (mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
                 mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
@@ -435,9 +431,8 @@
                     // soon as their animations are complete
                     token.mAppAnimator.clearAnimation();
                     token.mAppAnimator.animating = false;
-                    if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT)
-                        Slog.v(TAG_WM,
-                                "performLayout: App token exiting now removed" + token);
+                    if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+                            "performLayout: App token exiting now removed" + token);
                     token.removeAppFromTaskLocked();
                 }
             }
@@ -482,7 +477,7 @@
                     || Settings.Global.getInt(mService.mContext.getContentResolver(),
                         Settings.Global.THEATER_MODE_ON, 0) == 0) {
                 if (DEBUG_VISIBILITY || DEBUG_POWER) {
-                    Slog.v(TAG_WM, "Turning screen on after layout!");
+                    Slog.v(TAG, "Turning screen on after layout!");
                 }
                 mService.mPowerManager.wakeUp(SystemClock.uptimeMillis(),
                         "android.server.wm:TURN_ON");
@@ -491,8 +486,7 @@
         }
 
         if (mUpdateRotation) {
-            if (DEBUG_ORIENTATION) Slog.d(TAG_WM,
-                    "Performing post-rotate rotation");
+            if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
             if (mService.updateRotationUncheckedLocked(false)) {
                 mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
             } else {
@@ -545,7 +539,7 @@
 
         mService.scheduleAnimationLocked();
 
-        if (DEBUG_WINDOW_TRACE) Slog.e(TAG_WM,
+        if (DEBUG_WINDOW_TRACE) Slog.e(TAG,
                 "performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating());
     }
 
@@ -589,7 +583,7 @@
             do {
                 repeats++;
                 if (repeats > 6) {
-                    Slog.w(TAG_WM, "Animation repeat aborted after too many iterations");
+                    Slog.w(TAG, "Animation repeat aborted after too many iterations");
                     displayContent.layoutNeeded = false;
                     break;
                 }
@@ -605,7 +599,7 @@
 
                 if (isDefaultDisplay
                         && (displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
-                    if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Computing new config from layout");
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
                     if (mService.updateOrientationFromAppTokensLocked(true)) {
                         displayContent.layoutNeeded = true;
                         mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
@@ -621,7 +615,7 @@
                     performLayoutLockedInner(displayContent, repeats == 1,
                             false /* updateInputWindows */);
                 } else {
-                    Slog.w(TAG_WM, "Layout repeat skipped after too many iterations");
+                    Slog.w(TAG, "Layout repeat skipped after too many iterations");
                 }
 
                 // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think
@@ -707,7 +701,7 @@
                     }
                 }
 
-                //Slog.i(TAG_WM, "Window " + this + " clearing mContentChanged - done placing");
+                //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
                 w.mContentChanged = false;
                 w.mMovedByResize = false;
 
@@ -730,7 +724,7 @@
                         }
                         if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
                             if (DEBUG_WALLPAPER_LIGHT)
-                                Slog.v(TAG_WM, "First draw done in potential wallpaper target " + w);
+                                Slog.v(TAG, "First draw done in potential wallpaper target " + w);
                             mWallpaperMayChange = true;
                             displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                             if (DEBUG_LAYOUT_REPEATS) {
@@ -753,7 +747,7 @@
 
                 final AppWindowToken atoken = w.mAppToken;
                 if (DEBUG_STARTING_WINDOW && atoken != null && w == atoken.startingWindow) {
-                    Slog.d(TAG_WM, "updateWindows: starting " + w
+                    Slog.d(TAG, "updateWindows: starting " + w
                             + " isOnScreen=" + w.isOnScreen() + " allDrawn=" + atoken.allDrawn
                             + " freezingScreen=" + atoken.mAppAnimator.freezingScreen);
                 }
@@ -767,11 +761,11 @@
                             || winAnimator.mAttrType == TYPE_BASE_APPLICATION)
                             && !w.mExiting && !w.mDestroying) {
                         if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
-                            Slog.v(TAG_WM, "Eval win " + w + ": isDrawn="
+                            Slog.v(TAG, "Eval win " + w + ": isDrawn="
                                     + w.isDrawnLw()
                                     + ", isAnimating=" + winAnimator.isAnimating());
                             if (!w.isDrawnLw()) {
-                                Slog.v(TAG_WM, "Not displayed: s="
+                                Slog.v(TAG, "Not displayed: s="
                                         + winAnimator.mSurfaceController
                                         + " pv=" + w.mPolicyVisibility
                                         + " mDrawState=" + winAnimator.drawStateToString()
@@ -786,7 +780,7 @@
                                 if (w.isDrawnLw()) {
                                     atoken.numDrawnWindows++;
                                     if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
-                                        Slog.v(TAG_WM, "tokenMayBeDrawn: " + atoken
+                                        Slog.v(TAG, "tokenMayBeDrawn: " + atoken
                                                 + " freezingScreen="
                                                 + atoken.mAppAnimator.freezingScreen
                                                 + " mAppFreezing=" + w.mAppFreezing);
@@ -854,8 +848,8 @@
         int i;
 
         if (DEBUG_LAYOUT) {
-            Slog.v(TAG_WM, "-------------------------------------");
-            Slog.v(TAG_WM, "performLayout: needed="
+            Slog.v(TAG, "-------------------------------------");
+            Slog.v(TAG, "performLayout: needed="
                     + displayContent.layoutNeeded + " dw=" + dw + " dh=" + dh);
         }
 
@@ -889,18 +883,18 @@
                     || win.isGoneForLayoutLw();
 
             if (DEBUG_LAYOUT && !win.mLayoutAttached) {
-                Slog.v(TAG_WM, "1ST PASS " + win
+                Slog.v(TAG, "1ST PASS " + win
                         + ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame
                         + " mLayoutAttached=" + win.mLayoutAttached
                         + " screen changed=" + win.isConfigChanged());
                 final AppWindowToken atoken = win.mAppToken;
-                if (gone) Slog.v(TAG_WM, "  GONE: mViewVisibility="
+                if (gone) Slog.v(TAG, "  GONE: mViewVisibility="
                         + win.mViewVisibility + " mRelayoutCalled="
                         + win.mRelayoutCalled + " hidden="
                         + win.mRootToken.hidden + " hiddenRequested="
                         + (atoken != null && atoken.hiddenRequested)
                         + " mAttachedHidden=" + win.mAttachedHidden);
-                else Slog.v(TAG_WM, "  VIS: mViewVisibility="
+                else Slog.v(TAG, "  VIS: mViewVisibility="
                         + win.mViewVisibility + " mRelayoutCalled="
                         + win.mRelayoutCalled + " hidden="
                         + win.mRootToken.hidden + " hiddenRequested="
@@ -920,7 +914,7 @@
                             win.mAppToken.layoutConfigChanges)))) {
                 if (!win.mLayoutAttached) {
                     if (initial) {
-                        //Slog.i(TAG_WM, "Window " + this + " clearing mContentChanged - initial");
+                        //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
                         win.mContentChanged = false;
                     }
                     if (win.mAttrs.type == TYPE_DREAM) {
@@ -940,7 +934,7 @@
                         displayContent.mDimLayerController.updateDimLayer(task);
                     }
 
-                    if (DEBUG_LAYOUT) Slog.v(TAG_WM,
+                    if (DEBUG_LAYOUT) Slog.v(TAG,
                             "  LAYOUT: mFrame="
                             + win.mFrame + " mContainingFrame="
                             + win.mContainingFrame + " mDisplayFrame="
@@ -961,7 +955,7 @@
             final WindowState win = windows.get(i);
 
             if (win.mLayoutAttached) {
-                if (DEBUG_LAYOUT) Slog.v(TAG_WM,
+                if (DEBUG_LAYOUT) Slog.v(TAG,
                         "2ND PASS " + win + " mHaveFrame=" + win.mHaveFrame + " mViewVisibility="
                         + win.mViewVisibility + " mRelayoutCalled=" + win.mRelayoutCalled);
                 // If this view is GONE, then skip it -- keep the current
@@ -975,14 +969,14 @@
                 if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
                         || !win.mHaveFrame || win.mLayoutNeeded) {
                     if (initial) {
-                        //Slog.i(TAG_WM, "Window " + this + " clearing mContentChanged - initial");
+                        //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
                         win.mContentChanged = false;
                     }
                     win.mLayoutNeeded = false;
                     win.prelayout();
                     mService.mPolicy.layoutWindowLw(win, win.mAttachedWindow);
                     win.mLayoutSeq = seq;
-                    if (DEBUG_LAYOUT) Slog.v(TAG_WM,
+                    if (DEBUG_LAYOUT) Slog.v(TAG,
                             "  LAYOUT: mFrame=" + win.mFrame + " mContainingFrame="
                             + win.mContainingFrame + " mDisplayFrame=" + win.mDisplayFrame);
                 }
@@ -1013,7 +1007,7 @@
         if (!transitionGoodToGo(appsCount)) {
             return 0;
         }
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "**** GOOD TO GO");
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
         int transit = mService.mAppTransition.getAppTransition();
         if (mService.mSkipAppTransitionAnimation) {
             transit = AppTransition.TRANSIT_UNSET;
@@ -1105,7 +1099,7 @@
         // example, when this transition is being done behind
         // the lock screen.
         if (!mService.mPolicy.allowAppAnimationsLw()) {
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                     "Animations disallowed by keyguard or dream.");
             animLp = null;
         }
@@ -1154,7 +1148,7 @@
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
             final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Now opening app" + wtoken);
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
 
             if (!appAnimator.usingTransferredAnimation) {
                 appAnimator.clearThumbnail();
@@ -1179,14 +1173,14 @@
             for (int j = 0; j < windowsCount; j++) {
                 appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
             }
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
             SurfaceControl.openTransaction();
             try {
                 mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
             } finally {
                 SurfaceControl.closeTransaction();
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                         "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
             }
             mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
@@ -1231,7 +1225,7 @@
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mClosingApps.valueAt(i);
             final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Now closing app " + wtoken);
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
             appAnimator.clearThumbnail();
             appAnimator.animation = null;
             wtoken.inPendingTransaction = false;
@@ -1270,14 +1264,14 @@
     }
 
     private boolean transitionGoodToGo(int appsCount) {
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                 "Checking " + appsCount + " opening apps (frozen="
                         + mService.mDisplayFrozen + " timeout="
                         + mService.mAppTransition.isTimeout() + ")...");
         if (!mService.mAppTransition.isTimeout()) {
             for (int i = 0; i < appsCount; i++) {
                 AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                         "Check opening app=" + wtoken + ": allDrawn="
                         + wtoken.allDrawn + " startingDisplayed="
                         + wtoken.startingDisplayed + " startingMoved="
@@ -1293,7 +1287,7 @@
 
             // We also need to wait for the specs to be fetched, if needed.
             if (mService.mAppTransition.isFetchingAppTransitionsSpecs()) {
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "isFetchingAppTransitionSpecs=true");
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "isFetchingAppTransitionSpecs=true");
                 return false;
             }
 
@@ -1314,7 +1308,7 @@
                         ? null : wallpaperTarget;
         final ArraySet<AppWindowToken> openingApps = mService.mOpeningApps;
         final ArraySet<AppWindowToken> closingApps = mService.mClosingApps;
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                 "New wallpaper target=" + wallpaperTarget
                         + ", oldWallpaper=" + oldWallpaper
                         + ", lower target=" + lowerWallpaperTarget
@@ -1324,7 +1318,7 @@
         mService.mAnimateWallpaperWithTarget = false;
         if (closingAppHasWallpaper && openingAppHasWallpaper) {
             if (DEBUG_APP_TRANSITIONS)
-                Slog.v(TAG_WM, "Wallpaper animation!");
+                Slog.v(TAG, "Wallpaper animation!");
             switch (transit) {
                 case AppTransition.TRANSIT_ACTIVITY_OPEN:
                 case AppTransition.TRANSIT_TASK_OPEN:
@@ -1337,14 +1331,14 @@
                     transit = AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
                     break;
             }
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                     "New transit: " + AppTransition.appTransitionToString(transit));
         } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
                 && !openingApps.contains(oldWallpaper.mAppToken)
                 && closingApps.contains(oldWallpaper.mAppToken)) {
             // We are transitioning from an activity with a wallpaper to one without.
             transit = AppTransition.TRANSIT_WALLPAPER_CLOSE;
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                     "New transit away from wallpaper: "
                     + AppTransition.appTransitionToString(transit));
         } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() &&
@@ -1352,7 +1346,7 @@
             // We are transitioning from an activity without
             // a wallpaper to now showing the wallpaper
             transit = AppTransition.TRANSIT_WALLPAPER_OPEN;
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                     "New transit into wallpaper: "
                     + AppTransition.appTransitionToString(transit));
         } else {
@@ -1447,7 +1441,7 @@
                         int numInteresting = wtoken.numInterestingWindows;
                         if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
                             if (DEBUG_VISIBILITY)
-                                Slog.v(TAG_WM, "allDrawn: " + wtoken
+                                Slog.v(TAG, "allDrawn: " + wtoken
                                     + " interesting=" + numInteresting
                                     + " drawn=" + wtoken.numDrawnWindows);
                             wtoken.allDrawn = true;
@@ -1476,7 +1470,7 @@
                 final AppWindowToken wtoken = win.mAppToken;
                 final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
                 if (DEBUG_APP_TRANSITIONS)
-                    Slog.v(TAG_WM, "Now animating app in place " + wtoken);
+                    Slog.v(TAG, "Now animating app in place " + wtoken);
                 appAnimator.clearThumbnail();
                 appAnimator.animation = null;
                 mService.updateTokenInPlaceLocked(wtoken, transit);
@@ -1502,7 +1496,7 @@
         final int taskId = appToken.mTask.mTaskId;
         Bitmap thumbnailHeader = mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
         if (thumbnailHeader == null || thumbnailHeader.getConfig() == Bitmap.Config.ALPHA_8) {
-            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "No thumbnail header bitmap for: " + taskId);
+            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
             return;
         }
         // This thumbnail animation is very special, we need to have
@@ -1520,7 +1514,7 @@
                     PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
             surfaceControl.setLayerStack(display.getLayerStack());
             if (SHOW_TRANSACTIONS) {
-                Slog.i(TAG_WM, "  THUMBNAIL " + surfaceControl + ": CREATE");
+                Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
             }
 
             // Draw the thumbnail onto the surface
@@ -1563,7 +1557,7 @@
             openingAppAnimator.thumbnailX = mTmpStartRect.left;
             openingAppAnimator.thumbnailY = mTmpStartRect.top;
         } catch (Surface.OutOfResourcesException e) {
-            Slog.e(TAG_WM, "Can't allocate thumbnail/Canvas surface w="
+            Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
                     + dirty.width() + " h=" + dirty.height(), e);
             openingAppAnimator.clearThumbnail();
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index eb75914..f68a299 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -67,6 +67,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.net.ConnectivityManager;
@@ -487,6 +488,7 @@
         private static final String TAG_SHORT_SUPPORT_MESSAGE = "short-support-message";
         private static final String TAG_LONG_SUPPORT_MESSAGE = "long-support-message";
         private static final String TAG_PARENT_ADMIN = "parent-admin";
+        private static final String TAG_ORGANIZATION_COLOR = "organization-color";
 
         final DeviceAdminInfo info;
 
@@ -580,6 +582,10 @@
         String shortSupportMessage = null;
         String longSupportMessage = null;
 
+        // Background color of confirm credentials screen. Default: gray.
+        static final int DEF_ORGANIZATION_COLOR = Color.GRAY;
+        int organizationColor = DEF_ORGANIZATION_COLOR;
+
         ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
             info = _info;
             isParent = parent;
@@ -787,6 +793,11 @@
                 parentAdmin.writeToXml(out);
                 out.endTag(null, TAG_PARENT_ADMIN);
             }
+            if (organizationColor != DEF_ORGANIZATION_COLOR) {
+                out.startTag(null, TAG_ORGANIZATION_COLOR);
+                out.attribute(null, ATTR_VALUE, Integer.toString(organizationColor));
+                out.endTag(null, TAG_ORGANIZATION_COLOR);
+            }
         }
 
         void writePackageListToXml(XmlSerializer out, String outerTag,
@@ -920,6 +931,9 @@
                 } else if (TAG_PARENT_ADMIN.equals(tag)) {
                     parentAdmin = new ActiveAdmin(info, /* parent */ true);
                     parentAdmin.readFromXml(parser);
+                } else if (TAG_ORGANIZATION_COLOR.equals(tag)) {
+                    organizationColor = Integer.parseInt(
+                            parser.getAttributeValue(null, ATTR_VALUE));
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -7685,4 +7699,46 @@
         }
         return null;
     }
+
+    @Override
+    public void setOrganizationColor(@NonNull ComponentName who, int color) {
+        final int userHandle = mInjector.userHandleGetCallingUserId();
+        if (!mHasFeature || !isManagedProfile(userHandle)) {
+            return;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        synchronized (this) {
+            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            admin.organizationColor = color;
+            saveSettingsLocked(userHandle);
+        }
+    }
+
+    @Override
+    public int getOrganizationColor(@NonNull ComponentName who) {
+        final int userHandle = mInjector.userHandleGetCallingUserId();
+        if (!mHasFeature || !isManagedProfile(userHandle)) {
+            return ActiveAdmin.DEF_ORGANIZATION_COLOR;
+        }
+        synchronized (this) {
+            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            return admin.organizationColor;
+        }
+    }
+
+    @Override
+    public int getOrganizationColorForUser(int userHandle) {
+        if (!mHasFeature || !isManagedProfile(userHandle)) {
+            return ActiveAdmin.DEF_ORGANIZATION_COLOR;
+        }
+        enforceCrossUsersPermission(userHandle);
+        synchronized (this) {
+            ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userHandle);
+            return (profileOwner != null)
+                    ? profileOwner.organizationColor
+                    : ActiveAdmin.DEF_ORGANIZATION_COLOR;
+        }
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8114031..c186a12 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -602,7 +602,6 @@
 
         StatusBarManagerService statusBar = null;
         INotificationManager notification = null;
-        InputMethodManagerService imm = null;
         WallpaperManagerService wallpaper = null;
         LocationManagerService location = null;
         CountryDetectorService countryDetector = null;
@@ -613,14 +612,7 @@
 
         // Bring up services needed for UI.
         if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
-            traceBeginAndSlog("StartInputMethodManagerService");
-            try {
-                imm = new InputMethodManagerService(context);
-                ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
-            } catch (Throwable e) {
-                reportWtf("starting Input Manager Service", e);
-            }
-            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+            mSystemServiceManager.startService(InputMethodManagerService.Lifecycle.class);
 
             traceBeginAndSlog("StartAccessibilityManagerService");
             try {
@@ -1207,7 +1199,6 @@
         final ConnectivityService connectivityF = connectivity;
         final NetworkScoreService networkScoreF = networkScore;
         final WallpaperManagerService wallpaperF = wallpaper;
-        final InputMethodManagerService immF = imm;
         final LocationManagerService locationF = location;
         final CountryDetectorService countryDetectorF = countryDetector;
         final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
@@ -1304,11 +1295,6 @@
                     reportWtf("Notifying WallpaperService running", e);
                 }
                 try {
-                    if (immF != null) immF.systemRunning(statusBarF);
-                } catch (Throwable e) {
-                    reportWtf("Notifying InputMethodService running", e);
-                }
-                try {
                     if (locationF != null) locationF.systemRunning();
                 } catch (Throwable e) {
                     reportWtf("Notifying Location Service running", e);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4253cd4..f2949bf4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -558,6 +558,7 @@
             }
             mRealTimeSnapshot = actualRealtime;
             mSystemTimeSnapshot = actualSystemTime;
+            postCheckIdleStates(UserHandle.USER_ALL);
         }
         return actualSystemTime;
     }
@@ -602,7 +603,7 @@
                     || event.mEventType == Event.SYSTEM_INTERACTION
                     || event.mEventType == Event.USER_INTERACTION)) {
                 if (previouslyIdle) {
-                    // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
+                    //Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
                     mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
                             /* idle = */ 0, event.mPackage));
                     notifyBatteryStats(event.mPackage, userId, false);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index d45b26f..3e30188 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -298,6 +298,16 @@
     public static final String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
 
     /**
+     * A boolean meta-data value indicating whether an {@link InCallService} implements an
+     * in-call user interface to be used while the device is in car-mode (see
+     * {@link android.content.res.Configuration.UI_MODE_TYPE_CAR}).
+     *
+     * @hide
+     */
+    public static final String METADATA_IN_CALL_SERVICE_CAR_MODE_UI =
+            "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI";
+
+    /**
      * The dual tone multi-frequency signaling character sent to indicate the dialing system should
      * pause for a predefined period.
      */
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 81aa6c6..5296d4d 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -532,6 +532,12 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
         throw new UnsupportedOperationException();
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index d346731..3b01827 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -235,7 +235,7 @@
          setRegion(part2.string());
      } else if (part2.length() == 4 && isAlpha(part2)) {
          setScript(part2.string());
-     } else if (part2.length() >= 5 && part2.length() <= 8) {
+     } else if (part2.length() >= 4 && part2.length() <= 8) {
          setVariant(part2.string());
      } else {
          valid = false;
@@ -250,7 +250,7 @@
      if (((part3.length() == 2 && isAlpha(part3)) ||
          (part3.length() == 3 && isNumber(part3))) && script[0]) {
          setRegion(part3.string());
-     } else if (part3.length() >= 5 && part3.length() <= 8) {
+     } else if (part3.length() >= 4 && part3.length() <= 8) {
          setVariant(part3.string());
      } else {
          valid = false;
@@ -261,7 +261,7 @@
      }
 
      const String8& part4 = parts[3];
-     if (part4.length() >= 5 && part4.length() <= 8) {
+     if (part4.length() >= 4 && part4.length() <= 8) {
          setVariant(part4.string());
      } else {
          valid = false;
@@ -280,7 +280,7 @@
 
     String8 part = parts[currentIndex];
     if (part[0] == 'b' && part[1] == '+') {
-        // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
+        // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
         // except that the separator is "+" and not "-".
         Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+');
         subtags.removeItemsAt(0);
@@ -296,8 +296,11 @@
                     setRegion(subtags[1]);
                     break;
                 case 4:
-                    setScript(subtags[1]);
-                    break;
+                    if (isAlpha(subtags[1])) {
+                        setScript(subtags[1]);
+                        break;
+                    }
+                    // This is not alphabetical, so we fall through to variant
                 case 5:
                 case 6:
                 case 7:
@@ -305,7 +308,7 @@
                     setVariant(subtags[1]);
                     break;
                 default:
-                    fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n",
+                    fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n",
                             part.string());
                     return -1;
             }
@@ -322,13 +325,13 @@
                 setRegion(subtags[1]);
                 hasRegion = true;
             } else {
-                fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n", part.string());
+                fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n", part.string());
                 return -1;
             }
 
             // The third tag can either be a region code (if the second tag was
             // a script), else a variant code.
-            if (subtags[2].size() > 4) {
+            if (subtags[2].size() >= 4) {
                 setVariant(subtags[2]);
             } else {
                 setRegion(subtags[2]);
@@ -339,7 +342,7 @@
             setRegion(subtags[2]);
             setVariant(subtags[3]);
         } else {
-            fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name: %s\n", part.string());
+            fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name: %s\n", part.string());
             return -1;
         }
 
@@ -370,7 +373,7 @@
 void AaptLocaleValue::initFromResTable(const ResTable_config& config) {
     config.unpackLanguage(language);
     config.unpackRegion(region);
-    if (config.localeScript[0]) {
+    if (config.localeScriptWasProvided) {
         memcpy(script, config.localeScript, sizeof(config.localeScript));
     }
 
@@ -385,6 +388,10 @@
 
     if (script[0]) {
         memcpy(out->localeScript, script, sizeof(out->localeScript));
+        out->localeScriptWasProvided = true;
+    } else {
+        out->computeScript();
+        out->localeScriptWasProvided = false;
     }
 
     if (variant[0]) {
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index 20a2d0c..0369156 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -96,7 +96,7 @@
          setRegion(part2.c_str());
      } else if (part2.length() == 4 && isAlpha(part2)) {
          setScript(part2.c_str());
-     } else if (part2.length() >= 5 && part2.length() <= 8) {
+     } else if (part2.length() >= 4 && part2.length() <= 8) {
          setVariant(part2.c_str());
      } else {
          valid = false;
@@ -111,7 +111,7 @@
      if (((part3.length() == 2 && isAlpha(part3)) ||
          (part3.length() == 3 && isNumber(part3))) && script[0]) {
          setRegion(part3.c_str());
-     } else if (part3.length() >= 5 && part3.length() <= 8) {
+     } else if (part3.length() >= 4 && part3.length() <= 8) {
          setVariant(part3.c_str());
      } else {
          valid = false;
@@ -122,7 +122,7 @@
      }
 
      const std::string& part4 = parts[3];
-     if (part4.length() >= 5 && part4.length() <= 8) {
+     if (part4.length() >= 4 && part4.length() <= 8) {
          setVariant(part4.c_str());
      } else {
          valid = false;
@@ -141,7 +141,7 @@
 
     std::string& part = *iter;
     if (part[0] == 'b' && part[1] == '+') {
-        // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
+        // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
         // except that the separator is "+" and not "-".
         std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
         subtags.erase(subtags.begin());
@@ -157,8 +157,12 @@
                     setRegion(subtags[1].c_str());
                     break;
                 case 4:
-                    setScript(subtags[1].c_str());
-                    break;
+                    if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
+                        // This is a variant: fall through
+                    } else {
+                        setScript(subtags[1].c_str());
+                        break;
+                    }
                 case 5:
                 case 6:
                 case 7:
@@ -184,7 +188,7 @@
 
             // The third tag can either be a region code (if the second tag was
             // a script), else a variant code.
-            if (subtags[2].size() > 4) {
+            if (subtags[2].size() >= 4) {
                 setVariant(subtags[2].c_str());
             } else {
                 setRegion(subtags[2].c_str());
@@ -249,7 +253,7 @@
 void LocaleValue::initFromResTable(const ResTable_config& config) {
     config.unpackLanguage(language);
     config.unpackRegion(region);
-    if (config.localeScript[0]) {
+    if (config.localeScriptWasProvided) {
         memcpy(script, config.localeScript, sizeof(config.localeScript));
     }
 
@@ -264,6 +268,10 @@
 
     if (script[0]) {
         memcpy(out->localeScript, script, sizeof(out->localeScript));
+        out->localeScriptWasProvided = true;
+    } else {
+        out->computeScript();
+        out->localeScriptWasProvided = false;
     }
 
     if (variant[0]) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 08258c9..037ce57 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -492,6 +492,11 @@
     }
 
     @Override
+    public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
+        return null;
+    }
+
+    @Override
     public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
         return null;
     }
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
new file mode 100755
index 0000000..b071093
--- /dev/null
+++ b/tools/localedata/extract_icu_data.py
@@ -0,0 +1,286 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 The Android Open Source Project. All Rights Reserved.
+#
+# 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.
+#
+
+"""Generate a C++ data table containing locale data."""
+
+import collections
+import glob
+import os.path
+import sys
+
+
+def get_locale_parts(locale):
+    """Split a locale into three parts, for langauge, script, and region."""
+    parts = locale.split('_')
+    if len(parts) == 1:
+        return (parts[0], None, None)
+    elif len(parts) == 2:
+        if len(parts[1]) == 4:  # parts[1] is a script
+            return (parts[0], parts[1], None)
+        else:
+            return (parts[0], None, parts[1])
+    else:
+        assert len(parts) == 3
+        return tuple(parts)
+
+
+def read_likely_subtags(input_file_name):
+    """Read and parse ICU's likelySubtags.txt."""
+    with open(input_file_name) as input_file:
+        likely_script_dict = {
+            # Android's additions for pseudo-locales. These internal codes make
+            # sure that the pseudo-locales would not match other English or
+            # Arabic locales. (We can't use private-use ISO 15924 codes, since
+            # they may be used by apps for other purposes.)
+            "en_XA": "~~~A",
+            "ar_XB": "~~~B",
+        }
+        representative_locales = {
+            # Android's additions
+            "en_Latn_GB", # representative for en_Latn_001
+            "es_Latn_MX", # representative for es_Latn_419
+            "es_Latn_US", # representative for es_Latn_419 (not the best idea,
+                          # but Android has been shipping with it for quite a
+                          # while. Fortunately, MX < US, so if both exist, MX
+                          # would be chosen.)
+        }
+        for line in input_file:
+            line = unicode(line, 'UTF-8').strip(u' \n\uFEFF').encode('UTF-8')
+            if line.startswith('//'):
+                continue
+            if '{' in line and '}' in line:
+                from_locale = line[:line.index('{')]
+                to_locale = line[line.index('"')+1:line.rindex('"')]
+                from_lang, from_scr, from_region = get_locale_parts(from_locale)
+                _, to_scr, to_region = get_locale_parts(to_locale)
+                if from_lang == 'und':
+                    continue  # not very useful for our purposes
+                if from_region is None and to_region != '001':
+                    representative_locales.add(to_locale)
+                if from_scr is None:
+                    likely_script_dict[from_locale] = to_scr
+        return likely_script_dict, frozenset(representative_locales)
+
+
+# From packLanguageOrRegion() in ResourceTypes.cpp
+def pack_language_or_region(inp, base):
+    """Pack langauge or region in a two-byte tuple."""
+    if inp is None:
+        return (0, 0)
+    elif len(inp) == 2:
+        return ord(inp[0]), ord(inp[1])
+    else:
+        assert len(inp) == 3
+        base = ord(base)
+        first = ord(inp[0]) - base
+        second = ord(inp[1]) - base
+        third = ord(inp[2]) - base
+
+        return (0x80 | (third << 2) | (second >>3),
+                ((second << 5) | first) & 0xFF)
+
+
+# From packLanguage() in ResourceTypes.cpp
+def pack_language(language):
+    """Pack language in a two-byte tuple."""
+    return pack_language_or_region(language, 'a')
+
+
+# From packRegion() in ResourceTypes.cpp
+def pack_region(region):
+    """Pack region in a two-byte tuple."""
+    return pack_language_or_region(region, '0')
+
+
+def pack_to_uint32(locale):
+    """Pack language+region of locale into a 32-bit unsigned integer."""
+    lang, _, region = get_locale_parts(locale)
+    plang = pack_language(lang)
+    pregion = pack_region(region)
+    return (plang[0] << 24) | (plang[1] << 16) | (pregion[0] << 8) | pregion[1]
+
+
+def dump_script_codes(all_scripts):
+    """Dump the SCRIPT_CODES table."""
+    print 'const char SCRIPT_CODES[][4] = {'
+    for index, script in enumerate(all_scripts):
+        print "    /* %-2d */ {'%c', '%c', '%c', '%c'}," % (
+            index, script[0], script[1], script[2], script[3])
+    print '};'
+    print
+
+
+def dump_script_data(likely_script_dict, all_scripts):
+    """Dump the script data."""
+    print
+    print 'const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({'
+    for locale in sorted(likely_script_dict.keys()):
+        script = likely_script_dict[locale]
+        print '    {0x%08Xu, %2du}, // %s -> %s' % (
+            pack_to_uint32(locale),
+            all_scripts.index(script),
+            locale.replace('_', '-'),
+            script)
+    print '});'
+
+
+def pack_to_uint64(locale):
+    """Pack a full locale into a 64-bit unsigned integer."""
+    _, script, _ = get_locale_parts(locale)
+    return ((pack_to_uint32(locale) << 32) |
+            (ord(script[0]) << 24) |
+            (ord(script[1]) << 16) |
+            (ord(script[2]) << 8) |
+            ord(script[3]))
+
+
+def dump_representative_locales(representative_locales):
+    """Dump the set of representative locales."""
+    print
+    print 'std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({'
+    for locale in sorted(representative_locales):
+        print '    0x%08Xllu, // %s' % (
+            pack_to_uint64(locale),
+            locale)
+    print '});'
+
+
+def read_and_dump_likely_data(icu_data_dir):
+    """Read and dump the likely-script data."""
+    likely_subtags_txt = os.path.join(icu_data_dir, 'misc', 'likelySubtags.txt')
+    likely_script_dict, representative_locales = read_likely_subtags(
+        likely_subtags_txt)
+
+    all_scripts = list(set(likely_script_dict.values()))
+    assert len(all_scripts) <= 256
+    all_scripts.sort()
+
+    dump_script_codes(all_scripts)
+    dump_script_data(likely_script_dict, all_scripts)
+    dump_representative_locales(representative_locales)
+    return likely_script_dict
+
+
+def read_parent_data(icu_data_dir):
+    """Read locale parent data from ICU data files."""
+    all_icu_data_files = glob.glob(os.path.join(icu_data_dir, '*', '*.txt'))
+    parent_dict = {}
+    for data_file in all_icu_data_files:
+        locale = os.path.splitext(os.path.basename(data_file))[0]
+        with open(data_file) as input_file:
+            for line in input_file:
+                if '%%Parent' in line:
+                    parent = line[line.index('"')+1:line.rindex('"')]
+                    if locale in parent_dict:
+                        # Different files shouldn't have different parent info
+                        assert parent_dict[locale] == parent
+                    else:
+                        parent_dict[locale] = parent
+                elif locale.startswith('ar_') and 'default{"latn"}' in line:
+                    # Arabic parent overrides for ASCII digits. Since
+                    # Unicode extensions are not supported in ResourceTypes,
+                    # we will use ar-015 (Arabic, Northern Africa) instead
+                    # of the more correct ar-u-nu-latn.
+                    parent_dict[locale] = 'ar_015'
+    return parent_dict
+
+
+def get_likely_script(locale, likely_script_dict):
+    """Find the likely script for a locale, given the likely-script dictionary.
+    """
+    if locale.count('_') == 2:
+        # it already has a script
+        return locale.split('_')[1]
+    elif locale in likely_script_dict:
+        return likely_script_dict[locale]
+    else:
+        language = locale.split('_')[0]
+        return likely_script_dict[language]
+
+
+def dump_parent_data(script_organized_dict):
+    """Dump information for parents of locales."""
+    sorted_scripts = sorted(script_organized_dict.keys())
+    print
+    for script in sorted_scripts:
+        parent_dict = script_organized_dict[script]
+        print ('const std::unordered_map<uint32_t, uint32_t> %s_PARENTS({'
+            % script.upper())
+        for locale in sorted(parent_dict.keys()):
+            parent = parent_dict[locale]
+            print '    {0x%08Xu, 0x%08Xu}, // %s -> %s' % (
+                pack_to_uint32(locale),
+                pack_to_uint32(parent),
+                locale.replace('_', '-'),
+                parent.replace('_', '-'))
+        print '});'
+        print
+
+    print 'const struct {'
+    print '    const char script[4];'
+    print '    const std::unordered_map<uint32_t, uint32_t>* map;'
+    print '} SCRIPT_PARENTS[] = {'
+    for script in sorted_scripts:
+        print "    {{'%c', '%c', '%c', '%c'}, &%s_PARENTS}," % (
+            script[0], script[1], script[2], script[3],
+            script.upper())
+    print '};'
+
+
+def dump_parent_tree_depth(parent_dict):
+    """Find and dump the depth of the parent tree."""
+    max_depth = 1
+    for locale, _ in parent_dict.items():
+        depth = 1
+        while locale in parent_dict:
+            locale = parent_dict[locale]
+            depth += 1
+        max_depth = max(max_depth, depth)
+    assert max_depth < 5 # Our algorithms assume small max_depth
+    print
+    print 'const size_t MAX_PARENT_DEPTH = %d;' % max_depth
+
+
+def read_and_dump_parent_data(icu_data_dir, likely_script_dict):
+    """Read parent data from ICU and dump it."""
+    parent_dict = read_parent_data(icu_data_dir)
+    script_organized_dict = collections.defaultdict(dict)
+    for locale in parent_dict:
+        parent = parent_dict[locale]
+        if parent == 'root':
+            continue
+        script = get_likely_script(locale, likely_script_dict)
+        script_organized_dict[script][locale] = parent_dict[locale]
+    dump_parent_data(script_organized_dict)
+    dump_parent_tree_depth(parent_dict)
+
+
+def main():
+    """Read the data files from ICU and dump the output to a C++ file."""
+    source_root = sys.argv[1]
+    icu_data_dir = os.path.join(
+        source_root,
+        'external', 'icu', 'icu4c', 'source', 'data')
+
+    print '// Auto-generated by %s' % sys.argv[0]
+    print
+    likely_script_dict = read_and_dump_likely_data(icu_data_dir)
+    read_and_dump_parent_data(icu_data_dir, likely_script_dict)
+
+
+if __name__ == '__main__':
+    main()